Add Proxy API types with CEL validation (Step 1)
Full ProxySpec/ProxyStatus/Proxy types per the plan: PlacementSpec,
CloudInitSpec, EndpointSpec, HealthCheckSpec, SecretKeySelector, all
defaults, and 7 CEL XValidation rules enforcing mode/provider
immutability, provider/endpoint required-iff-Managed/External, and
cloud-init exactly-one-of inline/secretRef.
Applies the four corrections identified during planning that would
otherwise be silent bugs: MaxLeases as *int32 (so an explicit 0 survives
Go round-trips instead of re-defaulting to 5), HealthCheck's
default={} marker (so nested defaults apply even when the field is
omitted entirely), MinLength=1 on Provider/CloudInit.Inline (so the CEL
has() checks stay simple), and listType=map on Conditions.
Adds pure helpers (EffectivePort, EffectiveHost, HealthCheckOrDefault,
MaxLeasesOrDefault) with table-driven tests, for use by the health
engine, discovery API, and spec-hash computation in later steps.
Patches the scaffolded placeholder controller test's resource literal to
a schema-valid spec so it survives the new CRD validation — the test
itself is rewritten wholesale in Step 4 alongside the real reconciler.
Regenerated deepcopy and the CRD; make test green (envtest confirmed all
7 CEL rules enforced by a real apiserver).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,31 @@ spec:
|
||||
kind: Proxy
|
||||
listKind: ProxyList
|
||||
plural: proxies
|
||||
shortNames:
|
||||
- px
|
||||
singular: proxy
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.mode
|
||||
name: Mode
|
||||
type: string
|
||||
- jsonPath: .spec.provider
|
||||
name: Provider
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
- jsonPath: .status.ip
|
||||
name: IP
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Healthy")].status
|
||||
name: Healthy
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Proxy is the Schema for the proxies API
|
||||
@@ -39,25 +60,198 @@ spec:
|
||||
spec:
|
||||
description: spec defines the desired state of Proxy
|
||||
properties:
|
||||
foo:
|
||||
description: foo is an example field of Proxy. Edit proxy_types.go
|
||||
to remove/update
|
||||
attributes:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
attributes are selection attributes exposed to the discovery API
|
||||
(geo, asn, purpose, ...). Deliberately separate from Kubernetes object
|
||||
labels, which stay an operator implementation concern.
|
||||
maxProperties: 32
|
||||
type: object
|
||||
cloudInit:
|
||||
description: |-
|
||||
cloudInit supplies the VM's cloud-init user-data. Only meaningful for
|
||||
Managed proxies. Changing the resolved content triggers replacement.
|
||||
properties:
|
||||
inline:
|
||||
description: inline is the literal cloud-init user-data.
|
||||
maxLength: 262144
|
||||
minLength: 1
|
||||
type: string
|
||||
secretRef:
|
||||
description: secretRef points at a Secret key holding the cloud-init
|
||||
user-data.
|
||||
properties:
|
||||
key:
|
||||
default: user-data
|
||||
description: key is the data key holding the cloud-init user-data.
|
||||
type: string
|
||||
name:
|
||||
description: name is the Secret's name.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: exactly one of inline or secretRef must be set
|
||||
rule: has(self.inline) != has(self.secretRef)
|
||||
endpoint:
|
||||
description: |-
|
||||
endpoint identifies an External proxy's network location. Required
|
||||
iff mode is External; must be unset iff mode is Managed.
|
||||
properties:
|
||||
host:
|
||||
description: host is the proxy's hostname or IP address.
|
||||
minLength: 1
|
||||
type: string
|
||||
port:
|
||||
default: 3128
|
||||
description: port is the port the proxy listens on.
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
healthCheck:
|
||||
default: {}
|
||||
description: |-
|
||||
healthCheck configures the through-the-proxy healthcheck. All nested
|
||||
fields are defaulted; the default={} marker ensures a proxy that omits
|
||||
healthCheck entirely still gets every nested default.
|
||||
properties:
|
||||
expectedStatusCodes:
|
||||
default:
|
||||
- 200
|
||||
- 204
|
||||
description: |-
|
||||
expectedStatusCodes are the HTTP status codes a probe response must
|
||||
match to count as successful.
|
||||
items:
|
||||
format: int32
|
||||
type: integer
|
||||
maxItems: 8
|
||||
type: array
|
||||
failureThreshold:
|
||||
default: 3
|
||||
description: |-
|
||||
failureThreshold is the number of consecutive failed probes required
|
||||
to transition Healthy -> False.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
intervalSeconds:
|
||||
default: 30
|
||||
description: intervalSeconds is the time between probes for a
|
||||
given proxy.
|
||||
format: int32
|
||||
minimum: 5
|
||||
type: integer
|
||||
probeURL:
|
||||
default: https://www.gstatic.com/generate_204
|
||||
description: probeURL is fetched through the proxy on every probe.
|
||||
minLength: 1
|
||||
type: string
|
||||
successThreshold:
|
||||
default: 1
|
||||
description: |-
|
||||
successThreshold is the number of consecutive successful probes
|
||||
required to transition Healthy -> True.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
default: 5
|
||||
description: |-
|
||||
timeoutSeconds bounds a single probe, including the CONNECT tunnel
|
||||
setup and the TLS handshake through it.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
maxLeases:
|
||||
default: 5
|
||||
description: |-
|
||||
maxLeases is the maximum number of concurrent leases handed out for
|
||||
this proxy. 0 means unleasable (list-only visibility). A pointer so an
|
||||
explicit 0 survives Go round-trips instead of being re-defaulted to 5.
|
||||
format: int32
|
||||
maximum: 1000
|
||||
minimum: 0
|
||||
type: integer
|
||||
mode:
|
||||
description: |-
|
||||
mode selects who owns the VM lifecycle: Managed (operator creates it)
|
||||
or External (operator only tracks and healthchecks it). Immutable.
|
||||
enum:
|
||||
- Managed
|
||||
- External
|
||||
type: string
|
||||
placement:
|
||||
description: |-
|
||||
placement is provider-opaque placement/size configuration. Only
|
||||
meaningful for Managed proxies.
|
||||
properties:
|
||||
image:
|
||||
description: image is the boot image reference.
|
||||
type: string
|
||||
machineType:
|
||||
description: machineType is the provider's machine/instance type
|
||||
(e.g. "e2-micro").
|
||||
type: string
|
||||
region:
|
||||
description: region is the provider's region identifier (e.g.
|
||||
"europe-west1").
|
||||
type: string
|
||||
zone:
|
||||
description: zone is the provider's zone identifier (e.g. "europe-west1-b").
|
||||
type: string
|
||||
type: object
|
||||
port:
|
||||
default: 3128
|
||||
description: |-
|
||||
port is the port the proxy listens on once the VM is up. Only
|
||||
meaningful for Managed proxies; External proxies use endpoint.port.
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
provider:
|
||||
description: |-
|
||||
provider is the name of a configured provider ("mock", "gcp-eu", ...).
|
||||
Required iff mode is Managed; must be unset iff mode is External.
|
||||
Immutable.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- mode
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: mode is immutable
|
||||
rule: self.mode == oldSelf.mode
|
||||
- message: provider is immutable
|
||||
rule: has(self.provider) == has(oldSelf.provider) && (!has(self.provider)
|
||||
|| self.provider == oldSelf.provider)
|
||||
- message: provider is required when mode is Managed
|
||||
rule: self.mode != 'Managed' || has(self.provider)
|
||||
- message: provider must not be set when mode is External
|
||||
rule: self.mode != 'External' || !has(self.provider)
|
||||
- message: endpoint is required when mode is External
|
||||
rule: self.mode != 'External' || has(self.endpoint)
|
||||
- message: endpoint must not be set when mode is Managed
|
||||
rule: self.mode != 'Managed' || !has(self.endpoint)
|
||||
status:
|
||||
description: status defines the observed state of Proxy
|
||||
properties:
|
||||
conditions:
|
||||
description: |-
|
||||
conditions represent the current state of the Proxy resource.
|
||||
Each condition has a unique type and reflects the status of a specific aspect of the resource.
|
||||
|
||||
Standard condition types include:
|
||||
- "Available": the resource is fully functional
|
||||
- "Progressing": the resource is being created or updated
|
||||
- "Degraded": the resource failed to reach or maintain its desired state
|
||||
|
||||
The status of each condition is one of True, False, or Unknown.
|
||||
Standard types are Provisioned and Healthy.
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
@@ -116,6 +310,37 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
ip:
|
||||
description: |-
|
||||
ip is the proxy's current IP address: the VM's address for Managed
|
||||
proxies, or spec.endpoint.host for External proxies.
|
||||
type: string
|
||||
lastHealthCheckTime:
|
||||
description: |-
|
||||
lastHealthCheckTime is the time of the last status-affecting probe,
|
||||
not the time of the most recent probe — probes that don't change the
|
||||
Healthy condition or move latency materially don't write status.
|
||||
format: date-time
|
||||
type: string
|
||||
latencyMillis:
|
||||
description: latencyMillis is the latency of the last status-affecting
|
||||
probe.
|
||||
format: int64
|
||||
type: integer
|
||||
observedGeneration:
|
||||
description: observedGeneration is the .metadata.generation last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
description: |-
|
||||
phase is a high-level, human-readable summary derived from conditions.
|
||||
One of Pending, Provisioning, Ready, Unhealthy, Deleting, Failed.
|
||||
type: string
|
||||
providerID:
|
||||
description: |-
|
||||
providerID is the opaque cloud resource ID returned by the provider.
|
||||
Empty for External proxies.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Reference in New Issue
Block a user