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:
2026-08-07 21:01:56 +02:00
parent 26991fbe13
commit f28766fce3
8 changed files with 985 additions and 41 deletions

87
api/v1alpha1/helpers.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
// EffectivePort returns the port a client should use to reach the proxy:
// spec.endpoint.port for External proxies, spec.port for Managed proxies.
// Falls back to DefaultPort if the relevant field is unset, so callers that
// bypass CRD structural defaulting (unit tests, fake clients) still get a
// sane value.
func (p *Proxy) EffectivePort() int32 {
if p.Spec.Mode == ModeExternal && p.Spec.Endpoint != nil {
if p.Spec.Endpoint.Port != 0 {
return p.Spec.Endpoint.Port
}
return DefaultPort
}
if p.Spec.Port != 0 {
return p.Spec.Port
}
return DefaultPort
}
// EffectiveHost returns the host a client should use to reach the proxy:
// spec.endpoint.host for External proxies, status.ip for Managed proxies
// (populated once the VM is running).
func (p *Proxy) EffectiveHost() string {
if p.Spec.Mode == ModeExternal && p.Spec.Endpoint != nil {
return p.Spec.Endpoint.Host
}
return p.Status.IP
}
// HealthCheckOrDefault returns spec.healthCheck with every unset field
// filled from its default. CRD structural defaulting (the default={} marker
// on ProxySpec.HealthCheck) already does this for objects that went through
// the API server; this is for callers that didn't (unit tests, fake
// clients, or a Proxy constructed directly in Go).
func (p *Proxy) HealthCheckOrDefault() HealthCheckSpec {
var hc HealthCheckSpec
if p.Spec.HealthCheck != nil {
hc = *p.Spec.HealthCheck
}
if hc.ProbeURL == "" {
hc.ProbeURL = DefaultProbeURL
}
if hc.IntervalSeconds == 0 {
hc.IntervalSeconds = DefaultHealthCheckIntervalSeconds
}
if hc.TimeoutSeconds == 0 {
hc.TimeoutSeconds = DefaultHealthCheckTimeoutSeconds
}
if hc.FailureThreshold == 0 {
hc.FailureThreshold = DefaultFailureThreshold
}
if hc.SuccessThreshold == 0 {
hc.SuccessThreshold = DefaultSuccessThreshold
}
if len(hc.ExpectedStatusCodes) == 0 {
hc.ExpectedStatusCodes = append([]int32(nil), DefaultExpectedStatusCodes...)
}
return hc
}
// MaxLeasesOrDefault returns spec.maxLeases, or DefaultMaxLeases if unset.
// spec.maxLeases is a pointer specifically so an explicit 0 (unleasable) is
// distinguishable from "unset" and survives Go round-trips; this helper
// preserves that distinction.
func (p *Proxy) MaxLeasesOrDefault() int32 {
if p.Spec.MaxLeases != nil {
return *p.Spec.MaxLeases
}
return DefaultMaxLeases
}

View File

