{
  "description": "Connector defines a Tailscale node that will be deployed in the cluster. The\nnode can be configured to act as a Tailscale subnet router and/or a Tailscale\nexit node.\nConnector is a cluster-scoped resource.\nMore info:\nhttps://tailscale.com/kb/1441/kubernetes-operator-connector",
  "properties": {
    "apiVersion": {
      "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
      "type": [
        "string",
        "null"
      ]
    },
    "kind": {
      "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
      "type": [
        "string",
        "null"
      ]
    },
    "metadata": {
      "type": [
        "object",
        "null"
      ]
    },
    "spec": {
      "additionalProperties": false,
      "description": "ConnectorSpec describes the desired Tailscale component.\nMore info:\nhttps://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
      "properties": {
        "appConnector": {
          "additionalProperties": false,
          "description": "AppConnector defines whether the Connector device should act as a Tailscale app connector. A Connector that is\nconfigured as an app connector cannot be a subnet router or an exit node. If this field is unset, the\nConnector does not act as an app connector.\nNote that you will need to manually configure the permissions and the domains for the app connector via the\nAdmin panel.\nNote also that the main tested and supported use case of this config option is to deploy an app connector on\nKubernetes to access SaaS applications available on the public internet. Using the app connector to expose\ncluster workloads or other internal workloads to tailnet might work, but this is not a use case that we have\ntested or optimised for.\nIf you are using the app connector to access SaaS applications because you need a predictable egress IP that\ncan be whitelisted, it is also your responsibility to ensure that cluster traffic from the connector flows\nvia that predictable IP, for example by enforcing that cluster egress traffic is routed via an egress NAT\ndevice with a static IP address.\nhttps://tailscale.com/kb/1281/app-connectors",
          "properties": {
            "routes": {
              "description": "Routes are optional preconfigured routes for the domains routed via the app connector.\nIf not set, routes for the domains will be discovered dynamically.\nIf set, the app connector will immediately be able to route traffic using the preconfigured routes, but may\nalso dynamically discover other routes.\nhttps://tailscale.com/kb/1332/apps-best-practices#preconfiguration",
              "items": {
                "format": "cidr",
                "type": "string"
              },
              "minItems": 1,
              "type": [
                "array",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "exitNode": {
          "description": "ExitNode defines whether the Connector device should act as a Tailscale exit node. Defaults to false.\nThis field is mutually exclusive with the appConnector field.\nhttps://tailscale.com/kb/1103/exit-nodes",
          "type": [
            "boolean",
            "null"
          ]
        },
        "hostname": {
          "description": "Hostname is the tailnet hostname that should be assigned to the\nConnector node. If unset, hostname defaults to \u003cconnector\nname\u003e-connector. Hostname can contain lower case letters, numbers and\ndashes, it must not start or end with a dash and must be between 2\nand 63 characters long. This field should only be used when creating a connector\nwith an unspecified number of replicas, or a single replica.",
          "pattern": "^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$",
          "type": [
            "string",
            "null"
          ]
        },
        "hostnamePrefix": {
          "description": "HostnamePrefix specifies the hostname prefix for each\nreplica. Each device will have the integer number\nfrom its StatefulSet pod appended to this prefix to form the full hostname.\nHostnamePrefix can contain lower case letters, numbers and dashes, it\nmust not start with a dash and must be between 1 and 62 characters long.",
          "pattern": "^[a-z0-9][a-z0-9-]{0,61}$",
          "type": [
            "string",
            "null"
          ]
        },
        "proxyClass": {
          "description": "ProxyClass is the name of the ProxyClass custom resource that\ncontains configuration options that should be applied to the\nresources created for this Connector. If unset, the operator will\ncreate resources with the default configuration.",
          "type": [
            "string",
            "null"
          ]
        },
        "replicas": {
          "description": "Replicas specifies how many devices to create. Set this to enable\nhigh availability for app connectors, subnet routers, or exit nodes.\nhttps://tailscale.com/kb/1115/high-availability. Defaults to 1.",
          "format": "int32",
          "minimum": 0,
          "type": [
            "integer",
            "null"
          ]
        },
        "subnetRouter": {
          "additionalProperties": false,
          "description": "SubnetRouter defines subnet routes that the Connector device should\nexpose to tailnet as a Tailscale subnet router.\nhttps://tailscale.com/kb/1019/subnets/\nIf this field is unset, the device does not get configured as a Tailscale subnet router.\nThis field is mutually exclusive with the appConnector field.",
          "properties": {
            "advertiseRoutes": {
              "description": "AdvertiseRoutes refer to CIDRs that the subnet router should make\navailable. Route values must be strings that represent a valid IPv4\nor IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes.\nhttps://tailscale.com/kb/1201/4via6-subnets/",
              "items": {
                "format": "cidr",
                "type": "string"
              },
              "minItems": 1,
              "type": "array"
            }
          },
          "required": [
            "advertiseRoutes"
          ],
          "type": [
            "object",
            "null"
          ]
        },
        "tags": {
          "description": "Tags that the Tailscale node will be tagged with.\nDefaults to [tag:k8s].\nTo autoapprove the subnet routes or exit node defined by a Connector,\nyou can configure Tailscale ACLs to give these tags the necessary\npermissions.\nSee https://tailscale.com/kb/1337/acl-syntax#autoapprovers.\nIf you specify custom tags here, you must also make the operator an owner of these tags.\nSee  https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.\nTags cannot be changed once a Connector node has been created.\nTag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.",
          "items": {
            "pattern": "^tag:[a-zA-Z][a-zA-Z0-9-]*$",
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "tailnet": {
          "description": "Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this\nname must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set.",
          "type": [
            "string",
            "null"
          ],
          "x-kubernetes-validations": [
            {
              "message": "Connector tailnet is immutable",
              "rule": "self == oldSelf"
            }
          ]
        }
      },
      "type": "object",
      "x-kubernetes-validations": [
        {
          "message": "A Connector needs to have at least one of exit node, subnet router or app connector configured.",
          "rule": "has(self.subnetRouter) || (has(self.exitNode) \u0026\u0026 self.exitNode == true) || has(self.appConnector)"
        },
        {
          "message": "The appConnector field is mutually exclusive with exitNode and subnetRouter fields.",
          "rule": "!((has(self.subnetRouter) || (has(self.exitNode)  \u0026\u0026 self.exitNode == true)) \u0026\u0026 has(self.appConnector))"
        },
        {
          "message": "The hostname field cannot be specified when replicas is greater than 1.",
          "rule": "!(has(self.hostname) \u0026\u0026 has(self.replicas) \u0026\u0026 self.replicas \u003e 1)"
        },
        {
          "message": "The hostname and hostnamePrefix fields are mutually exclusive.",
          "rule": "!(has(self.hostname) \u0026\u0026 has(self.hostnamePrefix))"
        }
      ]
    },
    "status": {
      "additionalProperties": false,
      "description": "ConnectorStatus describes the status of the Connector. This is set\nand managed by the Tailscale operator.",
      "properties": {
        "conditions": {
          "description": "List of status conditions to indicate the status of the Connector.\nKnown condition types are `ConnectorReady`.",
          "items": {
            "additionalProperties": false,
            "description": "Condition contains details for one aspect of the current state of this API Resource.",
            "properties": {
              "lastTransitionTime": {
                "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.",
                "format": "date-time",
                "type": "string"
              },
              "message": {
                "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.",
                "maxLength": 32768,
                "type": "string"
              },
              "observedGeneration": {
                "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.",
                "format": "int64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "reason": {
                "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.",
                "maxLength": 1024,
                "minLength": 1,
                "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$",
                "type": "string"
              },
              "status": {
                "description": "status of the condition, one of True, False, Unknown.",
                "enum": [
                  "True",
                  "False",
                  "Unknown"
                ],
                "type": "string"
              },
              "type": {
                "description": "type of condition in CamelCase or in foo.example.com/CamelCase.",
                "maxLength": 316,
                "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$",
                "type": "string"
              }
            },
            "required": [
              "lastTransitionTime",
              "message",
              "reason",
              "status",
              "type"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ],
          "x-kubernetes-list-map-keys": [
            "type"
          ],
          "x-kubernetes-list-type": "map"
        },
        "devices": {
          "description": "Devices contains information on each device managed by the Connector resource.",
          "items": {
            "additionalProperties": false,
            "properties": {
              "hostname": {
                "description": "Hostname is the fully qualified domain name of the Connector replica.\nIf MagicDNS is enabled in your tailnet, it is the MagicDNS name of the\nnode.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "tailnetIPs": {
                "description": "TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)\nassigned to the Connector replica.",
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "hostname": {
          "description": "Hostname is the fully qualified domain name of the Connector node.\nIf MagicDNS is enabled in your tailnet, it is the MagicDNS name of the\nnode. When using multiple replicas, this field will be populated with the\nfirst replica's hostname. Use the Hostnames field for the full list\nof hostnames.",
          "type": [
            "string",
            "null"
          ]
        },
        "isAppConnector": {
          "description": "IsAppConnector is set to true if the Connector acts as an app connector.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "isExitNode": {
          "description": "IsExitNode is set to true if the Connector acts as an exit node.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "subnetRoutes": {
          "description": "SubnetRoutes are the routes currently exposed to tailnet via this\nConnector instance.",
          "type": [
            "string",
            "null"
          ]
        },
        "tailnetIPs": {
          "description": "TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)\nassigned to the Connector node.",
          "items": {
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        }
      },
      "type": [
        "object",
        "null"
      ]
    }
  },
  "required": [
    "spec"
  ],
  "type": "object"
}