{
  "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"
    },
    "spec": {
      "additionalProperties": false,
      "description": "Spec describes the desired state of the PeerRelay.\nMore info:\nhttps://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
      "properties": {
        "aws": {
          "additionalProperties": false,
          "description": "AWS contains configuration for pinning each replica to a specific AWS Elastic IP and subnet. Only meaningful\nwhen running on EKS with the AWS Load Balancer Controller. When set, the per-replica values override any\naws-load-balancer-eip-allocations or aws-load-balancer-subnets values supplied via spec.service.annotations.\n\nLeave this unset unless the peer relays must be reachable on addresses you control. Pinning a subnet\nconfines a replica's load balancer to that subnet's availability zone, and an AWS Network Load Balancer\nonly forwards to targets in a zone that is enabled on it, so a replica whose pod is scheduled into any\nother zone stops receiving traffic. Setting this field therefore also requires pinning the pods to the\nmatching zone with a ProxyClass, as described on ElasticIPs. Without this field the AWS Load Balancer\nController instead provisions each load balancer across every zone it discovers, and the operator turns on\ncross-zone load balancing so the replica is reachable wherever it happens to be scheduled, with no\nscheduling constraints needed.",
          "properties": {
            "elasticIPs": {
              "description": "ElasticIPs pins each replica to a specific AWS EIP allocation and subnet. Only meaningful when Network Load\nBalancers are provisioned by the AWS Load Balancer Controller. ElasticIPs supplies one allocation-subnet pair\nper replica: replica N uses ElasticIPs[N]. The list must be at least as long as spec.replicas so every replica\nhas a distinct EIP; extra entries are permitted so that scale-up doesn't immediately trip validation.\n\nPinning a subnet enables only that subnet's availability zone on the replica's load balancer, and a Network\nLoad Balancer only forwards to targets in an enabled zone. Nothing constrains the scheduler to place the\nreplica's pod in that zone, so a pod scheduled elsewhere, including after a reschedule, becomes unreachable\non its Elastic IP while still appearing healthy.\n\nEvery replica of a PeerRelay shares one pod template, so a ProxyClass referenced by spec.proxyClass can\nconfine the pods to a zone but cannot place different replicas in different zones. To use this field\nsafely, name subnets in a single availability zone and pin the pods to that same zone with a ProxyClass\nsetting spec.statefulSet.pod.nodeSelector to topology.kubernetes.io/zone. Note that this trades the zone\nredundancy that running several replicas would otherwise buy. Spreading replicas across zones with their\nown Elastic IPs needs a per-replica scheduling constraint that neither PeerRelay nor ProxyClass can\ncurrently express.\n\nWhen set, the reconciler stamps\nservice.beta.kubernetes.io/aws-load-balancer-eip-allocations and\nservice.beta.kubernetes.io/aws-load-balancer-subnets on each per-replica Service, overriding any values in\nspec.service.annotations.",
              "items": {
                "additionalProperties": false,
                "description": "PeerRelayAWSElasticIP pairs an EIP allocation with the subnet it is attached to.",
                "properties": {
                  "allocationID": {
                    "description": "AllocationID is the AWS EIP allocation ID (e.g. eipalloc-0123abcd) whose public IP this replica is reachable\non. Stamped as service.beta.kubernetes.io/aws-load-balancer-eip-allocations on the replica's Service.",
                    "pattern": "^eipalloc-[0-9a-f]+$",
                    "type": "string"
                  },
                  "subnetID": {
                    "description": "SubnetID is the AWS subnet the replica's load balancer is provisioned in (e.g. subnet-0123abcd). It must be\na public subnet, and no two replicas may name subnets in the same availability zone, since a load balancer\naccepts only one Elastic IP per zone. A standard VPC Elastic IP is regional rather than zonal, so it takes\nthe zone of whichever subnet it is paired with here. Stamped as\nservice.beta.kubernetes.io/aws-load-balancer-subnets on the replica's Service.",
                    "pattern": "^subnet-[0-9a-f]+$",
                    "type": "string"
                  }
                },
                "required": [
                  "allocationID",
                  "subnetID"
                ],
                "type": "object"
              },
              "minItems": 1,
              "type": "array",
              "x-kubernetes-list-type": "atomic"
            }
          },
          "required": [
            "elasticIPs"
          ],
          "type": [
            "object",
            "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 PeerRelay. If unset, the operator will\ncreate resources with the default configuration.",
          "type": [
            "string",
            "null"
          ]
        },
        "replicas": {
          "default": 1,
          "description": "Replicas specifies how many devices to create. Set this to enable\nhigh availability for peer relays.\nhttps://tailscale.com/kb/1115/high-availability. Defaults to 1.",
          "format": "int32",
          "minimum": 0,
          "type": [
            "integer",
            "null"
          ]
        },
        "service": {
          "additionalProperties": false,
          "description": "Service contains configuration values to modify the LoadBalancer service used to expose the peer relay.",
          "properties": {
            "annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "description": "Annotations to apply to the LoadBalancer service. Any annotations that conflict with those used by known\ncloud providers to ensure IP addresses rather than DNS names are ignored.",
              "type": [
                "object",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "tags": {
          "description": "Tags that the Tailscale node will be tagged with.\nDefaults to [tag:k8s].\nTo autoapprove the device defined by a PeerRelay,\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 PeerRelay 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 PeerRelay 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": "PeerRelay tailnet is immutable",
              "rule": "self == oldSelf"
            }
          ]
        }
      },
      "type": "object",
      "x-kubernetes-validations": [
        {
          "message": "spec.aws.elasticIPs must contain at least one entry per replica",
          "rule": "!has(self.aws) || !has(self.aws.elasticIPs) || self.aws.elasticIPs.size() \u003e= self.replicas"
        }
      ]
    },
    "status": {
      "additionalProperties": false,
      "description": "Status describes the status of the PeerRelay. This is set\nand managed by the Tailscale operator.",
      "properties": {
        "conditions": {
          "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"
        },
        "endpoints": {
          "description": "Endpoints lists the public address:port pairs each peer relay replica is reachable on. Entries appear as the\nunderlying cloud provisions each Service. A replica has one entry per address its LoadBalancer Service was\ngiven, which is usually one, but a load balancer spanning several availability zones has an address in each\nand every one of them is listed.",
          "items": {
            "additionalProperties": false,
            "properties": {
              "address": {
                "description": "Address is the public IP or hostname the cloud has allocated for this replica's LoadBalancer Service.\nPeers reach this relay by connecting to Address:Port over UDP.",
                "type": "string"
              },
              "port": {
                "description": "Port is the UDP port the peer relay listens on.",
                "format": "int32",
                "type": "integer"
              },
              "replica": {
                "description": "Replica is the zero-based index of the peer relay replica this endpoint targets.",
                "format": "int32",
                "type": "integer"
              }
            },
            "required": [
              "address",
              "port",
              "replica"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ],
          "x-kubernetes-list-map-keys": [
            "replica",
            "address"
          ],
          "x-kubernetes-list-type": "map"
        }
      },
      "type": [
        "object",
        "null"
      ]
    }
  },
  "required": [
    "metadata",
    "spec"
  ],
  "type": "object"
}