diff --git a/api/common/v1alpha1/adopt_types.go b/api/common/v1alpha1/adopt_types.go new file mode 100644 index 00000000..4788da4d --- /dev/null +++ b/api/common/v1alpha1/adopt_types.go @@ -0,0 +1,36 @@ +package v1alpha1 + +// AdoptOptions is the options for CRDs to attach to an existing Kong entity. +// +kubebuilder:object:generate=true +// +kubebuilder:validation:XValidation:rule="self.from == oldSelf.from",message="'from'(adopt source) is immutable" +// +kubebuilder:validation:XValidation:rule="self.from == 'konnect' ? has(self.konnect) : true",message="Must specify Konnect options when from='konnect'" +// +kubebuilder:validation:XValidation:rule="has(self.konnect) ? (self.konnect.id == oldSelf.konnect.id) : true",message="konnect.id is immutable" +// +apireference:kgo:include +type AdoptOptions struct { + // From is the source of the entity to adopt from. + // Now 'konnect' is supported. + // +required + // +kubebuilder:validation:Enum=konnect + From AdoptSource `json:"from"` + // Konnect is the options for adopting the entity from Konnect. + // Required when from == 'konnect'. + // +optional + Konnect *AdoptKonnectOptions `json:"konnect,omitempty"` +} + +// AdoptSource is the type to define the source of the entity to adopt from. +type AdoptSource string + +const ( + // AdoptSourceKonnect indicates that the entity is adopted from Konnect. + AdoptSourceKonnect AdoptSource = "konnect" +) + +// AdoptKonnectOptions specifies the options for adopting the entity from Konnect. +// +kubebuilder:object:generate=true +// +apireference:kgo:include +type AdoptKonnectOptions struct { + // ID is the Konnect ID of the entity. + // +required + ID string `json:"id"` +} diff --git a/api/common/v1alpha1/zz_generated.deepcopy.go b/api/common/v1alpha1/zz_generated.deepcopy.go index dbf6702c..9e221d47 100644 --- a/api/common/v1alpha1/zz_generated.deepcopy.go +++ b/api/common/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,41 @@ package v1alpha1 import () +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdoptKonnectOptions) DeepCopyInto(out *AdoptKonnectOptions) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdoptKonnectOptions. +func (in *AdoptKonnectOptions) DeepCopy() *AdoptKonnectOptions { + if in == nil { + return nil + } + out := new(AdoptKonnectOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdoptOptions) DeepCopyInto(out *AdoptOptions) { + *out = *in + if in.Konnect != nil { + in, out := &in.Konnect, &out.Konnect + *out = new(AdoptKonnectOptions) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdoptOptions. +func (in *AdoptOptions) DeepCopy() *AdoptOptions { + if in == nil { + return nil + } + out := new(AdoptOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControlPlaneRef) DeepCopyInto(out *ControlPlaneRef) { *out = *in diff --git a/api/configuration/v1alpha1/kongservice_types.go b/api/configuration/v1alpha1/kongservice_types.go index 8e1d35e5..118e2916 100644 --- a/api/configuration/v1alpha1/kongservice_types.go +++ b/api/configuration/v1alpha1/kongservice_types.go @@ -54,6 +54,8 @@ type KongService struct { // KongServiceSpec defines specification of a Kong Service. // +kubebuilder:validation:XValidation:rule="!has(self.controlPlaneRef) ? true : self.controlPlaneRef.type != 'kic'", message="KIC is not supported as control plane" +// +kubebuilder:validation:XValidation:rule="!has(self.adopt) ? true : (has(self.controlPlaneRef) && self.controlPlaneRef.type == 'konnectNamespacedRef')", message="spec.adopt is allowed only when controlPlaneRef is konnectNamespacedRef" +// +kubebuilder:validation:XValidation:rule="(has(oldSelf.adopt) && has(self.adopt)) || (!has(oldSelf.adopt) && !has(self.adopt))", message="Cannot set or unset spec.adopt in updates" // +apireference:kgo:include type KongServiceSpec struct { // ControlPlaneRef is a reference to a ControlPlane this KongService is associated with. @@ -61,6 +63,10 @@ type KongServiceSpec struct { // +required ControlPlaneRef *commonv1alpha1.ControlPlaneRef `json:"controlPlaneRef"` + // Adopt is the options for adopting a service from an existing service in Konnect. + // +optional + Adopt *commonv1alpha1.AdoptOptions `json:"adopt,omitempty"` + KongServiceAPISpec `json:",inline"` } diff --git a/api/configuration/v1alpha1/zz_generated.deepcopy.go b/api/configuration/v1alpha1/zz_generated.deepcopy.go index bd6a9b4e..b0465136 100644 --- a/api/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/api/configuration/v1alpha1/zz_generated.deepcopy.go @@ -2320,6 +2320,11 @@ func (in *KongServiceSpec) DeepCopyInto(out *KongServiceSpec) { *out = new(commonv1alpha1.ControlPlaneRef) (*in).DeepCopyInto(*out) } + if in.Adopt != nil { + in, out := &in.Adopt, &out.Adopt + *out = new(commonv1alpha1.AdoptOptions) + (*in).DeepCopyInto(*out) + } in.KongServiceAPISpec.DeepCopyInto(&out.KongServiceAPISpec) } diff --git a/config/crd/gateway-operator/configuration.konghq.com_kongservices.yaml b/config/crd/gateway-operator/configuration.konghq.com_kongservices.yaml index 458309d2..78b7d89e 100644 --- a/config/crd/gateway-operator/configuration.konghq.com_kongservices.yaml +++ b/config/crd/gateway-operator/configuration.konghq.com_kongservices.yaml @@ -56,6 +56,39 @@ spec: spec: description: KongServiceSpec defines specification of a Kong Service. properties: + adopt: + description: Adopt is the options for adopting a service from an existing + service in Konnect. + properties: + from: + description: |- + From is the source of the entity to adopt from. + Now 'konnect' is supported. + enum: + - konnect + type: string + konnect: + description: |- + Konnect is the options for adopting the entity from Konnect. + Required when from == 'konnect'. + properties: + id: + description: ID is the Konnect ID of the entity. + type: string + required: + - id + type: object + required: + - from + type: object + x-kubernetes-validations: + - message: '''from''(adopt source) is immutable' + rule: self.from == oldSelf.from + - message: Must specify Konnect options when from='konnect' + rule: 'self.from == ''konnect'' ? has(self.konnect) : true' + - message: konnect.id is immutable + rule: 'has(self.konnect) ? (self.konnect.id == oldSelf.konnect.id) + : true' connect_timeout: description: The timeout in milliseconds for establishing a connection to the upstream server. @@ -198,6 +231,12 @@ spec: - message: KIC is not supported as control plane rule: '!has(self.controlPlaneRef) ? true : self.controlPlaneRef.type != ''kic''' + - message: spec.adopt is allowed only when controlPlaneRef is konnectNamespacedRef + rule: '!has(self.adopt) ? true : (has(self.controlPlaneRef) && self.controlPlaneRef.type + == ''konnectNamespacedRef'')' + - message: Cannot set or unset spec.adopt in updates + rule: (has(oldSelf.adopt) && has(self.adopt)) || (!has(oldSelf.adopt) + && !has(self.adopt)) status: default: conditions: diff --git a/config/samples/konnect_kongservice_adopt.yaml b/config/samples/konnect_kongservice_adopt.yaml new file mode 100644 index 00000000..65ab342c --- /dev/null +++ b/config/samples/konnect_kongservice_adopt.yaml @@ -0,0 +1,40 @@ +kind: KonnectAPIAuthConfiguration +apiVersion: konnect.konghq.com/v1alpha1 +metadata: + name: konnect-api-auth-1 + namespace: default +spec: + type: token + token: kpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + serverURL: eu.api.konghq.com +--- +kind: KonnectGatewayControlPlane +apiVersion: konnect.konghq.com/v1alpha1 +metadata: + name: test1 + namespace: default +spec: + name: test1 + labels: + app: test1 + key1: test1 + konnect: + authRef: + name: konnect-api-auth-1 +--- +kind: KongService +apiVersion: configuration.konghq.com/v1alpha1 +metadata: + name: service-adopted-1 + namespace: default +spec: + adopt: + from: konnect + konnect: + id: "aaaabbbb-cccc-dddd-eeee-000011112222" + name: service-1 + host: example.com + controlPlaneRef: + type: konnectNamespacedRef + konnectNamespacedRef: + name: test1 diff --git a/docs/all-api-reference.md b/docs/all-api-reference.md index b00d5177..63285b20 100644 --- a/docs/all-api-reference.md +++ b/docs/all-api-reference.md @@ -1284,6 +1284,7 @@ KongServiceSpec defines specification of a Kong Service. | Field | Description | | --- | --- | | `controlPlaneRef` _[ControlPlaneRef](#controlplaneref)_ | ControlPlaneRef is a reference to a ControlPlane this KongService is associated with. | +| `adopt` _[AdoptOptions](#adoptoptions)_ | Adopt is the options for adopting a service from an existing service in Konnect. | | `url` _string_ | Helper field to set `protocol`, `host`, `port` and `path` using a URL. This field is write-only and is not returned in responses. | | `connect_timeout` _integer_ | The timeout in milliseconds for establishing a connection to the upstream server. | | `enabled` _boolean_ | Whether the Service is active. If set to `false`, the proxy behavior will be as if any routes attached to it do not exist (404). Default: `true`. | diff --git a/docs/configuration-api-reference.md b/docs/configuration-api-reference.md index b149f407..7e3473e2 100644 --- a/docs/configuration-api-reference.md +++ b/docs/configuration-api-reference.md @@ -1278,6 +1278,7 @@ KongServiceSpec defines specification of a Kong Service. | Field | Description | | --- | --- | | `controlPlaneRef` _[ControlPlaneRef](#controlplaneref)_ | ControlPlaneRef is a reference to a ControlPlane this KongService is associated with. | +| `adopt` _[AdoptOptions](#adoptoptions)_ | Adopt is the options for adopting a service from an existing service in Konnect. | | `url` _string_ | Helper field to set `protocol`, `host`, `port` and `path` using a URL. This field is write-only and is not returned in responses. | | `connect_timeout` _integer_ | The timeout in milliseconds for establishing a connection to the upstream server. | | `enabled` _boolean_ | Whether the Service is active. If set to `false`, the proxy behavior will be as if any routes attached to it do not exist (404). Default: `true`. | diff --git a/test/crdsvalidation/configuration.konghq.com/kongservice_test.go b/test/crdsvalidation/configuration.konghq.com/kongservice_test.go index 39ae78cd..c67b839b 100644 --- a/test/crdsvalidation/configuration.konghq.com/kongservice_test.go +++ b/test/crdsvalidation/configuration.konghq.com/kongservice_test.go @@ -100,4 +100,116 @@ func TestKongService(t *testing.T) { }, }.Run(t) }) + + t.Run("adopt options testing", func(t *testing.T) { + common.TestCasesGroup[*configurationv1alpha1.KongService]{ + { + Name: "valid adopt options", + TestObject: &configurationv1alpha1.KongService{ + ObjectMeta: common.CommonObjectMeta, + Spec: configurationv1alpha1.KongServiceSpec{ + ControlPlaneRef: &commonv1alpha1.ControlPlaneRef{ + Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, + KonnectNamespacedRef: &commonv1alpha1.KonnectNamespacedRef{ + Name: "test-konnect-control-plane", + }, + }, + Adopt: &commonv1alpha1.AdoptOptions{ + From: commonv1alpha1.AdoptSourceKonnect, + Konnect: &commonv1alpha1.AdoptKonnectOptions{ + ID: "test-konnect-id", + }, + }, + }, + }, + }, + { + Name: "invalid adopt.from", + TestObject: &configurationv1alpha1.KongService{ + ObjectMeta: common.CommonObjectMeta, + Spec: configurationv1alpha1.KongServiceSpec{ + ControlPlaneRef: &commonv1alpha1.ControlPlaneRef{ + Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, + KonnectNamespacedRef: &commonv1alpha1.KonnectNamespacedRef{ + Name: "test-konnect-control-plane", + }, + }, + Adopt: &commonv1alpha1.AdoptOptions{ + From: "invalid-adopt-from", + Konnect: &commonv1alpha1.AdoptKonnectOptions{ + ID: "test-konnect-id", + }, + }, + }, + }, + ExpectedErrorMessage: lo.ToPtr("spec.adopt.from: Unsupported value"), + }, + { + Name: "missing adopt.konnect", + TestObject: &configurationv1alpha1.KongService{ + ObjectMeta: common.CommonObjectMeta, + Spec: configurationv1alpha1.KongServiceSpec{ + ControlPlaneRef: &commonv1alpha1.ControlPlaneRef{ + Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, + KonnectNamespacedRef: &commonv1alpha1.KonnectNamespacedRef{ + Name: "test-konnect-control-plane", + }, + }, + Adopt: &commonv1alpha1.AdoptOptions{ + From: commonv1alpha1.AdoptSourceKonnect, + }, + }, + }, + ExpectedErrorMessage: lo.ToPtr("Must specify Konnect options when from='konnect'"), + }, + { + Name: "adopt.konnect.id is immutable", + TestObject: &configurationv1alpha1.KongService{ + ObjectMeta: common.CommonObjectMeta, + Spec: configurationv1alpha1.KongServiceSpec{ + ControlPlaneRef: &commonv1alpha1.ControlPlaneRef{ + Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, + KonnectNamespacedRef: &commonv1alpha1.KonnectNamespacedRef{ + Name: "test-konnect-control-plane", + }, + }, + Adopt: &commonv1alpha1.AdoptOptions{ + From: commonv1alpha1.AdoptSourceKonnect, + Konnect: &commonv1alpha1.AdoptKonnectOptions{ + ID: "test-konnect-id", + }, + }, + }, + }, + Update: func(ks *configurationv1alpha1.KongService) { + ks.Spec.Adopt.Konnect.ID = "test-konnect-id-2" + }, + ExpectedUpdateErrorMessage: lo.ToPtr("konnect.id is immutable"), + }, + { + Name: "Cannot unset adopt", + TestObject: &configurationv1alpha1.KongService{ + ObjectMeta: common.CommonObjectMeta, + Spec: configurationv1alpha1.KongServiceSpec{ + ControlPlaneRef: &commonv1alpha1.ControlPlaneRef{ + Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, + KonnectNamespacedRef: &commonv1alpha1.KonnectNamespacedRef{ + Name: "test-konnect-control-plane", + }, + }, + Adopt: &commonv1alpha1.AdoptOptions{ + From: commonv1alpha1.AdoptSourceKonnect, + Konnect: &commonv1alpha1.AdoptKonnectOptions{ + ID: "test-konnect-id", + }, + }, + }, + }, + Update: func(ks *configurationv1alpha1.KongService) { + ks.Spec.Adopt = nil + }, + ExpectedUpdateErrorMessage: lo.ToPtr("Cannot set or unset spec.adopt in updates"), + }, + }.Run(t) + }) }