@@ -0,0 +1,190 @@
/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"reflect"
"testing"
)
func TestEffectivePort(t *testing.T) {
t.Parallel()
tests := []struct {
name string
spec ProxySpec
want int32
}{
{
name: "managed with explicit port",
spec: ProxySpec{Mode: ModeManaged, Port: 8080},
want: 8080,
},
{
name: "managed with unset port falls back to default",
spec: ProxySpec{Mode: ModeManaged},
want: DefaultPort,
},
{
name: "external with explicit endpoint port",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "1.2.3.4", Port: 9999}},
want: 9999,
},
{
name: "external with unset endpoint port falls back to default",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "1.2.3.4"}},
want: DefaultPort,
},
{
name: "external with nil endpoint falls back to spec.port",
spec: ProxySpec{Mode: ModeExternal, Port: 3000},
want: 3000,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec}
if got := p.EffectivePort(); got != tc.want {
t.Errorf("EffectivePort() = %d, want %d", got, tc.want)
}
})
}
}
func TestEffectiveHost(t *testing.T) {
t.Parallel()
tests := []struct {
name string
spec ProxySpec
status ProxyStatus
want string
}{
{
name: "managed uses status.ip",
spec: ProxySpec{Mode: ModeManaged},
status: ProxyStatus{IP: "10.0.0.5"},
want: "10.0.0.5",
},
{
name: "external uses endpoint.host",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "proxy.example.com"}},
want: "proxy.example.com",
},
{
name: "external with nil endpoint falls back to status.ip",
spec: ProxySpec{Mode: ModeExternal},
status: ProxyStatus{IP: "10.0.0.6"},
want: "10.0.0.6",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec, Status: tc.status}
if got := p.EffectiveHost(); got != tc.want {
t.Errorf("EffectiveHost() = %q, want %q", got, tc.want)
}
})
}
}
func TestHealthCheckOrDefault(t *testing.T) {
t.Parallel()
fullDefault := HealthCheckSpec{
ProbeURL: DefaultProbeURL,
IntervalSeconds: DefaultHealthCheckIntervalSeconds,
TimeoutSeconds: DefaultHealthCheckTimeoutSeconds,
FailureThreshold: DefaultFailureThreshold,
SuccessThreshold: DefaultSuccessThreshold,
ExpectedStatusCodes: []int32{200, 204},
}
t.Run("nil healthCheck returns full default", func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: ProxySpec{}}
got := p.HealthCheckOrDefault()
if !reflect.DeepEqual(got, fullDefault) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, fullDefault)
}
})
t.Run("partial healthCheck fills only unset fields", func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: ProxySpec{HealthCheck: &HealthCheckSpec{
ProbeURL: "http://internal/probe",
FailureThreshold: 7,
}}}
got := p.HealthCheckOrDefault()
want := fullDefault
want.ProbeURL = "http://internal/probe"
want.FailureThreshold = 7
if !reflect.DeepEqual(got, want) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, want)
}
})
t.Run("fully set healthCheck passes through unchanged", func(t *testing.T) {
t.Parallel()
custom := HealthCheckSpec{
ProbeURL: "http://internal/probe",
IntervalSeconds: 10,
TimeoutSeconds: 2,
FailureThreshold: 5,
SuccessThreshold: 2,
ExpectedStatusCodes: []int32{200},
}
p := &Proxy{Spec: ProxySpec{HealthCheck: &custom}}
got := p.HealthCheckOrDefault()
if !reflect.DeepEqual(got, custom) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, custom)
}
})
t.Run("does not mutate the original spec", func(t *testing.T) {
t.Parallel()
hc := &HealthCheckSpec{ProbeURL: "http://internal/probe"}
p := &Proxy{Spec: ProxySpec{HealthCheck: hc}}
_ = p.HealthCheckOrDefault()
if hc.IntervalSeconds != 0 {
t.Errorf("original HealthCheckSpec was mutated: IntervalSeconds = %d, want 0", hc.IntervalSeconds)
}
})
}
func TestMaxLeasesOrDefault(t *testing.T) {
t.Parallel()
zero := int32(0)
seven := int32(7)
tests := []struct {
name string
spec ProxySpec
want int32
}{
{name: "unset falls back to default", spec: ProxySpec{}, want: DefaultMaxLeases},
{name: "explicit zero is preserved (unleasable)", spec: ProxySpec{MaxLeases: &zero}, want: 0},
{name: "explicit non-zero is preserved", spec: ProxySpec{MaxLeases: &seven}, want: 7},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec}
if got := p.MaxLeasesOrDefault(); got != tc.want {
t.Errorf("MaxLeasesOrDefault() = %d, want %d", got, tc.want)
}
})
}
}

View File

