diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 8f9e0220d..446871873 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -38,7 +38,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif" + enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif,s-proxy,s-account,s-container,s-object" conf_overrides: | enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }} enable_plugin manila https://github.com/openstack/manila ${{ matrix.openstack_version }} diff --git a/.gitignore b/.gitignore index e859277d7..545415b62 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ bin/* Dockerfile.cross +# Root-level compiled binaries +/manager +/resource-generator + # Test binary, built with `go test -c` *.test diff --git a/PROJECT b/PROJECT index e5a188a8b..d02355f97 100644 --- a/PROJECT +++ b/PROJECT @@ -176,6 +176,14 @@ resources: kind: Subnet path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: SwiftContainer + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/README.md b/README.md index a5663e5fd..f0c4e86f0 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ kubectl delete -f $ORC_RELEASE | server group | | ✔ | ✔ | | service | | ✔ | ✔ | | share network | | | ◐ | +| swift container | | | ◐ | | subnet | | ◐ | ◐ | | trunk | | ✔ | ✔ | | user | | ◐ | ◐ | diff --git a/api/v1alpha1/swiftcontainer_types.go b/api/v1alpha1/swiftcontainer_types.go new file mode 100644 index 000000000..fc6648bc9 --- /dev/null +++ b/api/v1alpha1/swiftcontainer_types.go @@ -0,0 +1,181 @@ +/* +Copyright The ORC Authors. + +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 + +// SwiftContainerName is the name of a Swift container. It must be between 1 +// and 256 characters long and must not contain forward slashes. +// +kubebuilder:validation:MinLength:=1 +// +kubebuilder:validation:MaxLength:=256 +// +kubebuilder:validation:Pattern:=`^[^/]+$` +// +kubebuilder:validation:XValidation:rule="self.size() <= 256",message="name must not exceed 256 UTF-8 bytes" +type SwiftContainerName string + +// SwiftContainerMetadata defines a key-value pair to be set as a Swift +// container metadata header (X-Container-Meta-: ). +type SwiftContainerMetadata struct { + // key is the key of the metadata item. It will be used as the suffix of + // the X-Container-Meta-* header. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +required + Key string `json:"key,omitempty"` + + // value is the value of the metadata item. + // +kubebuilder:validation:MaxLength:=255 + // +required + Value string `json:"value"` +} + +// SwiftContainerMetadataStatus represents an observed metadata key-value pair +// on a Swift container. +type SwiftContainerMetadataStatus struct { + // key is the key of the metadata item. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +required + Key string `json:"key,omitempty"` + + // value is the value of the metadata item. + // +kubebuilder:validation:MaxLength:=255 + // +optional + Value string `json:"value,omitempty"` +} + +// SwiftContainerFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type SwiftContainerFilter struct { + // name of the existing resource + // +optional + Name *SwiftContainerName `json:"name,omitempty"` + + // prefix filters containers by name prefix. Only containers whose names + // begin with this prefix will be considered. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=256 + // +optional + Prefix *string `json:"prefix,omitempty"` +} + +// SwiftContainerImport specifies an existing resource which will be imported +// instead of creating a new one. Swift containers are identified by name +// rather than UUID. +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type SwiftContainerImport struct { + // name contains the name of an existing Swift container to import. Note + // that when specifying an import by name, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +optional + Name *SwiftContainerName `json:"name,omitempty"` + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *SwiftContainerFilter `json:"filter,omitempty"` +} + +// SwiftContainerResourceSpec contains the desired state of a Swift container. +type SwiftContainerResourceSpec struct { + // name will be the name of the created Swift container. If not specified, + // the name of the ORC object will be used. The name must be unique within + // the account and must not contain forward slashes. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" + Name *SwiftContainerName `json:"name,omitempty"` + + // metadata is a list of key-value pairs which will be set as + // X-Container-Meta-* headers on the Swift container. + // +kubebuilder:validation:MaxItems:=64 + // +listType=map + // +listMapKey=key + // +optional + Metadata []SwiftContainerMetadata `json:"metadata,omitempty"` + + // containerRead sets the X-Container-Read ACL header which defines who + // can read objects in the container. Common values include ".r:*" for + // public read access or a comma-separated list of account/container + // combinations. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=256 + // +optional + ContainerRead string `json:"containerRead,omitempty"` + + // containerWrite sets the X-Container-Write ACL header which defines who + // can write objects to the container. Common values include a + // comma-separated list of account/container combinations. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=256 + // +optional + ContainerWrite string `json:"containerWrite,omitempty"` + + // storagePolicy is the name of the storage policy to use for this + // container. If not specified, the cluster's default storage policy will + // be used. This field is immutable after creation. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="storagePolicy is immutable" + StoragePolicy string `json:"storagePolicy,omitempty"` +} + +// SwiftContainerResourceStatus represents the observed state of a Swift container. +type SwiftContainerResourceStatus struct { + // name is the name of the Swift container. + // +kubebuilder:validation:MaxLength=256 + // +optional + Name string `json:"name,omitempty"` + + // bytesUsed is the total number of bytes stored in the container. + // +optional + BytesUsed int64 `json:"bytesUsed,omitempty"` + + // objectCount is the number of objects stored in the container. + // +optional + ObjectCount int64 `json:"objectCount,omitempty"` + + // metadata is the list of observed metadata key-value pairs on the container. + // +kubebuilder:validation:MaxItems:=64 + // +listType=map + // +listMapKey=key + // +optional + Metadata []SwiftContainerMetadataStatus `json:"metadata,omitempty"` + + // containerRead is the current X-Container-Read ACL, defining who can + // read objects in the container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ContainerRead string `json:"containerRead,omitempty"` + + // containerWrite is the current X-Container-Write ACL, defining who can + // write objects to the container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ContainerWrite string `json:"containerWrite,omitempty"` + + // storagePolicy is the name of the storage policy assigned to the container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + StoragePolicy string `json:"storagePolicy,omitempty"` + + // versions is the container where object versions are stored, if versioning + // is enabled on this container. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Versions string `json:"versions,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ead3ef708..3c399bee7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6183,6 +6183,253 @@ func (in *SubnetStatus) DeepCopy() *SubnetStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainer) DeepCopyInto(out *SwiftContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainer. +func (in *SwiftContainer) DeepCopy() *SwiftContainer { + if in == nil { + return nil + } + out := new(SwiftContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SwiftContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerFilter) DeepCopyInto(out *SwiftContainerFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(SwiftContainerName) + **out = **in + } + if in.Prefix != nil { + in, out := &in.Prefix, &out.Prefix + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerFilter. +func (in *SwiftContainerFilter) DeepCopy() *SwiftContainerFilter { + if in == nil { + return nil + } + out := new(SwiftContainerFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerImport) DeepCopyInto(out *SwiftContainerImport) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(SwiftContainerName) + **out = **in + } + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(SwiftContainerFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerImport. +func (in *SwiftContainerImport) DeepCopy() *SwiftContainerImport { + if in == nil { + return nil + } + out := new(SwiftContainerImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerList) DeepCopyInto(out *SwiftContainerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SwiftContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerList. +func (in *SwiftContainerList) DeepCopy() *SwiftContainerList { + if in == nil { + return nil + } + out := new(SwiftContainerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SwiftContainerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerMetadata) DeepCopyInto(out *SwiftContainerMetadata) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerMetadata. +func (in *SwiftContainerMetadata) DeepCopy() *SwiftContainerMetadata { + if in == nil { + return nil + } + out := new(SwiftContainerMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerMetadataStatus) DeepCopyInto(out *SwiftContainerMetadataStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerMetadataStatus. +func (in *SwiftContainerMetadataStatus) DeepCopy() *SwiftContainerMetadataStatus { + if in == nil { + return nil + } + out := new(SwiftContainerMetadataStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerResourceSpec) DeepCopyInto(out *SwiftContainerResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(SwiftContainerName) + **out = **in + } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make([]SwiftContainerMetadata, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerResourceSpec. +func (in *SwiftContainerResourceSpec) DeepCopy() *SwiftContainerResourceSpec { + if in == nil { + return nil + } + out := new(SwiftContainerResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerResourceStatus) DeepCopyInto(out *SwiftContainerResourceStatus) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make([]SwiftContainerMetadataStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerResourceStatus. +func (in *SwiftContainerResourceStatus) DeepCopy() *SwiftContainerResourceStatus { + if in == nil { + return nil + } + out := new(SwiftContainerResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerSpec) DeepCopyInto(out *SwiftContainerSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(SwiftContainerImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(SwiftContainerResourceSpec) + (*in).DeepCopyInto(*out) + } + if in.ManagedOptions != nil { + in, out := &in.ManagedOptions, &out.ManagedOptions + *out = new(ManagedOptions) + **out = **in + } + out.CloudCredentialsRef = in.CloudCredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerSpec. +func (in *SwiftContainerSpec) DeepCopy() *SwiftContainerSpec { + if in == nil { + return nil + } + out := new(SwiftContainerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SwiftContainerStatus) DeepCopyInto(out *SwiftContainerStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(SwiftContainerResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SwiftContainerStatus. +func (in *SwiftContainerStatus) DeepCopy() *SwiftContainerStatus { + if in == nil { + return nil + } + out := new(SwiftContainerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Trunk) DeepCopyInto(out *Trunk) { *out = *in diff --git a/api/v1alpha1/zz_generated.swiftcontainer-resource.go b/api/v1alpha1/zz_generated.swiftcontainer-resource.go new file mode 100644 index 000000000..87787b96e --- /dev/null +++ b/api/v1alpha1/zz_generated.swiftcontainer-resource.go @@ -0,0 +1,158 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SwiftContainerSpec defines the desired state of an ORC object. +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? !has(self.resource) : true",message="resource may not be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? has(self.__import__) : true",message="import must be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="has(self.managedOptions) ? self.managementPolicy == 'managed' : true",message="managedOptions may only be provided when policy is managed" +type SwiftContainerSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *SwiftContainerImport `json:"import,omitempty"` + + // resource specifies the desired state of the resource. + // + // resource may not be specified if the management policy is `unmanaged`. + // + // resource must be specified if the management policy is `managed`. + // +optional + Resource *SwiftContainerResourceSpec `json:"resource,omitempty"` + + // managementPolicy defines how ORC will treat the object. Valid values are + // `managed`: ORC will create, update, and delete the resource; `unmanaged`: + // ORC will import an existing resource, and will not apply updates to it or + // delete it. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable" + // +kubebuilder:default:=managed + // +optional + ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"` + + // managedOptions specifies options which may be applied to managed objects. + // +optional + ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + + // cloudCredentialsRef points to a secret containing OpenStack credentials + // +required + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` +} + +// SwiftContainerStatus defines the observed state of an ORC resource. +type SwiftContainerStatus struct { + // conditions represents the observed status of the object. + // Known .status.conditions.type are: "Available", "Progressing" + // + // Available represents the availability of the OpenStack resource. If it is + // true then the resource is ready for use. + // + // Progressing indicates whether the controller is still attempting to + // reconcile the current state of the OpenStack resource to the desired + // state. Progressing will be False either because the desired state has + // been achieved, or because some terminal error prevents it from ever being + // achieved and the controller is no longer attempting to reconcile. If + // Progressing is True, an observer waiting on the resource should continue + // to wait. + // + // +kubebuilder:validation:MaxItems:=32 + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // id is the unique identifier of the OpenStack resource. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *SwiftContainerResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &SwiftContainer{} + +func (i *SwiftContainer) GetConditions() []metav1.Condition { + return i.Status.Conditions +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=openstack +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID" +// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status" + +// SwiftContainer is the Schema for an ORC resource. +type SwiftContainer struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +required + Spec SwiftContainerSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status SwiftContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// SwiftContainerList contains a list of SwiftContainer. +type SwiftContainerList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of SwiftContainer. + // +required + Items []SwiftContainer `json:"items"` +} + +func (l *SwiftContainerList) GetItems() []SwiftContainer { + return l.Items +} + +func init() { + SchemeBuilder.Register(&SwiftContainer{}, &SwiftContainerList{}) +} + +func (i *SwiftContainer) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &SwiftContainer{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 9addc552d..3ffd04e41 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -49,6 +49,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/service" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/sharenetwork" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnet" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/swiftcontainer" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/trunk" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/user" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volume" @@ -138,6 +139,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), + swiftcontainer.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 5601a14eb..af82c9031 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -240,6 +240,16 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainer": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainer(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerImport": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerList": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerMetadataStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Trunk": schema_openstack_resource_controller_v2_api_v1alpha1_Trunk(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkImport": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkImport(ref), @@ -11743,6 +11753,471 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainer is the Schema for an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the object metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec specifies the desired state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status defines the observed state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "prefix": { + SchemaProps: spec.SchemaProps{ + Description: "prefix filters containers by name prefix. Only containers whose names begin with this prefix will be considered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerImport specifies an existing resource which will be imported instead of creating a new one. Swift containers are identified by name rather than UUID.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name contains the name of an existing Swift container to import. Note that when specifying an import by name, the resource MUST already exist. The ORC object will enter an error state if the resource does not exist.", + Type: []string{"string"}, + Format: "", + }, + }, + "filter": { + SchemaProps: spec.SchemaProps{ + Description: "filter contains a resource query which is expected to return a single result. The controller will continue to retry if filter returns no results. If filter returns multiple results the controller will set an error state and will not continue to retry.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerList contains a list of SwiftContainer.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the list metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items contains a list of SwiftContainer.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainer"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainer", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerMetadata defines a key-value pair to be set as a Swift container metadata header (X-Container-Meta-: ).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the key of the metadata item. It will be used as the suffix of the X-Container-Meta-* header.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value of the metadata item.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerMetadataStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerMetadataStatus represents an observed metadata key-value pair on a Swift container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the key of the metadata item.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value of the metadata item.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerResourceSpec contains the desired state of a Swift container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name will be the name of the created Swift container. If not specified, the name of the ORC object will be used. The name must be unique within the account and must not contain forward slashes.", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "key", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "metadata is a list of key-value pairs which will be set as X-Container-Meta-* headers on the Swift container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadata"), + }, + }, + }, + }, + }, + "containerRead": { + SchemaProps: spec.SchemaProps{ + Description: "containerRead sets the X-Container-Read ACL header which defines who can read objects in the container. Common values include \".r:*\" for public read access or a comma-separated list of account/container combinations.", + Type: []string{"string"}, + Format: "", + }, + }, + "containerWrite": { + SchemaProps: spec.SchemaProps{ + Description: "containerWrite sets the X-Container-Write ACL header which defines who can write objects to the container. Common values include a comma-separated list of account/container combinations.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "storagePolicy is the name of the storage policy to use for this container. If not specified, the cluster's default storage policy will be used. This field is immutable after creation.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadata"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerResourceStatus represents the observed state of a Swift container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the Swift container.", + Type: []string{"string"}, + Format: "", + }, + }, + "bytesUsed": { + SchemaProps: spec.SchemaProps{ + Description: "bytesUsed is the total number of bytes stored in the container.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "objectCount": { + SchemaProps: spec.SchemaProps{ + Description: "objectCount is the number of objects stored in the container.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "metadata": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "key", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "metadata is the list of observed metadata key-value pairs on the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadataStatus"), + }, + }, + }, + }, + }, + "containerRead": { + SchemaProps: spec.SchemaProps{ + Description: "containerRead is the current X-Container-Read ACL, defining who can read objects in the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "containerWrite": { + SchemaProps: spec.SchemaProps{ + Description: "containerWrite is the current X-Container-Write ACL, defining who can write objects to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "storagePolicy is the name of the storage policy assigned to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + SchemaProps: spec.SchemaProps{ + Description: "versions is the container where object versions are stored, if versioning is enabled on this container.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerMetadataStatus"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerSpec defines the desired state of an ORC object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "import": { + SchemaProps: spec.SchemaProps{ + Description: "import refers to an existing OpenStack resource which will be imported instead of creating a new one.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerImport"), + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource specifies the desired state of the resource.\n\nresource may not be specified if the management policy is `unmanaged`.\n\nresource must be specified if the management policy is `managed`.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceSpec"), + }, + }, + "managementPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.", + Type: []string{"string"}, + Format: "", + }, + }, + "managedOptions": { + SchemaProps: spec.SchemaProps{ + Description: "managedOptions specifies options which may be applied to managed objects.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), + }, + }, + "cloudCredentialsRef": { + SchemaProps: spec.SchemaProps{ + Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"), + }, + }, + }, + Required: []string{"cloudCredentialsRef"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SwiftContainerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SwiftContainerStatus defines the observed state of an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions represents the observed status of the object. Known .status.conditions.type are: \"Available\", \"Progressing\"\n\nAvailable represents the availability of the OpenStack resource. If it is true then the resource is ready for use.\n\nProgressing indicates whether the controller is still attempting to reconcile the current state of the OpenStack resource to the desired state. Progressing will be False either because the desired state has been achieved, or because some terminal error prevents it from ever being achieved and the controller is no longer attempting to reconcile. If Progressing is True, an observer waiting on the resource should continue to wait.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the unique identifier of the OpenStack resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource contains the observed state of the OpenStack resource.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SwiftContainerResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Trunk(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/resource-generator/data/adapter.template b/cmd/resource-generator/data/adapter.template index 7bec457ee..62dafde50 100644 --- a/cmd/resource-generator/data/adapter.template +++ b/cmd/resource-generator/data/adapter.template @@ -66,7 +66,15 @@ func (f adapterT) GetImportID() *string { if f.Spec.Import == nil { return nil } +{{- if .HasCustomImport }} + if f.Spec.Import.Name == nil { + return nil + } + name := string(*f.Spec.Import.Name) + return &name +{{- else }} return f.Spec.Import.ID +{{- end }} } func (f adapterT) GetImportFilter() *filterT { diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 9abb00d7a..0e6435230 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -19,7 +19,7 @@ package {{ .APIVersion }} import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) - +{{ if not .HasCustomImport }} // {{ .Name }}Import specifies an existing resource which will be imported instead of // creating a new one // +kubebuilder:validation:MinProperties:=1 @@ -50,7 +50,7 @@ type {{ .Name }}Import struct { // +optional Filter *{{ .Name }}Filter `json:"filter,omitempty"` } - +{{ end }} // {{ .Name }}Spec defines the desired state of an ORC object. // +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed" // +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed" diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 609bf9084..ffc2bdceb 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -67,6 +67,10 @@ type templateFields struct { // When true, the UUID validation will be omitted from the Import.ID field. // Default is false (uses UUID). UsesNameAsID bool + // HasCustomImport indicates that the resource defines its own Import type in the + // non-generated *_types.go file. When true, the Import type will not be generated. + // Default is false (Import type is generated). + HasCustomImport bool } var resources []templateFields = []templateFields{ @@ -180,6 +184,10 @@ var resources []templateFields = []templateFields{ { Name: "ApplicationCredential", }, + { + Name: "SwiftContainer", + HasCustomImport: true, // SwiftContainerImport is defined in swiftcontainer_types.go + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_swiftcontainers.yaml b/config/crd/bases/openstack.k-orc.cloud_swiftcontainers.yaml new file mode 100644 index 000000000..431dab334 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_swiftcontainers.yaml @@ -0,0 +1,407 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: swiftcontainers.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: SwiftContainer + listKind: SwiftContainerList + plural: swiftcontainers + singular: swiftcontainer + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Resource ID + jsonPath: .status.id + name: ID + type: string + - description: Availability status of resource + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Message describing current progress status + jsonPath: .status.conditions[?(@.type=='Progressing')].message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: SwiftContainer is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + name: + description: name of the existing resource + maxLength: 256 + minLength: 1 + pattern: ^[^/]+$ + type: string + x-kubernetes-validations: + - message: name must not exceed 256 UTF-8 bytes + rule: self.size() <= 256 + prefix: + description: |- + prefix filters containers by name prefix. Only containers whose names + begin with this prefix will be considered. + maxLength: 256 + minLength: 1 + type: string + type: object + name: + description: |- + name contains the name of an existing Swift container to import. Note + that when specifying an import by name, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + maxLength: 256 + minLength: 1 + pattern: ^[^/]+$ + type: string + x-kubernetes-validations: + - message: name must not exceed 256 UTF-8 bytes + rule: self.size() <= 256 + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + containerRead: + description: |- + containerRead sets the X-Container-Read ACL header which defines who + can read objects in the container. Common values include ".r:*" for + public read access or a comma-separated list of account/container + combinations. + maxLength: 256 + minLength: 1 + type: string + containerWrite: + description: |- + containerWrite sets the X-Container-Write ACL header which defines who + can write objects to the container. Common values include a + comma-separated list of account/container combinations. + maxLength: 256 + minLength: 1 + type: string + metadata: + description: |- + metadata is a list of key-value pairs which will be set as + X-Container-Meta-* headers on the Swift container. + items: + description: |- + SwiftContainerMetadata defines a key-value pair to be set as a Swift + container metadata header (X-Container-Meta-: ). + properties: + key: + description: |- + key is the key of the metadata item. It will be used as the suffix of + the X-Container-Meta-* header. + maxLength: 255 + minLength: 1 + type: string + value: + description: value is the value of the metadata item. + maxLength: 255 + type: string + required: + - key + - value + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + name: + description: |- + name will be the name of the created Swift container. If not specified, + the name of the ORC object will be used. The name must be unique within + the account and must not contain forward slashes. + maxLength: 256 + minLength: 1 + pattern: ^[^/]+$ + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must not exceed 256 UTF-8 bytes + rule: self.size() <= 256 + storagePolicy: + description: |- + storagePolicy is the name of the storage policy to use for this + container. If not specified, the cluster's default storage policy will + be used. This field is immutable after creation. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: storagePolicy is immutable + rule: self == oldSelf + type: object + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + maxLength: 1024 + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + bytesUsed: + description: bytesUsed is the total number of bytes stored in + the container. + format: int64 + type: integer + containerRead: + description: |- + containerRead is the current X-Container-Read ACL, defining who can + read objects in the container. + maxLength: 1024 + type: string + containerWrite: + description: |- + containerWrite is the current X-Container-Write ACL, defining who can + write objects to the container. + maxLength: 1024 + type: string + metadata: + description: metadata is the list of observed metadata key-value + pairs on the container. + items: + description: |- + SwiftContainerMetadataStatus represents an observed metadata key-value pair + on a Swift container. + properties: + key: + description: key is the key of the metadata item. + maxLength: 255 + minLength: 1 + type: string + value: + description: value is the value of the metadata item. + maxLength: 255 + type: string + required: + - key + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + name: + description: name is the name of the Swift container. + maxLength: 256 + type: string + objectCount: + description: objectCount is the number of objects stored in the + container. + format: int64 + type: integer + storagePolicy: + description: storagePolicy is the name of the storage policy assigned + to the container. + maxLength: 1024 + type: string + versions: + description: |- + versions is the container where object versions are stored, if versioning + is enabled on this container. + maxLength: 1024 + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 26b47f63f..90a1d3793 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -24,6 +24,7 @@ resources: - bases/openstack.k-orc.cloud_services.yaml - bases/openstack.k-orc.cloud_sharenetworks.yaml - bases/openstack.k-orc.cloud_subnets.yaml +- bases/openstack.k-orc.cloud_swiftcontainers.yaml - bases/openstack.k-orc.cloud_trunks.yaml - bases/openstack.k-orc.cloud_users.yaml - bases/openstack.k-orc.cloud_volumes.yaml diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 89421fc32..f34c53716 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -124,6 +124,11 @@ spec: kind: Subnet name: subnets.openstack.k-orc.cloud version: v1alpha1 + - description: SwiftContainer is the Schema for an ORC resource. + displayName: Swift Container + kind: SwiftContainer + name: swiftcontainers.openstack.k-orc.cloud + version: v1alpha1 - description: Trunk is the Schema for an ORC resource. displayName: Trunk kind: Trunk diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 4991cff67..8d59943dd 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -38,6 +38,7 @@ rules: - services - sharenetworks - subnets + - swiftcontainers - trunks - users - volumes @@ -74,6 +75,7 @@ rules: - services/status - sharenetworks/status - subnets/status + - swiftcontainers/status - trunks/status - users/status - volumes/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 8a50ba039..94b9199ee 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -22,6 +22,7 @@ resources: - openstack_v1alpha1_service.yaml - openstack_v1alpha1_sharenetwork.yaml - openstack_v1alpha1_subnet.yaml +- openstack_v1alpha1_swiftcontainer.yaml - openstack_v1alpha1_trunk.yaml - openstack_v1alpha1_user.yaml - openstack_v1alpha1_volume.yaml diff --git a/config/samples/openstack_v1alpha1_swiftcontainer.yaml b/config/samples/openstack_v1alpha1_swiftcontainer.yaml new file mode 100644 index 000000000..94b6fa4a4 --- /dev/null +++ b/config/samples/openstack_v1alpha1_swiftcontainer.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-sample +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: swiftcontainer-sample-name diff --git a/examples/swiftcontainer/basic.yaml b/examples/swiftcontainer/basic.yaml new file mode 100644 index 000000000..5c0541132 --- /dev/null +++ b/examples/swiftcontainer/basic.yaml @@ -0,0 +1,9 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: my-bucket +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + resource: {} diff --git a/examples/swiftcontainer/full.yaml b/examples/swiftcontainer/full.yaml new file mode 100644 index 000000000..2645e8726 --- /dev/null +++ b/examples/swiftcontainer/full.yaml @@ -0,0 +1,24 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: my-bucket-full +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + managementPolicy: managed + resource: + # name sets the Swift container name. Defaults to the ORC object name if not specified. + name: my-custom-bucket + # metadata sets arbitrary key-value pairs as X-Container-Meta-* headers. + metadata: + - key: owner + value: myteam + - key: environment + value: production + # containerRead sets the X-Container-Read ACL. Use ".r:*" for public read access. + containerRead: .r:*,.rlistings + # containerWrite sets the X-Container-Write ACL. + containerWrite: myaccount:myuser + # storagePolicy sets the storage policy for this container. Immutable after creation. + storagePolicy: gold diff --git a/examples/swiftcontainer/import-filter.yaml b/examples/swiftcontainer/import-filter.yaml new file mode 100644 index 000000000..26c1a3e4a --- /dev/null +++ b/examples/swiftcontainer/import-filter.yaml @@ -0,0 +1,15 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: my-filtered-bucket +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + managementPolicy: unmanaged + import: + # filter finds an existing container by matching properties. + # The filter must match exactly one container. + filter: + # prefix filters containers whose names begin with this string. + prefix: my-bucket- diff --git a/examples/swiftcontainer/import.yaml b/examples/swiftcontainer/import.yaml new file mode 100644 index 000000000..c68362cbb --- /dev/null +++ b/examples/swiftcontainer/import.yaml @@ -0,0 +1,12 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: my-existing-bucket +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + managementPolicy: unmanaged + import: + # name specifies the exact name of an existing Swift container to import. + name: my-existing-container diff --git a/internal/controllers/swiftcontainer/actuator.go b/internal/controllers/swiftcontainer/actuator.go new file mode 100644 index 000000000..e302cf77a --- /dev/null +++ b/internal/controllers/swiftcontainer/actuator.go @@ -0,0 +1,406 @@ +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + "context" + "iter" + "strings" + + "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" + corev1 "k8s.io/api/core/v1" + ctrl "sigs.k8s.io/controller-runtime" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + generic "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + osclients "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// osContainerT wraps containers.GetHeader with the container name and +// metadata, since GetHeader does not include the container name and the +// X-Container-Meta-* headers are returned separately via ExtractMetadata. +type osContainerT struct { + Name string + Metadata map[string]string + containers.GetHeader +} + +// OpenStack resource types +type ( + osResourceT = osContainerT + + createResourceActuator = generic.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = generic.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + reconcileResourceActuator = generic.ReconcileResourceActuator[orcObjectPT, osResourceT] + resourceReconciler = generic.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = generic.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) + +type swiftcontainerActuator struct { + osClient osclients.SwiftContainerClient +} + +var _ createResourceActuator = swiftcontainerActuator{} +var _ deleteResourceActuator = swiftcontainerActuator{} +var _ reconcileResourceActuator = swiftcontainerActuator{} + +func (swiftcontainerActuator) GetResourceID(osResource *osContainerT) string { + // Swift containers are identified by name + return osResource.Name +} + +func (actuator swiftcontainerActuator) GetOSResourceByID(ctx context.Context, id string) (*osContainerT, progress.ReconcileStatus) { + header, err := actuator.osClient.GetContainer(ctx, id, nil) + if err != nil { + return nil, progress.WrapError(err) + } + metadata, err := actuator.osClient.GetContainerMetadata(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return &osContainerT{Name: id, Metadata: metadata, GetHeader: *header}, nil +} + +func (actuator swiftcontainerActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osContainerT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + name := getResourceName(orcObject) + return func(yield func(*osContainerT, error) bool) { + header, err := actuator.osClient.GetContainer(ctx, name, nil) + if err != nil { + if !orcerrors.IsNotFound(err) { + yield(nil, err) + } + return + } + metadata, err := actuator.osClient.GetContainerMetadata(ctx, name) + if err != nil { + yield(nil, err) + return + } + yield(&osContainerT{Name: name, Metadata: metadata, GetHeader: *header}, nil) + }, true +} + +func (actuator swiftcontainerActuator) ListOSResourcesForImport(ctx context.Context, _ orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + return func(yield func(*osContainerT, error) bool) { + if filter.Name != nil { + name := string(*filter.Name) + // If a prefix filter is also set, verify the name satisfies it. + // Without this check a filter {name: "x", prefix: "y-"} would + // silently import "x" even though it does not match the prefix. + if filter.Prefix != nil && !hasPrefix(name, *filter.Prefix) { + return + } + header, err := actuator.osClient.GetContainer(ctx, name, nil) + if err != nil { + if !orcerrors.IsNotFound(err) { + yield(nil, err) + } + return + } + metadata, err := actuator.osClient.GetContainerMetadata(ctx, name) + if err != nil { + yield(nil, err) + return + } + yield(&osContainerT{Name: name, Metadata: metadata, GetHeader: *header}, nil) + } else { + // List all containers and filter by prefix + listOpts := containers.ListOpts{} + for container, err := range actuator.osClient.ListContainers(ctx, listOpts) { + if err != nil { + yield(nil, err) + return + } + + if filter.Prefix != nil && !hasPrefix(container.Name, *filter.Prefix) { + continue + } + + header, err := actuator.osClient.GetContainer(ctx, container.Name, nil) + if err != nil { + yield(nil, err) + return + } + metadata, err := actuator.osClient.GetContainerMetadata(ctx, container.Name) + if err != nil { + yield(nil, err) + return + } + if !yield(&osContainerT{Name: container.Name, Metadata: metadata, GetHeader: *header}, nil) { + return + } + } + } + }, nil +} + +// hasPrefix checks if name starts with prefix. +func hasPrefix(name, prefix string) bool { + if len(prefix) > len(name) { + return false + } + return name[:len(prefix)] == prefix +} + +func (actuator swiftcontainerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osContainerT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + + name := getResourceName(obj) + + // Swift treats '/' as a path separator in container names, making the name + // invalid at the API level. Validate explicitly to provide a clear error + // message rather than a confusing HTTP error from gophercloud. + if strings.Contains(name, "/") { + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "container name must not contain forward slashes")) + } + + // Swift limits container names to 256 UTF-8 bytes. The kubebuilder + // MaxLength:=256 marker counts Unicode code points, not bytes, so a name + // with multi-byte UTF-8 characters can pass API validation yet exceed the + // byte limit. Validate explicitly here to produce a clear error message. + if len(name) > 256 { + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "container name must not exceed 256 bytes")) + } + + createOpts := containers.CreateOpts{} + + if resource.ContainerRead != "" { + createOpts.ContainerRead = resource.ContainerRead + } + if resource.ContainerWrite != "" { + createOpts.ContainerWrite = resource.ContainerWrite + } + if resource.StoragePolicy != "" { + createOpts.StoragePolicy = resource.StoragePolicy + } + + if len(resource.Metadata) > 0 { + metadata := make(map[string]string, len(resource.Metadata)) + for _, m := range resource.Metadata { + metadata[m.Key] = m.Value + } + createOpts.Metadata = metadata + } + + _, err := actuator.osClient.CreateContainer(ctx, name, createOpts) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + // Fetch the created container to return its header and metadata + header, err := actuator.osClient.GetContainer(ctx, name, nil) + if err != nil { + return nil, progress.WrapError(err) + } + fetchedMetadata, err := actuator.osClient.GetContainerMetadata(ctx, name) + if err != nil { + return nil, progress.WrapError(err) + } + + return &osContainerT{Name: name, Metadata: fetchedMetadata, GetHeader: *header}, nil +} + +func (actuator swiftcontainerActuator) DeleteResource(ctx context.Context, _ orcObjectPT, osResource *osContainerT) progress.ReconcileStatus { + err := actuator.osClient.DeleteContainer(ctx, osResource.Name) + if orcerrors.IsNotFound(err) { + return nil + } + return progress.WrapError(err) +} + +func (actuator swiftcontainerActuator) GetResourceReconcilers(_ context.Context, _ orcObjectPT, _ *osResourceT, _ generic.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.reconcileACLs, + actuator.reconcileMetadata, + }, nil +} + +// reconcileACLs compares the desired ACLs from the spec with the current +// container ACLs and calls UpdateContainer if they differ. +func (actuator swiftcontainerActuator) reconcileACLs(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := orcObject.Spec.Resource + if resource == nil { + return nil + } + + // GetHeader.Read and GetHeader.Write are []string parsed from the ACL + // headers. We join them back to a comma-separated string for comparison + // with the spec which stores ACLs as a single string. + currentRead := strings.Join(osResource.Read, ",") + currentWrite := strings.Join(osResource.Write, ",") + + desiredRead := resource.ContainerRead + desiredWrite := resource.ContainerWrite + + if currentRead == desiredRead && currentWrite == desiredWrite { + log.V(logging.Debug).Info("Container ACLs are up to date") + return nil + } + + log.V(logging.Info).Info("Updating container ACLs") + updateOpts := containers.UpdateOpts{ + ContainerRead: &desiredRead, + ContainerWrite: &desiredWrite, + } + _, err := actuator.osClient.UpdateContainer(ctx, osResource.Name, updateOpts) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating container ACLs: "+err.Error(), err) + } + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +// reconcileMetadata compares the desired metadata from the spec with the +// current container metadata and calls UpdateContainer if they differ. +func (actuator swiftcontainerActuator) reconcileMetadata(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := orcObject.Spec.Resource + if resource == nil { + return nil + } + + // Fetch current metadata via GetContainerMetadata. Keys are returned in + // canonical HTTP header form (e.g. "Env", not "env") because Go's net/http + // canonicalises header keys on retrieval. + currentMetadata, err := actuator.osClient.GetContainerMetadata(ctx, osResource.Name) + if err != nil { + return progress.WrapError(err) + } + + // Build a lowercase-keyed view of current metadata for case-insensitive + // comparison. We also track the original canonical key so we can form + // correct X-Remove-Container-Meta- headers when removing entries. + currentLower := make(map[string]string, len(currentMetadata)) // lowercase key -> value + currentCanonical := make(map[string]string, len(currentMetadata)) // lowercase key -> canonical key + for k, v := range currentMetadata { + lk := strings.ToLower(k) + currentLower[lk] = v + currentCanonical[lk] = k + } + + // Build the desired metadata map from the spec with lowercase keys so that + // comparisons are case-insensitive (metadata key casing is not significant + // in Swift). + desiredMetadata := make(map[string]string, len(resource.Metadata)) + for _, m := range resource.Metadata { + desiredMetadata[strings.ToLower(m.Key)] = m.Value + } + + // Find keys to add/update and keys to remove. + var toSet map[string]string + var toRemove []string + + for key, desiredVal := range desiredMetadata { + if currentVal, exists := currentLower[key]; !exists || currentVal != desiredVal { + if toSet == nil { + toSet = make(map[string]string) + } + toSet[key] = desiredVal + } + } + for lk := range currentLower { + if _, desired := desiredMetadata[lk]; !desired { + // Use the canonical key so that the X-Remove-Container-Meta- + // header matches what Swift has stored. + toRemove = append(toRemove, currentCanonical[lk]) + } + } + + if len(toSet) == 0 && len(toRemove) == 0 { + log.V(logging.Debug).Info("Container metadata is up to date") + return nil + } + + log.V(logging.Info).Info("Updating container metadata") + updateOpts := containers.UpdateOpts{ + Metadata: toSet, + RemoveMetadata: toRemove, + } + _, err = actuator.osClient.UpdateContainer(ctx, osResource.Name, updateOpts) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating container metadata: "+err.Error(), err) + } + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +type swiftcontainerHelperFactory struct{} + +var _ helperFactory = swiftcontainerHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.SwiftContainer, controller generic.ResourceController) (swiftcontainerActuator, progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + // Ensure credential secrets exist and have our finalizer + _, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true }) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return swiftcontainerActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return swiftcontainerActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewSwiftContainerClient() + if err != nil { + return swiftcontainerActuator{}, progress.WrapError(err) + } + + return swiftcontainerActuator{ + osClient: osClient, + }, nil +} + +func (swiftcontainerHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return swiftcontainerAdapter{obj} +} + +func (swiftcontainerHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller generic.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (swiftcontainerHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller generic.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/swiftcontainer/actuator_test.go b/internal/controllers/swiftcontainer/actuator_test.go new file mode 100644 index 000000000..1f0b200db --- /dev/null +++ b/internal/controllers/swiftcontainer/actuator_test.go @@ -0,0 +1,688 @@ +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + "context" + "errors" + "fmt" + "iter" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + osclients "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +var ( + errNotImplemented = errors.New("not implemented") + errTest = errors.New("test error") +) + +// mockSwiftContainerClient implements the osclients.SwiftContainerClient +// interface for use in unit tests. +type mockSwiftContainerClient struct { + // containers is the list of containers returned by ListContainers. + containers []containers.Container + + // containerData maps container name to its simulated GetHeader and metadata. + containerData map[string]mockContainerData + + // listErr, if non-nil, is returned as an error from ListContainers. + listErr error +} + +type mockContainerData struct { + header containers.GetHeader + metadata map[string]string +} + +var _ osclients.SwiftContainerClient = &mockSwiftContainerClient{} + +func (m *mockSwiftContainerClient) ListContainers(_ context.Context, _ containers.ListOptsBuilder) iter.Seq2[*containers.Container, error] { + return func(yield func(*containers.Container, error) bool) { + if m.listErr != nil { + yield(nil, m.listErr) + return + } + for i := range m.containers { + if !yield(&m.containers[i], nil) { + return + } + } + } +} + +func (m *mockSwiftContainerClient) CreateContainer(_ context.Context, containerName string, _ containers.CreateOptsBuilder) (*containers.CreateHeader, error) { + if m.containerData == nil { + m.containerData = make(map[string]mockContainerData) + } + m.containerData[containerName] = mockContainerData{ + header: containers.GetHeader{}, + metadata: map[string]string{}, + } + return &containers.CreateHeader{}, nil +} + +func (m *mockSwiftContainerClient) GetContainer(_ context.Context, containerName string, _ containers.GetOptsBuilder) (*containers.GetHeader, error) { + if m.containerData == nil { + return nil, gophercloud.ErrResourceNotFound{Name: containerName} + } + data, ok := m.containerData[containerName] + if !ok { + return nil, gophercloud.ErrResourceNotFound{Name: containerName} + } + header := data.header + return &header, nil +} + +func (m *mockSwiftContainerClient) GetContainerMetadata(_ context.Context, containerName string) (map[string]string, error) { + if m.containerData == nil { + return nil, gophercloud.ErrResourceNotFound{Name: containerName} + } + data, ok := m.containerData[containerName] + if !ok { + return nil, gophercloud.ErrResourceNotFound{Name: containerName} + } + meta := make(map[string]string, len(data.metadata)) + for k, v := range data.metadata { + meta[k] = v + } + return meta, nil +} + +func (m *mockSwiftContainerClient) DeleteContainer(_ context.Context, _ string) error { + return errNotImplemented +} + +func (m *mockSwiftContainerClient) UpdateContainer(_ context.Context, _ string, _ containers.UpdateOptsBuilder) (*containers.UpdateHeader, error) { + return nil, errNotImplemented +} + +// containerResult holds the result of a single iteration from a container iterator. +type containerResult struct { + container *osContainerT + err error +} + +type checkFunc func([]containerResult) error + +func checks(fns ...checkFunc) []checkFunc { return fns } + +func noError(results []containerResult) error { + for _, result := range results { + if result.err != nil { + return fmt.Errorf("unexpected error: %w", result.err) + } + } + return nil +} + +func wantError(wantErr error) checkFunc { + return func(results []containerResult) error { + for _, result := range results { + if result.err == nil { + continue + } + if errors.Is(result.err, wantErr) { + return nil + } + return fmt.Errorf("unexpected error: %w", result.err) + } + // If we get here, no error was found anywhere + return fmt.Errorf("expected error %v", wantErr) + } +} + +func findsN(wantN int) checkFunc { + return func(results []containerResult) error { + found := len(results) + if found != wantN { + return fmt.Errorf("expected %d results, got %d", wantN, found) + } + return nil + } +} + +func findsID(wantName string) checkFunc { + return func(results []containerResult) error { + for _, result := range results { + if result.container == nil { + continue + } + if result.container.Name == wantName { + return nil + } + } + return fmt.Errorf("did not find container with name %s", wantName) + } +} + +// newSwiftContainerObject creates a SwiftContainer object for use in tests. +func newSwiftContainerObject(name string, resource *orcv1alpha1.SwiftContainerResourceSpec) *orcv1alpha1.SwiftContainer { + return &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: resource, + }, + } +} + +func TestListOSResourcesForImport(t *testing.T) { + for _, tc := range [...]struct { + name string + filter orcv1alpha1.SwiftContainerFilter + client osclients.SwiftContainerClient + checks []checkFunc + }{ + { + name: "finds one by name", + filter: orcv1alpha1.SwiftContainerFilter{Name: ptr.To[orcv1alpha1.SwiftContainerName]("my-container")}, + client: &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "my-container": {header: containers.GetHeader{}, metadata: map[string]string{}}, + "other-container": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + }, + checks: checks(noError, findsID("my-container"), findsN(1)), + }, + { + name: "finds none by name", + filter: orcv1alpha1.SwiftContainerFilter{Name: ptr.To[orcv1alpha1.SwiftContainerName]("missing-container")}, + client: &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "my-container": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + }, + checks: checks(noError, findsN(0)), + }, + { + name: "finds multiple containers matching prefix filter", + filter: orcv1alpha1.SwiftContainerFilter{Prefix: ptr.To("test-")}, + client: &mockSwiftContainerClient{ + containers: []containers.Container{ + {Name: "test-alpha"}, + {Name: "test-beta"}, + {Name: "other"}, + }, + containerData: map[string]mockContainerData{ + "test-alpha": {header: containers.GetHeader{}, metadata: map[string]string{}}, + "test-beta": {header: containers.GetHeader{}, metadata: map[string]string{}}, + "other": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + }, + checks: checks(noError, findsN(2)), + }, + { + name: "returns lister errors from client", + filter: orcv1alpha1.SwiftContainerFilter{Prefix: ptr.To("test-")}, + client: &mockSwiftContainerClient{ + listErr: errTest, + }, + checks: checks(wantError(errTest)), + }, + { + // When both Name and Prefix are set and the named container does not + // match the prefix, no results should be returned. Previously the + // prefix predicate was silently ignored in the name-lookup branch. + name: "finds none when name does not match prefix", + filter: orcv1alpha1.SwiftContainerFilter{ + Name: ptr.To[orcv1alpha1.SwiftContainerName]("prod-bucket"), + Prefix: ptr.To("test-"), + }, + client: &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "prod-bucket": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + }, + checks: checks(noError, findsN(0)), + }, + { + // When both Name and Prefix are set and the named container matches + // the prefix, the container should be found normally. + name: "finds one when name matches prefix", + filter: orcv1alpha1.SwiftContainerFilter{ + Name: ptr.To[orcv1alpha1.SwiftContainerName]("test-bucket"), + Prefix: ptr.To("test-"), + }, + client: &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "test-bucket": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + }, + checks: checks(noError, findsN(1), findsID("test-bucket")), + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + actuator := swiftcontainerActuator{tc.client} + containerIter, _ := actuator.ListOSResourcesForImport(ctx, &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "swiftcontainer", + }, + }, tc.filter) + + var results []containerResult + for container, err := range containerIter { + results = append(results, containerResult{container, err}) + } + + for _, check := range tc.checks { + if e := check(results); e != nil { + t.Error(e) + } + } + }) + } +} + +func TestListOSResourcesForAdoption(t *testing.T) { + t.Run("finds container by resource name", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "explicit-name": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + } + actuator := swiftcontainerActuator{client} + orcObject := newSwiftContainerObject("my-object", &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To[orcv1alpha1.SwiftContainerName]("explicit-name"), + }) + + containerIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if !canAdopt { + t.Fatal("canAdopt should be true when spec.resource is set") + } + + var results []containerResult + for container, err := range containerIter { + results = append(results, containerResult{container, err}) + } + + for _, check := range checks(noError, findsID("explicit-name"), findsN(1)) { + if e := check(results); e != nil { + t.Error(e) + } + } + }) + + t.Run("finds container by object name when spec.resource.name is nil", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "my-object": {header: containers.GetHeader{}, metadata: map[string]string{}}, + }, + } + actuator := swiftcontainerActuator{client} + // spec.resource.name is nil, so the object's own name should be used + orcObject := newSwiftContainerObject("my-object", &orcv1alpha1.SwiftContainerResourceSpec{}) + + containerIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if !canAdopt { + t.Fatal("canAdopt should be true when spec.resource is set") + } + + var results []containerResult + for container, err := range containerIter { + results = append(results, containerResult{container, err}) + } + + for _, check := range checks(noError, findsID("my-object"), findsN(1)) { + if e := check(results); e != nil { + t.Error(e) + } + } + }) + + t.Run("finds none when container does not exist", func(t *testing.T) { + ctx := context.Background() + // Empty containerData so all GetContainer calls return 404 + client := &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{}, + } + actuator := swiftcontainerActuator{client} + orcObject := newSwiftContainerObject("nonexistent", &orcv1alpha1.SwiftContainerResourceSpec{}) + + containerIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if !canAdopt { + t.Fatal("canAdopt should be true when spec.resource is set") + } + + var results []containerResult + for container, err := range containerIter { + results = append(results, containerResult{container, err}) + } + + for _, check := range checks(noError, findsN(0)) { + if e := check(results); e != nil { + t.Error(e) + } + } + }) + + t.Run("returns metadata when container has metadata", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{ + containerData: map[string]mockContainerData{ + "my-object": { + header: containers.GetHeader{}, + metadata: map[string]string{"env": "prod", "team": "infra"}, + }, + }, + } + actuator := swiftcontainerActuator{client} + orcObject := newSwiftContainerObject("my-object", &orcv1alpha1.SwiftContainerResourceSpec{ + Metadata: []orcv1alpha1.SwiftContainerMetadata{ + {Key: "env", Value: "prod"}, + {Key: "team", Value: "infra"}, + }, + }) + + containerIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if !canAdopt { + t.Fatal("canAdopt should be true when spec.resource is set") + } + + var results []containerResult + for container, err := range containerIter { + results = append(results, containerResult{container, err}) + } + + for _, check := range checks(noError, findsN(1)) { + if e := check(results); e != nil { + t.Error(e) + } + } + + // Verify metadata is passed through + if len(results) == 1 && results[0].container != nil { + if results[0].container.Metadata["env"] != "prod" { + t.Errorf("expected metadata env=prod, got %q", results[0].container.Metadata["env"]) + } + if results[0].container.Metadata["team"] != "infra" { + t.Errorf("expected metadata team=infra, got %q", results[0].container.Metadata["team"]) + } + } + }) + + t.Run("returns false for canAdopt when spec.resource is nil", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + // spec.resource is nil + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "my-object"}, + Spec: orcv1alpha1.SwiftContainerSpec{}, + } + + _, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if canAdopt { + t.Error("canAdopt should be false when spec.resource is nil") + } + }) +} + +func TestCreateResource(t *testing.T) { + t.Run("successfully creates container with minimal config", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + orcObject := newSwiftContainerObject("my-container", &orcv1alpha1.SwiftContainerResourceSpec{}) + + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if reconcileStatus != nil { + t.Fatalf("unexpected reconcile status: %v", reconcileStatus) + } + if result == nil { + t.Fatal("expected non-nil result") + } + if result.Name != "my-container" { + t.Errorf("expected container name %q, got %q", "my-container", result.Name) + } + }) + + t.Run("successfully creates container with full config", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + orcObject := newSwiftContainerObject("full-container", &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To[orcv1alpha1.SwiftContainerName]("full-container"), + Metadata: []orcv1alpha1.SwiftContainerMetadata{ + {Key: "project", Value: "orc"}, + {Key: "env", Value: "test"}, + }, + ContainerRead: ".r:*", + ContainerWrite: "account:user", + StoragePolicy: "gold", + }) + + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if reconcileStatus != nil { + t.Fatalf("unexpected reconcile status: %v", reconcileStatus) + } + if result == nil { + t.Fatal("expected non-nil result") + } + if result.Name != "full-container" { + t.Errorf("expected container name %q, got %q", "full-container", result.Name) + } + }) + + t.Run("returns terminal error for invalid container name containing slash", func(t *testing.T) { + ctx := context.Background() + + // The actuator explicitly validates the container name before calling + // the Swift API. A slash in the name is caught early and returned as a + // terminal error with a message mentioning "forward slashes". + // (Kubebuilder validation normally prevents this from reaching the + // controller, but in unit tests API validation is not enforced.) + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + // Use a name that bypasses kubebuilder validation (in unit tests, API + // validation is not enforced); this simulates what would happen if a + // slash somehow reached the actuator. + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid"}, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To[orcv1alpha1.SwiftContainerName]("invalid/name"), + }, + }, + } + + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if result != nil { + t.Errorf("expected nil result, got %v", result) + } + if reconcileStatus == nil { + t.Fatal("expected non-nil reconcile status for terminal error") + } + _, err := reconcileStatus.NeedsReschedule() + if err == nil { + t.Error("expected error from reconcile status") + } + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected TerminalError, got %T: %v", err, err) + } + }) + + t.Run("returns terminal error for container name exceeding 256 bytes", func(t *testing.T) { + ctx := context.Background() + + // A name exceeding 256 UTF-8 bytes should cause Swift to reject it. + // Simulate this via the error client. + terminalErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "container name exceeds maximum length") + client := osclients.NewSwiftContainerErrorClient(terminalErr) + actuator := swiftcontainerActuator{client} + longName := strings.Repeat("a", 257) + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid"}, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To[orcv1alpha1.SwiftContainerName](orcv1alpha1.SwiftContainerName(longName)), + }, + }, + } + + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if result != nil { + t.Errorf("expected nil result, got %v", result) + } + if reconcileStatus == nil { + t.Fatal("expected non-nil reconcile status for terminal error") + } + _, err := reconcileStatus.NeedsReschedule() + if err == nil { + t.Error("expected error from reconcile status") + } + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected TerminalError, got %T: %v", err, err) + } + }) + + t.Run("returns error when spec.resource is nil", func(t *testing.T) { + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "my-container"}, + Spec: orcv1alpha1.SwiftContainerSpec{}, + } + + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if result != nil { + t.Errorf("expected nil result, got %v", result) + } + if reconcileStatus == nil { + t.Fatal("expected non-nil reconcile status when spec.resource is nil") + } + _, err := reconcileStatus.NeedsReschedule() + if err == nil { + t.Error("expected error from reconcile status") + } + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected TerminalError, got %T: %v", err, err) + } + }) +} + +func TestContainerNameValidation(t *testing.T) { + t.Run("rejects names containing forward slash", func(t *testing.T) { + name := orcv1alpha1.SwiftContainerName("containers/bucket") + if !strings.Contains(string(name), "/") { + t.Fatal("test setup error: name should contain a slash") + } + // The actuator validates the name before calling the Swift API. + // A slash in the name causes an early terminal error with a message + // mentioning "forward slashes". (Kubebuilder pattern validation would + // normally catch this before it reaches the controller, but in unit + // tests API validation is not enforced.) + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid"}, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To(name), + }, + }, + } + _, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if reconcileStatus == nil { + t.Error("expected reconcile status error for name with slash") + return + } + _, err := reconcileStatus.NeedsReschedule() + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected TerminalError for invalid name, got %T: %v", err, err) + } + if !strings.Contains(termErr.Error(), "forward slashes") { + t.Errorf("expected error message to mention 'forward slashes', got: %v", termErr.Error()) + } + }) + + t.Run("rejects names exceeding 256 UTF-8 bytes", func(t *testing.T) { + longName := orcv1alpha1.SwiftContainerName(strings.Repeat("x", 257)) + if len(longName) <= 256 { + t.Fatal("test setup error: name should exceed 256 bytes") + } + ctx := context.Background() + terminalErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "container name exceeds 256 bytes") + client := osclients.NewSwiftContainerErrorClient(terminalErr) + actuator := swiftcontainerActuator{client} + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid"}, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To(longName), + }, + }, + } + _, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if reconcileStatus == nil { + t.Error("expected reconcile status error for name exceeding 256 bytes") + return + } + _, err := reconcileStatus.NeedsReschedule() + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected TerminalError for too-long name, got %T: %v", err, err) + } + }) + + t.Run("accepts valid names at boundary (exactly 256 bytes)", func(t *testing.T) { + // A name of exactly 256 ASCII characters is valid. + exactName := orcv1alpha1.SwiftContainerName(strings.Repeat("a", 256)) + if len(exactName) != 256 { + t.Fatal("test setup error: name should be exactly 256 bytes") + } + ctx := context.Background() + client := &mockSwiftContainerClient{} + actuator := swiftcontainerActuator{client} + orcObject := &orcv1alpha1.SwiftContainer{ + ObjectMeta: metav1.ObjectMeta{Name: "boundary-test"}, + Spec: orcv1alpha1.SwiftContainerSpec{ + Resource: &orcv1alpha1.SwiftContainerResourceSpec{ + Name: ptr.To(exactName), + }, + }, + } + result, reconcileStatus := actuator.CreateResource(ctx, orcObject) + if reconcileStatus != nil { + t.Fatalf("expected no error for valid 256-byte name, got: %v", reconcileStatus) + } + if result == nil { + t.Error("expected non-nil result for valid 256-byte name") + } + }) +} diff --git a/internal/controllers/swiftcontainer/controller.go b/internal/controllers/swiftcontainer/controller.go new file mode 100644 index 000000000..8f3b759db --- /dev/null +++ b/internal/controllers/swiftcontainer/controller.go @@ -0,0 +1,68 @@ +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials" +) + +const controllerName = "swiftcontainer" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=swiftcontainers,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=swiftcontainers/status,verbs=get;update;patch + +type swiftcontainerReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return swiftcontainerReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (swiftcontainerReconcilerConstructor) GetName() string { + return controllerName +} + +// SetupWithManager sets up the controller with the Manager. +func (c swiftcontainerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + For(&orcv1alpha1.SwiftContainer{}) + + if err := errors.Join( + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, swiftcontainerHelperFactory{}, swiftcontainerStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/swiftcontainer/status.go b/internal/controllers/swiftcontainer/status.go new file mode 100644 index 000000000..1436144a4 --- /dev/null +++ b/internal/controllers/swiftcontainer/status.go @@ -0,0 +1,98 @@ +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + "sort" + "strings" + + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +type swiftcontainerStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.SwiftContainerApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.SwiftContainerStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.SwiftContainer, *osContainerT, *objectApplyT, *statusApplyT] = swiftcontainerStatusWriter{} + +func (swiftcontainerStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.SwiftContainer(name, namespace) +} + +func (swiftcontainerStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.SwiftContainer, osResource *osContainerT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource == nil { + if orcObject.Status.ID == nil { + return metav1.ConditionFalse, nil + } + return metav1.ConditionUnknown, nil + } + + // SwiftContainer is available as soon as it exists + return metav1.ConditionTrue, nil +} + +func (swiftcontainerStatusWriter) ApplyResourceStatus(_ logr.Logger, osResource *osContainerT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.SwiftContainerResourceStatus(). + WithName(osResource.Name). + WithBytesUsed(osResource.BytesUsed). + WithObjectCount(osResource.ObjectCount) + + // containerRead and containerWrite are stored as []string (parsed from + // comma-separated ACL headers); join them back for status reporting. + // Only set the status field if the resulting ACL string is non-empty; + // gophercloud may return []string{""} for an absent header, which + // should not be surfaced as an empty-string ACL in status. + if readACL := strings.Join(osResource.Read, ","); readACL != "" { + resourceStatus.WithContainerRead(readACL) + } + if writeACL := strings.Join(osResource.Write, ","); writeACL != "" { + resourceStatus.WithContainerWrite(writeACL) + } + + if osResource.StoragePolicy != "" { + resourceStatus.WithStoragePolicy(osResource.StoragePolicy) + } + if osResource.VersionsLocation != "" { + resourceStatus.WithVersions(osResource.VersionsLocation) + } + + // Populate observed metadata from X-Container-Meta-* headers. + // Keys are sorted to ensure deterministic output. + if len(osResource.Metadata) > 0 { + keys := make([]string, 0, len(osResource.Metadata)) + for k := range osResource.Metadata { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + resourceStatus.WithMetadata( + orcapplyconfigv1alpha1.SwiftContainerMetadataStatus(). + WithKey(k). + WithValue(osResource.Metadata[k]), + ) + } + } + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/swiftcontainer/tests/.gitkeep b/internal/controllers/swiftcontainer/tests/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-assert.yaml new file mode 100644 index 000000000..412672e1f --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-create-full +status: + resource: + name: swiftcontainer-create-full-override + bytesUsed: 0 + objectCount: 0 + containerRead: ".r:*,.rlistings" + containerWrite: "openstack:*" + storagePolicy: Policy-0 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-create-full + ref: swiftcontainer +assertAll: + - celExpr: "swiftcontainer.status.id != ''" + - celExpr: "swiftcontainer.status.id == 'swiftcontainer-create-full-override'" + - celExpr: "has(swiftcontainer.status.resource.metadata)" + - celExpr: "swiftcontainer.status.resource.metadata.exists(m, m.key == 'Environment' && m.value == 'test')" + - celExpr: "swiftcontainer.status.resource.metadata.exists(m, m.key == 'Owner' && m.value == 'orc-e2e')" diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-create-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-create-resource.yaml new file mode 100644 index 000000000..90219cd98 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-create-resource.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-create-full +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: swiftcontainer-create-full-override + metadata: + - key: environment + value: test + - key: owner + value: orc-e2e + containerRead: ".r:*,.rlistings" + containerWrite: "openstack:*" + storagePolicy: Policy-0 diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-secret.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/00-secret.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/README.md b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/README.md new file mode 100644 index 000000000..9ab312501 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/README.md @@ -0,0 +1,21 @@ +# Create a SwiftContainer with all the options + +## Step 00 + +Create a Swift container using all available fields, and verify that the +observed state corresponds to the spec. + +Validates that: +- The OpenStack resource uses the name from `spec.resource.name` when it is + specified, rather than the ORC object name (SC-002). +- Custom metadata key-value pairs are applied and reflected in + `status.resource.metadata`. +- Read ACL (`containerRead`) and write ACL (`containerWrite`) are configured + and reflected in `status.resource`. +- Storage policy (`storagePolicy`) is configured and reflected in + `status.resource`. +- `Available=True` and `Progressing=False` conditions are set. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-assert.yaml new file mode 100644 index 000000000..83bc8c14f --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-assert.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-create-minimal +status: + resource: + name: swiftcontainer-create-minimal + bytesUsed: 0 + objectCount: 0 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-create-minimal + ref: swiftcontainer +assertAll: + - celExpr: "swiftcontainer.status.id != ''" + - celExpr: "swiftcontainer.status.id == 'swiftcontainer-create-minimal'" + - celExpr: "!has(swiftcontainer.status.resource.metadata)" + - celExpr: "!has(swiftcontainer.status.resource.containerRead)" + - celExpr: "!has(swiftcontainer.status.resource.containerWrite)" diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-create-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..152c59c78 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-create-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-secret.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-secret.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-assert.yaml new file mode 100644 index 000000000..ee0d964f8 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: v1 + kind: Secret + name: openstack-clouds + ref: secret +assertAll: + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/swiftcontainer' in secret.metadata.finalizers" diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-delete-secret.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..95d2be18b --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/01-delete-secret.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete secret openstack-clouds --wait=false + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/README.md b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/README.md new file mode 100644 index 000000000..7a799b6c1 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/README.md @@ -0,0 +1,18 @@ +# Create a SwiftContainer with the minimum options + +## Step 00 + +Create a minimal SwiftContainer, that sets only the required fields, and verify +that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name of the ORC object when +`spec.resource.name` is not specified (SC-001). + +## Step 01 + +Try deleting the secret and ensure that it is not deleted thanks to the +finalizer. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-assert.yaml new file mode 100644 index 000000000..b83c5c6b1 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error-external-1 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error-external-2 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-create-resources.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-create-resources.yaml new file mode 100644 index 000000000..1670beda9 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-create-resources.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error-external-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error-external-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-secret.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/00-secret.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-assert.yaml new file mode 100644 index 000000000..1286cd9a7 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error +status: + conditions: + - type: Available + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration + - type: Progressing + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-import-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-import-resource.yaml new file mode 100644 index 000000000..a4ae554b5 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + prefix: swiftcontainer-import-error-external diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/README.md b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/README.md new file mode 100644 index 000000000..f9a875fea --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import-error/README.md @@ -0,0 +1,15 @@ +# Import SwiftContainer with more than one matching resource + +## Step 00 + +Create two Swift containers whose names share the same prefix +(`swiftcontainer-import-error-external`). + +## Step 01 + +Ensure that an imported SwiftContainer using a prefix filter that matches both +resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-assert.yaml new file mode 100644 index 000000000..bc1cc936a --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-import-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-import-resource.yaml new file mode 100644 index 000000000..26fe9737f --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: swiftcontainer-import-external diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-secret.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/00-secret.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-assert.yaml new file mode 100644 index 000000000..996ee707a --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-external-not-this-one +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: swiftcontainer-import-external-not-this-one +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-create-trap-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..5f2a0332e --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/01-create-trap-resource.yaml @@ -0,0 +1,15 @@ +--- +# This `swiftcontainer-import-external-not-this-one` resource serves two purposes: +# - ensure that we can successfully create another resource whose name is a superstring +# of the import filter name (i.e. it's not being adopted by the import CR) +# - ensure that importing a resource whose name matches the filter will not pick this one. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-assert.yaml new file mode 100644 index 000000000..c1a2c533f --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-import-external + ref: swiftcontainer1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-import-external-not-this-one + ref: swiftcontainer2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-import + ref: swiftcontainerImport +assertAll: + - celExpr: "swiftcontainer1.status.id != swiftcontainer2.status.id" + - celExpr: "swiftcontainerImport.status.id == swiftcontainer1.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: swiftcontainer-import-external + bytesUsed: 0 + objectCount: 0 diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-create-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-create-resource.yaml new file mode 100644 index 000000000..5d04265ed --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/02-create-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-import-external +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-import/README.md b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/README.md new file mode 100644 index 000000000..0ed6a9aff --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-import/README.md @@ -0,0 +1,29 @@ +# Import SwiftContainer + +## Step 00 + +Import a Swift container using a name filter, and verify it is waiting for +the external resource to be created. + +## Step 01 + +Create a Swift container whose name is a superstring of the one specified in +the import filter, and otherwise matching the filter, and verify that it is +not being imported (trap pattern). + +## Step 02 + +Create a Swift container matching the filter and verify that the observed +status on the imported container corresponds to the spec of the created +container. Also verify that the created container didn't adopt the one whose +name is a superstring of it (filter specificity). + +Validates that: +- The import filter matches the container with exact name (not a superstring). +- `status.id` is populated with the imported container name. +- Adoption from unmanaged import to available state works without recreating + the container (SC-003). + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-assert.yaml new file mode 100644 index 000000000..52885a915 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-update + ref: swiftcontainer +assertAll: + - celExpr: "swiftcontainer.status.id == 'swiftcontainer-update'" + - celExpr: "!has(swiftcontainer.status.resource.metadata)" + - celExpr: "!has(swiftcontainer.status.resource.containerRead)" + - celExpr: "!has(swiftcontainer.status.resource.containerWrite)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-update +status: + resource: + name: swiftcontainer-update + bytesUsed: 0 + objectCount: 0 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-minimal-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-minimal-resource.yaml new file mode 100644 index 000000000..584ff2baa --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-minimal-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-prerequisites.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-prerequisites.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/00-prerequisites.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-assert.yaml new file mode 100644 index 000000000..6d8904875 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-update + ref: swiftcontainer +assertAll: + - celExpr: "swiftcontainer.status.id == 'swiftcontainer-update'" + - celExpr: "has(swiftcontainer.status.resource.metadata)" + - celExpr: "swiftcontainer.status.resource.metadata.exists(m, m.key == 'Environment' && m.value == 'staging')" + - celExpr: "swiftcontainer.status.resource.metadata.exists(m, m.key == 'Team' && m.value == 'platform')" + - celExpr: "swiftcontainer.status.resource.containerRead == '.r:*,.rlistings'" + - celExpr: "swiftcontainer.status.resource.containerWrite == 'openstack:*'" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-update +status: + resource: + name: swiftcontainer-update + bytesUsed: 0 + objectCount: 0 + containerRead: ".r:*,.rlistings" + containerWrite: "openstack:*" + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-updated-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-updated-resource.yaml new file mode 100644 index 000000000..0447e9476 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/01-updated-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-update +spec: + resource: + metadata: + - key: environment + value: staging + - key: team + value: platform + containerRead: ".r:*,.rlistings" + containerWrite: "openstack:*" diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-assert.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-assert.yaml new file mode 100644 index 000000000..52885a915 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SwiftContainer + name: swiftcontainer-update + ref: swiftcontainer +assertAll: + - celExpr: "swiftcontainer.status.id == 'swiftcontainer-update'" + - celExpr: "!has(swiftcontainer.status.resource.metadata)" + - celExpr: "!has(swiftcontainer.status.resource.containerRead)" + - celExpr: "!has(swiftcontainer.status.resource.containerWrite)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SwiftContainer +metadata: + name: swiftcontainer-update +status: + resource: + name: swiftcontainer-update + bytesUsed: 0 + objectCount: 0 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-reverted-resource.yaml b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-reverted-resource.yaml new file mode 100644 index 000000000..3cb5c0c78 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/02-reverted-resource.yaml @@ -0,0 +1,7 @@ +# NOTE: kuttl only does patch updates, which means we can't delete a field. +# We have to use a kubectl replace command instead. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl replace -f 00-minimal-resource.yaml + namespaced: true diff --git a/internal/controllers/swiftcontainer/tests/swiftcontainer-update/README.md b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/README.md new file mode 100644 index 000000000..d982fa124 --- /dev/null +++ b/internal/controllers/swiftcontainer/tests/swiftcontainer-update/README.md @@ -0,0 +1,28 @@ +# Update SwiftContainer + +## Step 00 + +Create a SwiftContainer using only the required fields (no metadata, no ACLs), +and verify that the observed state corresponds to the spec. + +## Step 01 + +Update all mutable fields: +- Add custom metadata key-value pairs +- Set read and write ACLs (`containerRead` and `containerWrite`) + +Verify that all updated properties are reflected in the resource status. + +## Step 02 + +Revert the resource to its original value (no metadata, no ACLs) and verify +the resulting object matches the initial creation state. + +Validates that: +- Clearing `containerRead` and `containerWrite` (by removing the fields) removes the ACLs from the container. +- Removing metadata entries removes them from the container. +- `Available=True` and `Progressing=False` conditions are set after each step. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/controllers/swiftcontainer/zz_generated.adapter.go b/internal/controllers/swiftcontainer/zz_generated.adapter.go new file mode 100644 index 000000000..9dcb12416 --- /dev/null +++ b/internal/controllers/swiftcontainer/zz_generated.adapter.go @@ -0,0 +1,92 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" +) + +// Fundamental types +type ( + orcObjectT = orcv1alpha1.SwiftContainer + orcObjectListT = orcv1alpha1.SwiftContainerList + resourceSpecT = orcv1alpha1.SwiftContainerResourceSpec + filterT = orcv1alpha1.SwiftContainerFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = swiftcontainerAdapter +) + +type swiftcontainerAdapter struct { + *orcv1alpha1.SwiftContainer +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.SwiftContainer +} + +func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return f.Spec.ManagementPolicy +} + +func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return f.Spec.ManagedOptions +} + +func (f adapterT) GetStatusID() *string { + return f.Status.ID +} + +func (f adapterT) GetResourceSpec() *resourceSpecT { + return f.Spec.Resource +} + +func (f adapterT) GetImportID() *string { + if f.Spec.Import == nil { + return nil + } + if f.Spec.Import.Name == nil { + return nil + } + name := string(*f.Spec.Import.Name) + return &name +} + +func (f adapterT) GetImportFilter() *filterT { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.Filter +} + +// getResourceName returns the name of the OpenStack resource we should use. +// This method is not implemented as part of APIObjectAdapter as it is intended +// to be used by resource actuators, which don't use the adapter. +func getResourceName(orcObject orcObjectPT) string { + if orcObject.Spec.Resource.Name != nil { + return string(*orcObject.Spec.Resource.Name) + } + return orcObject.Name +} diff --git a/internal/controllers/swiftcontainer/zz_generated.controller.go b/internal/controllers/swiftcontainer/zz_generated.controller.go new file mode 100644 index 000000000..12a7d511b --- /dev/null +++ b/internal/controllers/swiftcontainer/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +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 swiftcontainer + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" +) + +var ( + // NOTE: controllerName must be defined in any controller using this template + + // finalizer is the string this controller adds to an object's Finalizers + finalizer = orcstrings.GetFinalizerName(controllerName) + + // externalObjectFieldOwner is the field owner we use when using + // server-side-apply on objects we don't control + externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName) + + credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret]( + "spec.cloudCredentialsRef.secretName", + func(obj orcObjectPT) []string { + return []string{obj.Spec.CloudCredentialsRef.SecretName} + }, + finalizer, externalObjectFieldOwner, + dependency.OverrideDependencyName("credentials"), + ) +) diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 5ee7aa5da..205d540b9 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -62,6 +62,9 @@ import ( //go:generate mockgen -package mock -destination=sharenetwork.go -source=../sharenetwork.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ShareNetworkClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt sharenetwork.go > _sharenetwork.go && mv _sharenetwork.go sharenetwork.go" +//go:generate mockgen -package mock -destination=swiftcontainer.go -source=../swiftcontainer.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock SwiftContainerClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt swiftcontainer.go > _swiftcontainer.go && mv _swiftcontainer.go swiftcontainer.go" + //go:generate mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt user.go > _user.go && mv _user.go user.go" diff --git a/internal/osclients/mock/swiftcontainer.go b/internal/osclients/mock/swiftcontainer.go new file mode 100644 index 000000000..3667e4e3e --- /dev/null +++ b/internal/osclients/mock/swiftcontainer.go @@ -0,0 +1,146 @@ +/* +Copyright The ORC Authors. + +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. +*/ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../swiftcontainer.go +// +// Generated by this command: +// +// mockgen -package mock -destination=swiftcontainer.go -source=../swiftcontainer.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock SwiftContainerClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + containers "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" + gomock "go.uber.org/mock/gomock" +) + +// MockSwiftContainerClient is a mock of SwiftContainerClient interface. +type MockSwiftContainerClient struct { + ctrl *gomock.Controller + recorder *MockSwiftContainerClientMockRecorder + isgomock struct{} +} + +// MockSwiftContainerClientMockRecorder is the mock recorder for MockSwiftContainerClient. +type MockSwiftContainerClientMockRecorder struct { + mock *MockSwiftContainerClient +} + +// NewMockSwiftContainerClient creates a new mock instance. +func NewMockSwiftContainerClient(ctrl *gomock.Controller) *MockSwiftContainerClient { + mock := &MockSwiftContainerClient{ctrl: ctrl} + mock.recorder = &MockSwiftContainerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSwiftContainerClient) EXPECT() *MockSwiftContainerClientMockRecorder { + return m.recorder +} + +// CreateContainer mocks base method. +func (m *MockSwiftContainerClient) CreateContainer(ctx context.Context, containerName string, opts containers.CreateOptsBuilder) (*containers.CreateHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateContainer", ctx, containerName, opts) + ret0, _ := ret[0].(*containers.CreateHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateContainer indicates an expected call of CreateContainer. +func (mr *MockSwiftContainerClientMockRecorder) CreateContainer(ctx, containerName, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateContainer", reflect.TypeOf((*MockSwiftContainerClient)(nil).CreateContainer), ctx, containerName, opts) +} + +// DeleteContainer mocks base method. +func (m *MockSwiftContainerClient) DeleteContainer(ctx context.Context, containerName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteContainer", ctx, containerName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteContainer indicates an expected call of DeleteContainer. +func (mr *MockSwiftContainerClientMockRecorder) DeleteContainer(ctx, containerName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteContainer", reflect.TypeOf((*MockSwiftContainerClient)(nil).DeleteContainer), ctx, containerName) +} + +// GetContainer mocks base method. +func (m *MockSwiftContainerClient) GetContainer(ctx context.Context, containerName string, opts containers.GetOptsBuilder) (*containers.GetHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContainer", ctx, containerName, opts) + ret0, _ := ret[0].(*containers.GetHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContainer indicates an expected call of GetContainer. +func (mr *MockSwiftContainerClientMockRecorder) GetContainer(ctx, containerName, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainer", reflect.TypeOf((*MockSwiftContainerClient)(nil).GetContainer), ctx, containerName, opts) +} + +// GetContainerMetadata mocks base method. +func (m *MockSwiftContainerClient) GetContainerMetadata(ctx context.Context, containerName string) (map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContainerMetadata", ctx, containerName) + ret0, _ := ret[0].(map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContainerMetadata indicates an expected call of GetContainerMetadata. +func (mr *MockSwiftContainerClientMockRecorder) GetContainerMetadata(ctx, containerName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerMetadata", reflect.TypeOf((*MockSwiftContainerClient)(nil).GetContainerMetadata), ctx, containerName) +} + +// ListContainers mocks base method. +func (m *MockSwiftContainerClient) ListContainers(ctx context.Context, listOpts containers.ListOptsBuilder) iter.Seq2[*containers.Container, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListContainers", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*containers.Container, error]) + return ret0 +} + +// ListContainers indicates an expected call of ListContainers. +func (mr *MockSwiftContainerClientMockRecorder) ListContainers(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListContainers", reflect.TypeOf((*MockSwiftContainerClient)(nil).ListContainers), ctx, listOpts) +} + +// UpdateContainer mocks base method. +func (m *MockSwiftContainerClient) UpdateContainer(ctx context.Context, containerName string, opts containers.UpdateOptsBuilder) (*containers.UpdateHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateContainer", ctx, containerName, opts) + ret0, _ := ret[0].(*containers.UpdateHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateContainer indicates an expected call of UpdateContainer. +func (mr *MockSwiftContainerClientMockRecorder) UpdateContainer(ctx, containerName, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateContainer", reflect.TypeOf((*MockSwiftContainerClient)(nil).UpdateContainer), ctx, containerName, opts) +} diff --git a/internal/osclients/swiftcontainer.go b/internal/osclients/swiftcontainer.go new file mode 100644 index 000000000..74703742e --- /dev/null +++ b/internal/osclients/swiftcontainer.go @@ -0,0 +1,121 @@ +/* +Copyright The ORC Authors. + +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 osclients + +import ( + "context" + "fmt" + "iter" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +// SwiftContainerClient is an interface for interacting with OpenStack Swift containers. +type SwiftContainerClient interface { + ListContainers(ctx context.Context, listOpts containers.ListOptsBuilder) iter.Seq2[*containers.Container, error] + CreateContainer(ctx context.Context, containerName string, opts containers.CreateOptsBuilder) (*containers.CreateHeader, error) + GetContainer(ctx context.Context, containerName string, opts containers.GetOptsBuilder) (*containers.GetHeader, error) + GetContainerMetadata(ctx context.Context, containerName string) (map[string]string, error) + DeleteContainer(ctx context.Context, containerName string) error + UpdateContainer(ctx context.Context, containerName string, opts containers.UpdateOptsBuilder) (*containers.UpdateHeader, error) +} + +type swiftContainerClient struct{ client *gophercloud.ServiceClient } + +// NewSwiftContainerClient returns a new OpenStack Swift object storage client. +func NewSwiftContainerClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (SwiftContainerClient, error) { + client, err := openstack.NewObjectStorageV1(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create swiftcontainer service client: %v", err) + } + + return NewSwiftContainerClientFromServiceClient(client), nil +} + +// NewSwiftContainerClientFromServiceClient returns a SwiftContainerClient wrapping +// the given gophercloud ServiceClient. This is primarily useful for testing. +func NewSwiftContainerClientFromServiceClient(client *gophercloud.ServiceClient) SwiftContainerClient { + return &swiftContainerClient{client} +} + +func (c swiftContainerClient) ListContainers(ctx context.Context, listOpts containers.ListOptsBuilder) iter.Seq2[*containers.Container, error] { + pager := containers.List(c.client, listOpts) + return func(yield func(*containers.Container, error) bool) { + _ = pager.EachPage(ctx, yieldPage(containers.ExtractInfo, yield)) + } +} + +func (c swiftContainerClient) CreateContainer(ctx context.Context, containerName string, opts containers.CreateOptsBuilder) (*containers.CreateHeader, error) { + return containers.Create(ctx, c.client, containerName, opts).Extract() +} + +func (c swiftContainerClient) GetContainer(ctx context.Context, containerName string, opts containers.GetOptsBuilder) (*containers.GetHeader, error) { + return containers.Get(ctx, c.client, containerName, opts).Extract() +} + +func (c swiftContainerClient) GetContainerMetadata(ctx context.Context, containerName string) (map[string]string, error) { + return containers.Get(ctx, c.client, containerName, nil).ExtractMetadata() +} + +func (c swiftContainerClient) DeleteContainer(ctx context.Context, containerName string) error { + _, err := containers.Delete(ctx, c.client, containerName).Extract() + return err +} + +func (c swiftContainerClient) UpdateContainer(ctx context.Context, containerName string, opts containers.UpdateOptsBuilder) (*containers.UpdateHeader, error) { + return containers.Update(ctx, c.client, containerName, opts).Extract() +} + +type swiftContainerErrorClient struct{ error } + +// NewSwiftContainerErrorClient returns a SwiftContainerClient in which every method returns the given error. +func NewSwiftContainerErrorClient(e error) SwiftContainerClient { + return swiftContainerErrorClient{e} +} + +func (e swiftContainerErrorClient) ListContainers(_ context.Context, _ containers.ListOptsBuilder) iter.Seq2[*containers.Container, error] { + return func(yield func(*containers.Container, error) bool) { + yield(nil, e.error) + } +} + +func (e swiftContainerErrorClient) CreateContainer(_ context.Context, _ string, _ containers.CreateOptsBuilder) (*containers.CreateHeader, error) { + return nil, e.error +} + +func (e swiftContainerErrorClient) GetContainer(_ context.Context, _ string, _ containers.GetOptsBuilder) (*containers.GetHeader, error) { + return nil, e.error +} + +func (e swiftContainerErrorClient) GetContainerMetadata(_ context.Context, _ string) (map[string]string, error) { + return nil, e.error +} + +func (e swiftContainerErrorClient) DeleteContainer(_ context.Context, _ string) error { + return e.error +} + +func (e swiftContainerErrorClient) UpdateContainer(_ context.Context, _ string, _ containers.UpdateOptsBuilder) (*containers.UpdateHeader, error) { + return nil, e.error +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 8ea474b64..9edacf2dc 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -50,6 +50,7 @@ type MockScopeFactory struct { VolumeClient *mock.MockVolumeClient VolumeTypeClient *mock.MockVolumeTypeClient ShareNetworkClient *mock.MockShareNetworkClient + SwiftContainerClient *mock.MockSwiftContainerClient clientScopeCreateError error } @@ -69,6 +70,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { serviceClient := mock.NewMockServiceClient(mockCtrl) userClient := mock.NewMockUserClient(mockCtrl) sharenetworkClient := mock.NewMockShareNetworkClient(mockCtrl) + swiftcontainerClient := mock.NewMockSwiftContainerClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) @@ -86,6 +88,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { RoleClient: roleClient, ServiceClient: serviceClient, ShareNetworkClient: sharenetworkClient, + SwiftContainerClient: swiftcontainerClient, UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, @@ -147,6 +150,10 @@ func (f *MockScopeFactory) NewShareNetworkClient() (osclients.ShareNetworkClient return f.ShareNetworkClient, nil } +func (f *MockScopeFactory) NewSwiftContainerClient() (osclients.SwiftContainerClient, error) { + return f.SwiftContainerClient, nil +} + func (f *MockScopeFactory) NewKeyPairClient() (osclients.KeyPairClient, error) { return f.KeyPairClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index 1606e18a1..36ca8ab9c 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -189,6 +189,10 @@ func (s *providerScope) NewShareNetworkClient() (clients.ShareNetworkClient, err return clients.NewShareNetworkClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewSwiftContainerClient() (clients.SwiftContainerClient, error) { + return clients.NewSwiftContainerClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewKeyPairClient() (clients.KeyPairClient, error) { return clients.NewKeyPairClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 0b02b79bd..3ceae1a99 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -61,6 +61,7 @@ type Scope interface { NewRoleClient() (osclients.RoleClient, error) NewServiceClient() (osclients.ServiceClient, error) NewShareNetworkClient() (osclients.ShareNetworkClient, error) + NewSwiftContainerClient() (osclients.SwiftContainerClient, error) NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 71fc135ed..264580b0b 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -23,6 +23,7 @@ testDirs: - ./internal/controllers/service/tests/ - ./internal/controllers/sharenetwork/tests/ - ./internal/controllers/subnet/tests/ +- ./internal/controllers/swiftcontainer/tests/ - ./internal/controllers/trunk/tests/ - ./internal/controllers/user/tests/ - ./internal/controllers/volume/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainer.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainer.go new file mode 100644 index 000000000..e82fb1b86 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainer.go @@ -0,0 +1,281 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SwiftContainerApplyConfiguration represents a declarative configuration of the SwiftContainer type for use +// with apply. +type SwiftContainerApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *SwiftContainerSpecApplyConfiguration `json:"spec,omitempty"` + Status *SwiftContainerStatusApplyConfiguration `json:"status,omitempty"` +} + +// SwiftContainer constructs a declarative configuration of the SwiftContainer type for use with +// apply. +func SwiftContainer(name, namespace string) *SwiftContainerApplyConfiguration { + b := &SwiftContainerApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("SwiftContainer") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractSwiftContainer extracts the applied configuration owned by fieldManager from +// swiftContainer. If no managedFields are found in swiftContainer for fieldManager, a +// SwiftContainerApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// swiftContainer must be a unmodified SwiftContainer API object that was retrieved from the Kubernetes API. +// ExtractSwiftContainer provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractSwiftContainer(swiftContainer *apiv1alpha1.SwiftContainer, fieldManager string) (*SwiftContainerApplyConfiguration, error) { + return extractSwiftContainer(swiftContainer, fieldManager, "") +} + +// ExtractSwiftContainerStatus is the same as ExtractSwiftContainer except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractSwiftContainerStatus(swiftContainer *apiv1alpha1.SwiftContainer, fieldManager string) (*SwiftContainerApplyConfiguration, error) { + return extractSwiftContainer(swiftContainer, fieldManager, "status") +} + +func extractSwiftContainer(swiftContainer *apiv1alpha1.SwiftContainer, fieldManager string, subresource string) (*SwiftContainerApplyConfiguration, error) { + b := &SwiftContainerApplyConfiguration{} + err := managedfields.ExtractInto(swiftContainer, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainer"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(swiftContainer.Name) + b.WithNamespace(swiftContainer.Namespace) + + b.WithKind("SwiftContainer") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b SwiftContainerApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithKind(value string) *SwiftContainerApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithAPIVersion(value string) *SwiftContainerApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithName(value string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithGenerateName(value string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithNamespace(value string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithUID(value types.UID) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithResourceVersion(value string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithGeneration(value int64) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *SwiftContainerApplyConfiguration) WithLabels(entries map[string]string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *SwiftContainerApplyConfiguration) WithAnnotations(entries map[string]string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *SwiftContainerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *SwiftContainerApplyConfiguration) WithFinalizers(values ...string) *SwiftContainerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *SwiftContainerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithSpec(value *SwiftContainerSpecApplyConfiguration) *SwiftContainerApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *SwiftContainerApplyConfiguration) WithStatus(value *SwiftContainerStatusApplyConfiguration) *SwiftContainerApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *SwiftContainerApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *SwiftContainerApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *SwiftContainerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *SwiftContainerApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerfilter.go new file mode 100644 index 000000000..cda26dcfa --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerfilter.go @@ -0,0 +1,52 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SwiftContainerFilterApplyConfiguration represents a declarative configuration of the SwiftContainerFilter type for use +// with apply. +type SwiftContainerFilterApplyConfiguration struct { + Name *apiv1alpha1.SwiftContainerName `json:"name,omitempty"` + Prefix *string `json:"prefix,omitempty"` +} + +// SwiftContainerFilterApplyConfiguration constructs a declarative configuration of the SwiftContainerFilter type for use with +// apply. +func SwiftContainerFilter() *SwiftContainerFilterApplyConfiguration { + return &SwiftContainerFilterApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SwiftContainerFilterApplyConfiguration) WithName(value apiv1alpha1.SwiftContainerName) *SwiftContainerFilterApplyConfiguration { + b.Name = &value + return b +} + +// WithPrefix sets the Prefix field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Prefix field is set to the value of the last call. +func (b *SwiftContainerFilterApplyConfiguration) WithPrefix(value string) *SwiftContainerFilterApplyConfiguration { + b.Prefix = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerimport.go new file mode 100644 index 000000000..3e5d69df5 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerimport.go @@ -0,0 +1,52 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SwiftContainerImportApplyConfiguration represents a declarative configuration of the SwiftContainerImport type for use +// with apply. +type SwiftContainerImportApplyConfiguration struct { + Name *apiv1alpha1.SwiftContainerName `json:"name,omitempty"` + Filter *SwiftContainerFilterApplyConfiguration `json:"filter,omitempty"` +} + +// SwiftContainerImportApplyConfiguration constructs a declarative configuration of the SwiftContainerImport type for use with +// apply. +func SwiftContainerImport() *SwiftContainerImportApplyConfiguration { + return &SwiftContainerImportApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SwiftContainerImportApplyConfiguration) WithName(value apiv1alpha1.SwiftContainerName) *SwiftContainerImportApplyConfiguration { + b.Name = &value + return b +} + +// WithFilter sets the Filter field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Filter field is set to the value of the last call. +func (b *SwiftContainerImportApplyConfiguration) WithFilter(value *SwiftContainerFilterApplyConfiguration) *SwiftContainerImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadata.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadata.go new file mode 100644 index 000000000..ea014f10f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadata.go @@ -0,0 +1,48 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// SwiftContainerMetadataApplyConfiguration represents a declarative configuration of the SwiftContainerMetadata type for use +// with apply. +type SwiftContainerMetadataApplyConfiguration struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// SwiftContainerMetadataApplyConfiguration constructs a declarative configuration of the SwiftContainerMetadata type for use with +// apply. +func SwiftContainerMetadata() *SwiftContainerMetadataApplyConfiguration { + return &SwiftContainerMetadataApplyConfiguration{} +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *SwiftContainerMetadataApplyConfiguration) WithKey(value string) *SwiftContainerMetadataApplyConfiguration { + b.Key = &value + return b +} + +// WithValue sets the Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Value field is set to the value of the last call. +func (b *SwiftContainerMetadataApplyConfiguration) WithValue(value string) *SwiftContainerMetadataApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadatastatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadatastatus.go new file mode 100644 index 000000000..65f60268b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainermetadatastatus.go @@ -0,0 +1,48 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// SwiftContainerMetadataStatusApplyConfiguration represents a declarative configuration of the SwiftContainerMetadataStatus type for use +// with apply. +type SwiftContainerMetadataStatusApplyConfiguration struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// SwiftContainerMetadataStatusApplyConfiguration constructs a declarative configuration of the SwiftContainerMetadataStatus type for use with +// apply. +func SwiftContainerMetadataStatus() *SwiftContainerMetadataStatusApplyConfiguration { + return &SwiftContainerMetadataStatusApplyConfiguration{} +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *SwiftContainerMetadataStatusApplyConfiguration) WithKey(value string) *SwiftContainerMetadataStatusApplyConfiguration { + b.Key = &value + return b +} + +// WithValue sets the Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Value field is set to the value of the last call. +func (b *SwiftContainerMetadataStatusApplyConfiguration) WithValue(value string) *SwiftContainerMetadataStatusApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcespec.go new file mode 100644 index 000000000..29dc73e17 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcespec.go @@ -0,0 +1,84 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SwiftContainerResourceSpecApplyConfiguration represents a declarative configuration of the SwiftContainerResourceSpec type for use +// with apply. +type SwiftContainerResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.SwiftContainerName `json:"name,omitempty"` + Metadata []SwiftContainerMetadataApplyConfiguration `json:"metadata,omitempty"` + ContainerRead *string `json:"containerRead,omitempty"` + ContainerWrite *string `json:"containerWrite,omitempty"` + StoragePolicy *string `json:"storagePolicy,omitempty"` +} + +// SwiftContainerResourceSpecApplyConfiguration constructs a declarative configuration of the SwiftContainerResourceSpec type for use with +// apply. +func SwiftContainerResourceSpec() *SwiftContainerResourceSpecApplyConfiguration { + return &SwiftContainerResourceSpecApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SwiftContainerResourceSpecApplyConfiguration) WithName(value apiv1alpha1.SwiftContainerName) *SwiftContainerResourceSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithMetadata adds the given value to the Metadata field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Metadata field. +func (b *SwiftContainerResourceSpecApplyConfiguration) WithMetadata(values ...*SwiftContainerMetadataApplyConfiguration) *SwiftContainerResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMetadata") + } + b.Metadata = append(b.Metadata, *values[i]) + } + return b +} + +// WithContainerRead sets the ContainerRead field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContainerRead field is set to the value of the last call. +func (b *SwiftContainerResourceSpecApplyConfiguration) WithContainerRead(value string) *SwiftContainerResourceSpecApplyConfiguration { + b.ContainerRead = &value + return b +} + +// WithContainerWrite sets the ContainerWrite field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContainerWrite field is set to the value of the last call. +func (b *SwiftContainerResourceSpecApplyConfiguration) WithContainerWrite(value string) *SwiftContainerResourceSpecApplyConfiguration { + b.ContainerWrite = &value + return b +} + +// WithStoragePolicy sets the StoragePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the StoragePolicy field is set to the value of the last call. +func (b *SwiftContainerResourceSpecApplyConfiguration) WithStoragePolicy(value string) *SwiftContainerResourceSpecApplyConfiguration { + b.StoragePolicy = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcestatus.go new file mode 100644 index 000000000..9a0ccdc1f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerresourcestatus.go @@ -0,0 +1,107 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// SwiftContainerResourceStatusApplyConfiguration represents a declarative configuration of the SwiftContainerResourceStatus type for use +// with apply. +type SwiftContainerResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + BytesUsed *int64 `json:"bytesUsed,omitempty"` + ObjectCount *int64 `json:"objectCount,omitempty"` + Metadata []SwiftContainerMetadataStatusApplyConfiguration `json:"metadata,omitempty"` + ContainerRead *string `json:"containerRead,omitempty"` + ContainerWrite *string `json:"containerWrite,omitempty"` + StoragePolicy *string `json:"storagePolicy,omitempty"` + Versions *string `json:"versions,omitempty"` +} + +// SwiftContainerResourceStatusApplyConfiguration constructs a declarative configuration of the SwiftContainerResourceStatus type for use with +// apply. +func SwiftContainerResourceStatus() *SwiftContainerResourceStatusApplyConfiguration { + return &SwiftContainerResourceStatusApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithName(value string) *SwiftContainerResourceStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithBytesUsed sets the BytesUsed field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the BytesUsed field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithBytesUsed(value int64) *SwiftContainerResourceStatusApplyConfiguration { + b.BytesUsed = &value + return b +} + +// WithObjectCount sets the ObjectCount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObjectCount field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithObjectCount(value int64) *SwiftContainerResourceStatusApplyConfiguration { + b.ObjectCount = &value + return b +} + +// WithMetadata adds the given value to the Metadata field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Metadata field. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithMetadata(values ...*SwiftContainerMetadataStatusApplyConfiguration) *SwiftContainerResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMetadata") + } + b.Metadata = append(b.Metadata, *values[i]) + } + return b +} + +// WithContainerRead sets the ContainerRead field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContainerRead field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithContainerRead(value string) *SwiftContainerResourceStatusApplyConfiguration { + b.ContainerRead = &value + return b +} + +// WithContainerWrite sets the ContainerWrite field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContainerWrite field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithContainerWrite(value string) *SwiftContainerResourceStatusApplyConfiguration { + b.ContainerWrite = &value + return b +} + +// WithStoragePolicy sets the StoragePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the StoragePolicy field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithStoragePolicy(value string) *SwiftContainerResourceStatusApplyConfiguration { + b.StoragePolicy = &value + return b +} + +// WithVersions sets the Versions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Versions field is set to the value of the last call. +func (b *SwiftContainerResourceStatusApplyConfiguration) WithVersions(value string) *SwiftContainerResourceStatusApplyConfiguration { + b.Versions = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerspec.go new file mode 100644 index 000000000..077aba29e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerspec.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SwiftContainerSpecApplyConfiguration represents a declarative configuration of the SwiftContainerSpec type for use +// with apply. +type SwiftContainerSpecApplyConfiguration struct { + Import *SwiftContainerImportApplyConfiguration `json:"import,omitempty"` + Resource *SwiftContainerResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// SwiftContainerSpecApplyConfiguration constructs a declarative configuration of the SwiftContainerSpec type for use with +// apply. +func SwiftContainerSpec() *SwiftContainerSpecApplyConfiguration { + return &SwiftContainerSpecApplyConfiguration{} +} + +// WithImport sets the Import field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Import field is set to the value of the last call. +func (b *SwiftContainerSpecApplyConfiguration) WithImport(value *SwiftContainerImportApplyConfiguration) *SwiftContainerSpecApplyConfiguration { + b.Import = value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *SwiftContainerSpecApplyConfiguration) WithResource(value *SwiftContainerResourceSpecApplyConfiguration) *SwiftContainerSpecApplyConfiguration { + b.Resource = value + return b +} + +// WithManagementPolicy sets the ManagementPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagementPolicy field is set to the value of the last call. +func (b *SwiftContainerSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *SwiftContainerSpecApplyConfiguration { + b.ManagementPolicy = &value + return b +} + +// WithManagedOptions sets the ManagedOptions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagedOptions field is set to the value of the last call. +func (b *SwiftContainerSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *SwiftContainerSpecApplyConfiguration { + b.ManagedOptions = value + return b +} + +// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CloudCredentialsRef field is set to the value of the last call. +func (b *SwiftContainerSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *SwiftContainerSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerstatus.go new file mode 100644 index 000000000..863c8da49 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/swiftcontainerstatus.go @@ -0,0 +1,66 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SwiftContainerStatusApplyConfiguration represents a declarative configuration of the SwiftContainerStatus type for use +// with apply. +type SwiftContainerStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *SwiftContainerResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// SwiftContainerStatusApplyConfiguration constructs a declarative configuration of the SwiftContainerStatus type for use with +// apply. +func SwiftContainerStatus() *SwiftContainerStatusApplyConfiguration { + return &SwiftContainerStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *SwiftContainerStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *SwiftContainerStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithID sets the ID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ID field is set to the value of the last call. +func (b *SwiftContainerStatusApplyConfiguration) WithID(value string) *SwiftContainerStatusApplyConfiguration { + b.ID = &value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *SwiftContainerStatusApplyConfiguration) WithResource(value *SwiftContainerResourceStatusApplyConfiguration) *SwiftContainerStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 10461ed9c..c1c12b3bd 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3531,6 +3531,155 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainer + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerFilter + map: + fields: + - name: name + type: + scalar: string + - name: prefix + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerFilter + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerMetadata + map: + fields: + - name: key + type: + scalar: string + - name: value + type: + scalar: string + default: "" +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerMetadataStatus + map: + fields: + - name: key + type: + scalar: string + - name: value + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerResourceSpec + map: + fields: + - name: containerRead + type: + scalar: string + - name: containerWrite + type: + scalar: string + - name: metadata + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerMetadata + elementRelationship: associative + keys: + - key + - name: name + type: + scalar: string + - name: storagePolicy + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerResourceStatus + map: + fields: + - name: bytesUsed + type: + scalar: numeric + - name: containerRead + type: + scalar: string + - name: containerWrite + type: + scalar: string + - name: metadata + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerMetadataStatus + elementRelationship: associative + keys: + - key + - name: name + type: + scalar: string + - name: objectCount + type: + scalar: numeric + - name: storagePolicy + type: + scalar: string + - name: versions + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerSpec + map: + fields: + - name: cloudCredentialsRef + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference + default: {} + - name: import + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerImport + - name: managedOptions + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions + - name: managementPolicy + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: id + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SwiftContainerResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Trunk map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5d8f68cd9..e5b6d854f 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -410,6 +410,24 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SubnetSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetStatus"): return &apiv1alpha1.SubnetStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainer"): + return &apiv1alpha1.SwiftContainerApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerFilter"): + return &apiv1alpha1.SwiftContainerFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerImport"): + return &apiv1alpha1.SwiftContainerImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerMetadata"): + return &apiv1alpha1.SwiftContainerMetadataApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerMetadataStatus"): + return &apiv1alpha1.SwiftContainerMetadataStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerResourceSpec"): + return &apiv1alpha1.SwiftContainerResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerResourceStatus"): + return &apiv1alpha1.SwiftContainerResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerSpec"): + return &apiv1alpha1.SwiftContainerSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SwiftContainerStatus"): + return &apiv1alpha1.SwiftContainerStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Trunk"): return &apiv1alpha1.TrunkApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("TrunkFilter"): diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go index d5c517b1e..2a85aee7c 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -49,6 +49,7 @@ type OpenstackV1alpha1Interface interface { ServicesGetter ShareNetworksGetter SubnetsGetter + SwiftContainersGetter TrunksGetter UsersGetter VolumesGetter @@ -144,6 +145,10 @@ func (c *OpenstackV1alpha1Client) Subnets(namespace string) SubnetInterface { return newSubnets(c, namespace) } +func (c *OpenstackV1alpha1Client) SwiftContainers(namespace string) SwiftContainerInterface { + return newSwiftContainers(c, namespace) +} + func (c *OpenstackV1alpha1Client) Trunks(namespace string) TrunkInterface { return newTrunks(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go index f5dcb5da4..bbde5f0d1 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go @@ -112,6 +112,10 @@ func (c *FakeOpenstackV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterfa return newFakeSubnets(c, namespace) } +func (c *FakeOpenstackV1alpha1) SwiftContainers(namespace string) v1alpha1.SwiftContainerInterface { + return newFakeSwiftContainers(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Trunks(namespace string) v1alpha1.TrunkInterface { return newFakeTrunks(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_swiftcontainer.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_swiftcontainer.go new file mode 100644 index 000000000..d709c4de3 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_swiftcontainer.go @@ -0,0 +1,53 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeSwiftContainers implements SwiftContainerInterface +type fakeSwiftContainers struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.SwiftContainer, *v1alpha1.SwiftContainerList, *apiv1alpha1.SwiftContainerApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeSwiftContainers(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.SwiftContainerInterface { + return &fakeSwiftContainers{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.SwiftContainer, *v1alpha1.SwiftContainerList, *apiv1alpha1.SwiftContainerApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("swiftcontainers"), + v1alpha1.SchemeGroupVersion.WithKind("SwiftContainer"), + func() *v1alpha1.SwiftContainer { return &v1alpha1.SwiftContainer{} }, + func() *v1alpha1.SwiftContainerList { return &v1alpha1.SwiftContainerList{} }, + func(dst, src *v1alpha1.SwiftContainerList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.SwiftContainerList) []*v1alpha1.SwiftContainer { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.SwiftContainerList, items []*v1alpha1.SwiftContainer) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go index e13858a9c..d50a4cdcc 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -60,6 +60,8 @@ type ShareNetworkExpansion interface{} type SubnetExpansion interface{} +type SwiftContainerExpansion interface{} + type TrunkExpansion interface{} type UserExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/swiftcontainer.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/swiftcontainer.go new file mode 100644 index 000000000..c97e80553 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/swiftcontainer.go @@ -0,0 +1,74 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// SwiftContainersGetter has a method to return a SwiftContainerInterface. +// A group's client should implement this interface. +type SwiftContainersGetter interface { + SwiftContainers(namespace string) SwiftContainerInterface +} + +// SwiftContainerInterface has methods to work with SwiftContainer resources. +type SwiftContainerInterface interface { + Create(ctx context.Context, swiftContainer *apiv1alpha1.SwiftContainer, opts v1.CreateOptions) (*apiv1alpha1.SwiftContainer, error) + Update(ctx context.Context, swiftContainer *apiv1alpha1.SwiftContainer, opts v1.UpdateOptions) (*apiv1alpha1.SwiftContainer, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, swiftContainer *apiv1alpha1.SwiftContainer, opts v1.UpdateOptions) (*apiv1alpha1.SwiftContainer, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*apiv1alpha1.SwiftContainer, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.SwiftContainerList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apiv1alpha1.SwiftContainer, err error) + Apply(ctx context.Context, swiftContainer *applyconfigurationapiv1alpha1.SwiftContainerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.SwiftContainer, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, swiftContainer *applyconfigurationapiv1alpha1.SwiftContainerApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.SwiftContainer, err error) + SwiftContainerExpansion +} + +// swiftContainers implements SwiftContainerInterface +type swiftContainers struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.SwiftContainer, *apiv1alpha1.SwiftContainerList, *applyconfigurationapiv1alpha1.SwiftContainerApplyConfiguration] +} + +// newSwiftContainers returns a SwiftContainers +func newSwiftContainers(c *OpenstackV1alpha1Client, namespace string) *swiftContainers { + return &swiftContainers{ + gentype.NewClientWithListAndApply[*apiv1alpha1.SwiftContainer, *apiv1alpha1.SwiftContainerList, *applyconfigurationapiv1alpha1.SwiftContainerApplyConfiguration]( + "swiftcontainers", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.SwiftContainer { return &apiv1alpha1.SwiftContainer{} }, + func() *apiv1alpha1.SwiftContainerList { return &apiv1alpha1.SwiftContainerList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 2e9f92392..dfefed1f7 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -66,6 +66,8 @@ type Interface interface { ShareNetworks() ShareNetworkInformer // Subnets returns a SubnetInformer. Subnets() SubnetInformer + // SwiftContainers returns a SwiftContainerInformer. + SwiftContainers() SwiftContainerInformer // Trunks returns a TrunkInformer. Trunks() TrunkInformer // Users returns a UserInformer. @@ -192,6 +194,11 @@ func (v *version) Subnets() SubnetInformer { return &subnetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// SwiftContainers returns a SwiftContainerInformer. +func (v *version) SwiftContainers() SwiftContainerInformer { + return &swiftContainerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Trunks returns a TrunkInformer. func (v *version) Trunks() TrunkInformer { return &trunkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/swiftcontainer.go b/pkg/clients/informers/externalversions/api/v1alpha1/swiftcontainer.go new file mode 100644 index 000000000..683f61671 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/swiftcontainer.go @@ -0,0 +1,102 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset" + internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SwiftContainerInformer provides access to a shared informer and lister for +// SwiftContainers. +type SwiftContainerInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.SwiftContainerLister +} + +type swiftContainerInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSwiftContainerInformer constructs a new informer for SwiftContainer type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSwiftContainerInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSwiftContainerInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSwiftContainerInformer constructs a new informer for SwiftContainer type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSwiftContainerInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SwiftContainers(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SwiftContainers(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SwiftContainers(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SwiftContainers(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.SwiftContainer{}, + resyncPeriod, + indexers, + ) +} + +func (f *swiftContainerInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSwiftContainerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *swiftContainerInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.SwiftContainer{}, f.defaultInformer) +} + +func (f *swiftContainerInformer) Lister() apiv1alpha1.SwiftContainerLister { + return apiv1alpha1.NewSwiftContainerLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index fb3637f1e..ed23144ef 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -95,6 +95,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().ShareNetworks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subnets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("swiftcontainers"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().SwiftContainers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("trunks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Trunks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("users"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 4d7043581..6796bed11 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -186,6 +186,14 @@ type SubnetListerExpansion interface{} // SubnetNamespaceLister. type SubnetNamespaceListerExpansion interface{} +// SwiftContainerListerExpansion allows custom methods to be added to +// SwiftContainerLister. +type SwiftContainerListerExpansion interface{} + +// SwiftContainerNamespaceListerExpansion allows custom methods to be added to +// SwiftContainerNamespaceLister. +type SwiftContainerNamespaceListerExpansion interface{} + // TrunkListerExpansion allows custom methods to be added to // TrunkLister. type TrunkListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/swiftcontainer.go b/pkg/clients/listers/api/v1alpha1/swiftcontainer.go new file mode 100644 index 000000000..a452d1774 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/swiftcontainer.go @@ -0,0 +1,70 @@ +/* +Copyright The ORC Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// SwiftContainerLister helps list SwiftContainers. +// All objects returned here must be treated as read-only. +type SwiftContainerLister interface { + // List lists all SwiftContainers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.SwiftContainer, err error) + // SwiftContainers returns an object that can list and get SwiftContainers. + SwiftContainers(namespace string) SwiftContainerNamespaceLister + SwiftContainerListerExpansion +} + +// swiftContainerLister implements the SwiftContainerLister interface. +type swiftContainerLister struct { + listers.ResourceIndexer[*apiv1alpha1.SwiftContainer] +} + +// NewSwiftContainerLister returns a new SwiftContainerLister. +func NewSwiftContainerLister(indexer cache.Indexer) SwiftContainerLister { + return &swiftContainerLister{listers.New[*apiv1alpha1.SwiftContainer](indexer, apiv1alpha1.Resource("swiftcontainer"))} +} + +// SwiftContainers returns an object that can list and get SwiftContainers. +func (s *swiftContainerLister) SwiftContainers(namespace string) SwiftContainerNamespaceLister { + return swiftContainerNamespaceLister{listers.NewNamespaced[*apiv1alpha1.SwiftContainer](s.ResourceIndexer, namespace)} +} + +// SwiftContainerNamespaceLister helps list and get SwiftContainers. +// All objects returned here must be treated as read-only. +type SwiftContainerNamespaceLister interface { + // List lists all SwiftContainers in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.SwiftContainer, err error) + // Get retrieves the SwiftContainer from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.SwiftContainer, error) + SwiftContainerNamespaceListerExpansion +} + +// swiftContainerNamespaceLister implements the SwiftContainerNamespaceLister +// interface. +type swiftContainerNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.SwiftContainer] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index de769a2d0..405ce8a9e 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -31,6 +31,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Service](#service) - [ShareNetwork](#sharenetwork) - [Subnet](#subnet) +- [SwiftContainer](#swiftcontainer) - [Trunk](#trunk) - [User](#user) - [Volume](#volume) @@ -522,6 +523,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) +- [SwiftContainerSpec](#swiftcontainerspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) - [VolumeSpec](#volumespec) @@ -2244,6 +2246,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) +- [SwiftContainerSpec](#swiftcontainerspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) - [VolumeSpec](#volumespec) @@ -2284,6 +2287,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) +- [SwiftContainerSpec](#swiftcontainerspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) - [VolumeSpec](#volumespec) @@ -4601,6 +4605,200 @@ _Appears in:_ | `resource` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +#### SwiftContainer + + + +SwiftContainer is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `SwiftContainer` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[SwiftContainerSpec](#swiftcontainerspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[SwiftContainerStatus](#swiftcontainerstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### SwiftContainerFilter + + + +SwiftContainerFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [SwiftContainerImport](#swiftcontainerimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[SwiftContainerName](#swiftcontainername)_ | name of the existing resource | | MaxLength: 256
MinLength: 1
Pattern: `^[^/]+$`
Optional: \{\}
| +| `prefix` _string_ | prefix filters containers by name prefix. Only containers whose names
begin with this prefix will be considered. | | MaxLength: 256
MinLength: 1
Optional: \{\}
| + + +#### SwiftContainerImport + + + +SwiftContainerImport specifies an existing resource which will be imported +instead of creating a new one. Swift containers are identified by name +rather than UUID. + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [SwiftContainerSpec](#swiftcontainerspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[SwiftContainerName](#swiftcontainername)_ | name contains the name of an existing Swift container to import. Note
that when specifying an import by name, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | MaxLength: 256
MinLength: 1
Pattern: `^[^/]+$`
Optional: \{\}
| +| `filter` _[SwiftContainerFilter](#swiftcontainerfilter)_ | filter contains a resource query which is expected to return a single
result. The controller will continue to retry if filter returns no
results. If filter returns multiple results the controller will set an
error state and will not continue to retry. | | MinProperties: 1
Optional: \{\}
| + + +#### SwiftContainerMetadata + + + +SwiftContainerMetadata defines a key-value pair to be set as a Swift +container metadata header (X-Container-Meta-: ). + + + +_Appears in:_ +- [SwiftContainerResourceSpec](#swiftcontainerresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `key` _string_ | key is the key of the metadata item. It will be used as the suffix of
the X-Container-Meta-* header. | | MaxLength: 255
MinLength: 1
Required: \{\}
| +| `value` _string_ | value is the value of the metadata item. | | MaxLength: 255
Required: \{\}
| + + +#### SwiftContainerMetadataStatus + + + +SwiftContainerMetadataStatus represents an observed metadata key-value pair +on a Swift container. + + + +_Appears in:_ +- [SwiftContainerResourceStatus](#swiftcontainerresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `key` _string_ | key is the key of the metadata item. | | MaxLength: 255
MinLength: 1
Required: \{\}
| +| `value` _string_ | value is the value of the metadata item. | | MaxLength: 255
Optional: \{\}
| + + +#### SwiftContainerName + +_Underlying type:_ _string_ + +SwiftContainerName is the name of a Swift container. It must be between 1 +and 256 characters long and must not contain forward slashes. + +_Validation:_ +- MaxLength: 256 +- MinLength: 1 +- Pattern: `^[^/]+$` + +_Appears in:_ +- [SwiftContainerFilter](#swiftcontainerfilter) +- [SwiftContainerImport](#swiftcontainerimport) +- [SwiftContainerResourceSpec](#swiftcontainerresourcespec) + + + +#### SwiftContainerResourceSpec + + + +SwiftContainerResourceSpec contains the desired state of a Swift container. + + + +_Appears in:_ +- [SwiftContainerSpec](#swiftcontainerspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[SwiftContainerName](#swiftcontainername)_ | name will be the name of the created Swift container. If not specified,
the name of the ORC object will be used. The name must be unique within
the account and must not contain forward slashes. | | MaxLength: 256
MinLength: 1
Pattern: `^[^/]+$`
Optional: \{\}
| +| `metadata` _[SwiftContainerMetadata](#swiftcontainermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
Optional: \{\}
| +| `containerRead` _string_ | containerRead sets the X-Container-Read ACL header which defines who
can read objects in the container. Common values include ".r:*" for
public read access or a comma-separated list of account/container
combinations. | | MaxLength: 256
MinLength: 1
Optional: \{\}
| +| `containerWrite` _string_ | containerWrite sets the X-Container-Write ACL header which defines who
can write objects to the container. Common values include a
comma-separated list of account/container combinations. | | MaxLength: 256
MinLength: 1
Optional: \{\}
| +| `storagePolicy` _string_ | storagePolicy is the name of the storage policy to use for this
container. If not specified, the cluster's default storage policy will
be used. This field is immutable after creation. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| + + +#### SwiftContainerResourceStatus + + + +SwiftContainerResourceStatus represents the observed state of a Swift container. + + + +_Appears in:_ +- [SwiftContainerStatus](#swiftcontainerstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is the name of the Swift container. | | MaxLength: 256
Optional: \{\}
| +| `bytesUsed` _integer_ | bytesUsed is the total number of bytes stored in the container. | | Optional: \{\}
| +| `objectCount` _integer_ | objectCount is the number of objects stored in the container. | | Optional: \{\}
| +| `metadata` _[SwiftContainerMetadataStatus](#swiftcontainermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
Optional: \{\}
| +| `containerRead` _string_ | containerRead is the current X-Container-Read ACL, defining who can
read objects in the container. | | MaxLength: 1024
Optional: \{\}
| +| `containerWrite` _string_ | containerWrite is the current X-Container-Write ACL, defining who can
write objects to the container. | | MaxLength: 1024
Optional: \{\}
| +| `storagePolicy` _string_ | storagePolicy is the name of the storage policy assigned to the container. | | MaxLength: 1024
Optional: \{\}
| +| `versions` _string_ | versions is the container where object versions are stored, if versioning
is enabled on this container. | | MaxLength: 1024
Optional: \{\}
| + + +#### SwiftContainerSpec + + + +SwiftContainerSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [SwiftContainer](#swiftcontainer) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[SwiftContainerImport](#swiftcontainerimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[SwiftContainerResourceSpec](#swiftcontainerresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| + + +#### SwiftContainerStatus + + + +SwiftContainerStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [SwiftContainer](#swiftcontainer) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[SwiftContainerResourceStatus](#swiftcontainerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + #### Trunk