@@ -21,46 +21,302 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// ProvisioningMode selects who owns the lifecycle of the proxy VM.
type ProvisioningMode string
// ProxySpec defines the desired state of Proxy
type ProxySpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// The following markers will use OpenAPI v3 schema to validate the value
// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html
const (
// ModeManaged means the operator creates, monitors, and deletes the VM.
ModeManaged ProvisioningMode = "Managed"
// ModeExternal means the VM exists outside the operator's control; the
// operator only tracks and healthchecks it.
ModeExternal ProvisioningMode = "External"
)
// foo is an example field of Proxy. Edit proxy_types.go to remove/update
// ProxyPhase is a high-level, human-readable summary of status.conditions.
type ProxyPhase string
const (
PhasePending ProxyPhase = "Pending"
PhaseProvisioning ProxyPhase = "Provisioning"
PhaseReady ProxyPhase = "Ready"
PhaseUnhealthy ProxyPhase = "Unhealthy"
PhaseDeleting ProxyPhase = "Deleting"
PhaseFailed ProxyPhase = "Failed"
)
const (
// ConditionProvisioned reflects the state of the underlying VM (Managed)
// or endpoint (External).
ConditionProvisioned = "Provisioned"
// ConditionHealthy reflects the result of the through-the-proxy healthcheck.
ConditionHealthy = "Healthy"
// FinalizerName is set on Managed proxies so deletion can clean up the
// provider-side VM before the CR is removed. External proxies never get
// this finalizer.
FinalizerName = "crawl.example.com/proxy-cleanup"
// AnnotationSpecHash stores the hash of the replacement-triggering spec
// fields (placement, resolved cloud-init, port) as of the last successful
// provision. A mismatch against the freshly computed hash means the VM
// must be replaced.
AnnotationSpecHash = "crawl.example.com/spec-hash"
)
// Defaults, applied both by CRD structural defaulting (kubebuilder:default
// markers below) and by the OrDefault helpers for callers that bypass the
// API server (unit tests, fake clients).
const (
DefaultPort int32 = 3128
DefaultMaxLeases int32 = 5
DefaultProbeURL string = "https://www.gstatic.com/generate_204"
DefaultHealthCheckIntervalSeconds int32 = 30
DefaultHealthCheckTimeoutSeconds int32 = 5
DefaultFailureThreshold int32 = 3
DefaultSuccessThreshold int32 = 1
DefaultCloudInitSecretKey string = "user-data"
)
// DefaultExpectedStatusCodes is the default set of HTTP status codes a
// healthcheck probe treats as success.
var DefaultExpectedStatusCodes = []int32{200, 204}
// PlacementSpec is provider-opaque placement/size configuration. It is kept
// as a small typed struct rather than map[string]string; providers ignore
// fields that don't apply to them.
type PlacementSpec struct {
// region is the provider's region identifier (e.g. "europe-west1").
// +optional
Foo *string `json:"foo,omitempty"`
Region string `json:"region,omitempty"`
// zone is the provider's zone identifier (e.g. "europe-west1-b").
// +optional
Zone string `json:"zone,omitempty"`
// machineType is the provider's machine/instance type (e.g. "e2-micro").
// +optional
MachineType string `json:"machineType,omitempty"`
// image is the boot image reference.
// +optional
Image string `json:"image,omitempty"`
}
// SecretKeySelector references a key within a Secret in the same namespace.
type SecretKeySelector struct {
// name is the Secret's name.
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
// key is the data key holding the cloud-init user-data.
// +kubebuilder:default=user-data
// +optional
Key string `json:"key,omitempty"`
}
// CloudInitSpec supplies cloud-init user-data either inline or from a
// Secret. Exactly one of Inline or SecretRef must be set. Changing the
// resolved content on a Managed proxy triggers replacement.
//
// +kubebuilder:validation:XValidation:rule="has(self.inline) != has(self.secretRef)",message="exactly one of inline or secretRef must be set"
type CloudInitSpec struct {
// inline is the literal cloud-init user-data.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=262144
// +optional
Inline string `json:"inline,omitempty"`
// secretRef points at a Secret key holding the cloud-init user-data.
// +optional
SecretRef *SecretKeySelector `json:"secretRef,omitempty"`
}
// EndpointSpec identifies an External proxy's network location.
type EndpointSpec struct {
// host is the proxy's hostname or IP address.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// port is the port the proxy listens on.
// +kubebuilder:default=3128
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
// +optional
Port int32 `json:"port,omitempty"`
}
// HealthCheckSpec configures the through-the-proxy healthcheck. All fields
// are defaulted, both by the CRD (kubebuilder:default markers, activated by
// the default={} marker on ProxySpec.HealthCheck) and by
// Proxy.HealthCheckOrDefault for callers that bypass the API server.
type HealthCheckSpec struct {
// probeURL is fetched through the proxy on every probe.
// +kubebuilder:default="https://www.gstatic.com/generate_204"
// +kubebuilder:validation:MinLength=1
// +optional
ProbeURL string `json:"probeURL,omitempty"`
// intervalSeconds is the time between probes for a given proxy.
// +kubebuilder:default=30
// +kubebuilder:validation:Minimum=5
// +optional
IntervalSeconds int32 `json:"intervalSeconds,omitempty"`
// timeoutSeconds bounds a single probe, including the CONNECT tunnel
// setup and the TLS handshake through it.
// +kubebuilder:default=5
// +kubebuilder:validation:Minimum=1
// +optional
TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`
// failureThreshold is the number of consecutive failed probes required
// to transition Healthy -> False.
// +kubebuilder:default=3
// +kubebuilder:validation:Minimum=1
// +optional
FailureThreshold int32 `json:"failureThreshold,omitempty"`
// successThreshold is the number of consecutive successful probes
// required to transition Healthy -> True.
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +optional
SuccessThreshold int32 `json:"successThreshold,omitempty"`
// expectedStatusCodes are the HTTP status codes a probe response must
// match to count as successful.
// +kubebuilder:default={200,204}
// +kubebuilder:validation:MaxItems=8
// +optional
ExpectedStatusCodes []int32 `json:"expectedStatusCodes,omitempty"`
}
// ProxySpec defines the desired state of Proxy.
//
// Cross-field rules are deliberately split into one XValidation marker per
// concern: a rule referencing oldSelf is skipped on CREATE, so an
// immutability rule and a required-iff rule must never be combined into one
// `&&`-ed expression, or the required-iff half would silently stop applying
// on CREATE. The provider-immutability rule uses has(self.x)==has(oldSelf.x)
// rather than a field-level self==oldSelf rule because Provider is optional:
// a field-level rule does not fire when the field is absent on either side,
// which would silently allow adding or removing it after creation.
//
// +kubebuilder:validation:XValidation:rule="self.mode == oldSelf.mode",message="mode is immutable"
// +kubebuilder:validation:XValidation:rule="has(self.provider) == has(oldSelf.provider) && (!has(self.provider) || self.provider == oldSelf.provider)",message="provider is immutable"
// +kubebuilder:validation:XValidation:rule="self.mode != 'Managed' || has(self.provider)",message="provider is required when mode is Managed"
// +kubebuilder:validation:XValidation:rule="self.mode != 'External' || !has(self.provider)",message="provider must not be set when mode is External"
// +kubebuilder:validation:XValidation:rule="self.mode != 'External' || has(self.endpoint)",message="endpoint is required when mode is External"
// +kubebuilder:validation:XValidation:rule="self.mode != 'Managed' || !has(self.endpoint)",message="endpoint must not be set when mode is Managed"
type ProxySpec struct {
// mode selects who owns the VM lifecycle: Managed (operator creates it)
// or External (operator only tracks and healthchecks it). Immutable.
// +kubebuilder:validation:Enum=Managed;External
Mode ProvisioningMode `json:"mode"`
// provider is the name of a configured provider ("mock", "gcp-eu", ...).
// Required iff mode is Managed; must be unset iff mode is External.
// Immutable.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +optional
Provider string `json:"provider,omitempty"`
// placement is provider-opaque placement/size configuration. Only
// meaningful for Managed proxies.
// +optional
Placement *PlacementSpec `json:"placement,omitempty"`
// cloudInit supplies the VM's cloud-init user-data. Only meaningful for
// Managed proxies. Changing the resolved content triggers replacement.
// +optional
CloudInit *CloudInitSpec `json:"cloudInit,omitempty"`
// endpoint identifies an External proxy's network location. Required
// iff mode is External; must be unset iff mode is Managed.
// +optional
Endpoint *EndpointSpec `json:"endpoint,omitempty"`
// port is the port the proxy listens on once the VM is up. Only
// meaningful for Managed proxies; External proxies use endpoint.port.
// +kubebuilder:default=3128
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
// +optional
Port int32 `json:"port,omitempty"`
// attributes are selection attributes exposed to the discovery API
// (geo, asn, purpose, ...). Deliberately separate from Kubernetes object
// labels, which stay an operator implementation concern.
// +kubebuilder:validation:MaxProperties=32
// +optional
Attributes map[string]string `json:"attributes,omitempty"`
// 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.
// +kubebuilder:default={}
// +optional
HealthCheck *HealthCheckSpec `json:"healthCheck,omitempty"`
// 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.
// +kubebuilder:default=5
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=1000
// +optional
MaxLeases *int32 `json:"maxLeases,omitempty"`
}
// ProxyStatus defines the observed state of Proxy.
type ProxyStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// phase is a high-level, human-readable summary derived from conditions.
// One of Pending, Provisioning, Ready, Unhealthy, Deleting, Failed.
// +optional
Phase ProxyPhase `json:"phase,omitempty"`
// For Kubernetes API conventions, see:
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// providerID is the opaque cloud resource ID returned by the provider.
// Empty for External proxies.
// +optional
ProviderID string `json:"providerID,omitempty"`
// ip is the proxy's current IP address: the VM's address for Managed
// proxies, or spec.endpoint.host for External proxies.
// +optional
IP string `json:"ip,omitempty"`
// 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.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// 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.
// +optional
LastHealthCheckTime *metav1.Time `json:"lastHealthCheckTime,omitempty"`
// latencyMillis is the latency of the last status-affecting probe.
// +optional
LatencyMillis int64 `json:"latencyMillis,omitempty"`
// observedGeneration is the .metadata.generation last reconciled.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=px
// +kubebuilder:printcolumn:name="Mode",type=string,JSONPath=`.spec.mode`
// +kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.provider`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="IP",type=string,JSONPath=`.status.ip`
// +kubebuilder:printcolumn:name="Healthy",type=string,JSONPath=`.status.conditions[?(@.type=="Healthy")].status`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// Proxy is the Schema for the proxies API
type Proxy struct {

View File

@@ -25,6 +25,76 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CloudInitSpec) DeepCopyInto(out *CloudInitSpec) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(SecretKeySelector)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudInitSpec.
func (in *CloudInitSpec) DeepCopy() *CloudInitSpec {
if in == nil {
return nil
}
out := new(CloudInitSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointSpec) DeepCopyInto(out *EndpointSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointSpec.
func (in *EndpointSpec) DeepCopy() *EndpointSpec {
if in == nil {
return nil
}
out := new(EndpointSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HealthCheckSpec) DeepCopyInto(out *HealthCheckSpec) {
*out = *in
if in.ExpectedStatusCodes != nil {
in, out := &in.ExpectedStatusCodes, &out.ExpectedStatusCodes
*out = make([]int32, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckSpec.
func (in *HealthCheckSpec) DeepCopy() *HealthCheckSpec {
if in == nil {
return nil
}
out := new(HealthCheckSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PlacementSpec) DeepCopyInto(out *PlacementSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementSpec.
func (in *PlacementSpec) DeepCopy() *PlacementSpec {
if in == nil {
return nil
}
out := new(PlacementSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Proxy) DeepCopyInto(out *Proxy) {
*out = *in
@@ -87,9 +157,36 @@ func (in *ProxyList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProxySpec) DeepCopyInto(out *ProxySpec) {
*out = *in
if in.Foo != nil {
in, out := &in.Foo, &out.Foo
*out = new(string)
if in.Placement != nil {
in, out := &in.Placement, &out.Placement
*out = new(PlacementSpec)
**out = **in
}
if in.CloudInit != nil {
in, out := &in.CloudInit, &out.CloudInit
*out = new(CloudInitSpec)
(*in).DeepCopyInto(*out)
}
if in.Endpoint != nil {
in, out := &in.Endpoint, &out.Endpoint
*out = new(EndpointSpec)
**out = **in
}
if in.Attributes != nil {
in, out := &in.Attributes, &out.Attributes
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.HealthCheck != nil {
in, out := &in.HealthCheck, &out.HealthCheck
*out = new(HealthCheckSpec)
(*in).DeepCopyInto(*out)
}
if in.MaxLeases != nil {
in, out := &in.MaxLeases, &out.MaxLeases
*out = new(int32)
**out = **in
}
}
@@ -114,6 +211,10 @@ func (in *ProxyStatus) DeepCopyInto(out *ProxyStatus) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.LastHealthCheckTime != nil {
in, out := &in.LastHealthCheckTime, &out.LastHealthCheckTime
*out = (*in).DeepCopy()
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyStatus.
@@ -125,3 +226,18 @@ func (in *ProxyStatus) DeepCopy() *ProxyStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
if in == nil {
return nil
}
out := new(SecretKeySelector)
in.DeepCopyInto(out)
return out
}