diff --git a/PROJECT b/PROJECT index 357849779..71ae4d233 100644 --- a/PROJECT +++ b/PROJECT @@ -104,6 +104,14 @@ resources: kind: Project path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: Region + 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..083117f8b 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ kubectl delete -f $ORC_RELEASE | network | | ◐ | ◐ | | port | | ◐ | ◐ | | project | | ◐ | ◐ | +| region | | | ◐ | | role | | ✔ | ✔ | | router | | ◐ | ◐ | | security group (incl. rule) | | ✔ | ✔ | diff --git a/api/v1alpha1/region_types.go b/api/v1alpha1/region_types.go new file mode 100644 index 000000000..f6df8e8b0 --- /dev/null +++ b/api/v1alpha1/region_types.go @@ -0,0 +1,58 @@ +/* +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 + +// RegionResourceSpec contains the desired state of the resource. +type RegionResourceSpec struct { + // name will be the ID of the Region. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" + Name OpenStackName `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` +} + +// RegionFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type RegionFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` +} + +// RegionResourceStatus represents the observed state of the resource. +type RegionResourceStatus struct { + // name is the ID for the resource. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Name string `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Description string `json:"description,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4e7d7e3c1..570258405 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3908,6 +3908,222 @@ func (in *ProviderPropertiesStatus) DeepCopy() *ProviderPropertiesStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Region) DeepCopyInto(out *Region) { + *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 Region. +func (in *Region) DeepCopy() *Region { + if in == nil { + return nil + } + out := new(Region) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Region) 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 *RegionFilter) DeepCopyInto(out *RegionFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionFilter. +func (in *RegionFilter) DeepCopy() *RegionFilter { + if in == nil { + return nil + } + out := new(RegionFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegionImport) DeepCopyInto(out *RegionImport) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(RegionFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionImport. +func (in *RegionImport) DeepCopy() *RegionImport { + if in == nil { + return nil + } + out := new(RegionImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegionList) DeepCopyInto(out *RegionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Region, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionList. +func (in *RegionList) DeepCopy() *RegionList { + if in == nil { + return nil + } + out := new(RegionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RegionList) 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 *RegionResourceSpec) DeepCopyInto(out *RegionResourceSpec) { + *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionResourceSpec. +func (in *RegionResourceSpec) DeepCopy() *RegionResourceSpec { + if in == nil { + return nil + } + out := new(RegionResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegionResourceStatus) DeepCopyInto(out *RegionResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionResourceStatus. +func (in *RegionResourceStatus) DeepCopy() *RegionResourceStatus { + if in == nil { + return nil + } + out := new(RegionResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegionSpec) DeepCopyInto(out *RegionSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(RegionImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(RegionResourceSpec) + (*in).DeepCopyInto(*out) + } + if in.ManagedOptions != nil { + in, out := &in.ManagedOptions, &out.ManagedOptions + *out = new(ManagedOptions) + **out = **in + } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } + out.CloudCredentialsRef = in.CloudCredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionSpec. +func (in *RegionSpec) DeepCopy() *RegionSpec { + if in == nil { + return nil + } + out := new(RegionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegionStatus) DeepCopyInto(out *RegionStatus) { + *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(RegionResourceStatus) + **out = **in + } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegionStatus. +func (in *RegionStatus) DeepCopy() *RegionStatus { + if in == nil { + return nil + } + out := new(RegionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Role) DeepCopyInto(out *Role) { *out = *in diff --git a/api/v1alpha1/zz_generated.region-resource.go b/api/v1alpha1/zz_generated.region-resource.go new file mode 100644 index 000000000..84a4f4cc2 --- /dev/null +++ b/api/v1alpha1/zz_generated.region-resource.go @@ -0,0 +1,194 @@ +// 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" +) + +// RegionImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type RegionImport struct { + // id contains the name of an existing resource. Note: This resource uses + // the resource name as the unique identifier, not a UUID. + // When specifying an import by ID, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` //nolint:kubeapilinter + + // 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 *RegionFilter `json:"filter,omitempty"` +} + +// RegionSpec 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 RegionSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *RegionImport `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 *RegionResourceSpec `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"` + + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + + // cloudCredentialsRef points to a secret containing OpenStack credentials + // +required + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` +} + +// RegionStatus defines the observed state of an ORC resource. +type RegionStatus 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 *RegionResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` +} + +var _ ObjectWithConditions = &Region{} + +func (i *Region) 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" + +// Region is the Schema for an ORC resource. +type Region 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 RegionSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status RegionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RegionList contains a list of Region. +type RegionList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of Region. + // +required + Items []Region `json:"items"` +} + +func (l *RegionList) GetItems() []Region { + return l.Items +} + +func init() { + SchemeBuilder.Register(&Region{}, &RegionList{}) +} + +func (i *Region) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &Region{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 6f759c2e5..3d0703e2f 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -40,6 +40,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/network" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/region" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/role" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/roleassignment" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/router" @@ -141,6 +142,7 @@ func main() { sharenetwork.New(scopeFactory), keypair.New(scopeFactory), group.New(scopeFactory), + region.New(scopeFactory), role.New(scopeFactory), roleassignment.New(scopeFactory), } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 29d2fa1b1..a11f50f6d 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -160,6 +160,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Region": schema_openstack_resource_controller_v2_api_v1alpha1_Region(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RegionFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionImport": schema_openstack_resource_controller_v2_api_v1alpha1_RegionImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionList": schema_openstack_resource_controller_v2_api_v1alpha1_RegionList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RegionResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RegionResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RegionSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RegionStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignment(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentFilter(ref), @@ -7415,6 +7423,330 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStat } } +func schema_openstack_resource_controller_v2_api_v1alpha1_Region(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Region 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.RegionSpec"), + }, + }, + "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.RegionStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionFilter 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: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionImport specifies an existing resource which will be imported instead of creating a new one", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id contains the name of an existing resource. Note: This resource uses the resource name as the unique identifier, not a UUID. When specifying an import by ID, 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.RegionFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionList contains a list of Region.", + 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 Region.", + 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.Region"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Region", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionResourceSpec contains the desired state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name will be the ID of the Region.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionResourceStatus represents the observed state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the ID for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionSpec 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.RegionImport"), + }, + }, + "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.RegionResourceSpec"), + }, + }, + "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"), + }, + }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "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.RegionImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RegionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RegionStatus 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.RegionResourceStatus"), + }, + }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RegionResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index c5d6bc042..6bd030ca8 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -189,6 +189,12 @@ var resources []templateFields = []templateFields{ { Name: "ApplicationCredential", }, + { + Name: "Region", + // Region has no "name" field, but the ID is used as the name + UsesNameAsID: true, + IsNotNamed: true, + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_regions.yaml b/config/crd/bases/openstack.k-orc.cloud_regions.yaml new file mode 100644 index 000000000..07a12b35e --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_regions.yaml @@ -0,0 +1,307 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: regions.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Region + listKind: RegionList + plural: regions + singular: region + 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: Region 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: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + type: object + id: + description: |- + id contains the name of an existing resource. Note: This resource uses + the resource name as the unique identifier, not a UUID. + When specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + maxLength: 1024 + type: string + 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: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + name: + description: name will be the ID of the Region. + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + required: + - name + type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string + 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 + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + name: + description: name is the ID for the resource. + 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 9c133d11c..b9b02f3fc 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -15,6 +15,7 @@ resources: - bases/openstack.k-orc.cloud_networks.yaml - bases/openstack.k-orc.cloud_ports.yaml - bases/openstack.k-orc.cloud_projects.yaml +- bases/openstack.k-orc.cloud_regions.yaml - bases/openstack.k-orc.cloud_roles.yaml - bases/openstack.k-orc.cloud_roleassignments.yaml - bases/openstack.k-orc.cloud_routers.yaml diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 410c09b66..2aee1d6c8 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -79,6 +79,11 @@ spec: kind: Project name: projects.openstack.k-orc.cloud version: v1alpha1 + - description: Region is the Schema for an ORC resource. + displayName: Region + kind: Region + name: regions.openstack.k-orc.cloud + version: v1alpha1 - description: RoleAssignment is the Schema for an ORC resource. displayName: Role Assignment kind: RoleAssignment diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a04a488e2..0aecf95e1 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -29,6 +29,7 @@ rules: - networks - ports - projects + - regions - roleassignments - roles - routerinterfaces @@ -66,6 +67,7 @@ rules: - networks/status - ports/status - projects/status + - regions/status - roleassignments/status - roles/status - routerinterfaces/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index ceb05e15e..b804b5d6f 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -13,6 +13,7 @@ resources: - openstack_v1alpha1_network.yaml - openstack_v1alpha1_port.yaml - openstack_v1alpha1_project.yaml +- openstack_v1alpha1_region.yaml - openstack_v1alpha1_role.yaml - openstack_v1alpha1_roleassignment.yaml - openstack_v1alpha1_router.yaml diff --git a/config/samples/openstack_v1alpha1_region.yaml b/config/samples/openstack_v1alpha1_region.yaml new file mode 100644 index 000000000..2c0e79a8d --- /dev/null +++ b/config/samples/openstack_v1alpha1_region.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: SampleRegion + description: Sample Region diff --git a/internal/controllers/region/actuator.go b/internal/controllers/region/actuator.go new file mode 100644 index 000000000..32446df35 --- /dev/null +++ b/internal/controllers/region/actuator.go @@ -0,0 +1,256 @@ +/* +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 region + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/regions" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + 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" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = regions.Region + + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) + +type regionActuator struct { + osClient osclients.RegionClient + k8sClient client.Client +} + +var _ createResourceActuator = regionActuator{} +var _ deleteResourceActuator = regionActuator{} + +func (regionActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator regionActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetRegion(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator regionActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + // Filter by the expected resource name to avoid adopting wrong regions. + // The OpenStack Region API does not support server-side filtering by name/id, + // so we must use client-side filtering. + filters := []osclients.ResourceFilter[osResourceT]{ + func(r *regions.Region) bool { + return r.ID == string(orcObject.Spec.Resource.Name) + }, + } + + if resourceSpec.Description != nil { + filters = append(filters, func(r *regions.Region) bool { + return r.Description == *resourceSpec.Description + }) + } + + // TODO: + // listOpts := regions.ListOpts{ + // ParentRegionID: ptr.Deref(resourceSpec.ParentRegionID), + // } + + return actuator.listOSResources(ctx, filters, regions.ListOpts{}), true +} + +func (actuator regionActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // The OpenStack Region API does not support server-side filtering by name/id, + // so we must use client-side filtering. + var filters []osclients.ResourceFilter[osResourceT] + + if filter.Name != nil { + filters = append(filters, func(r *regions.Region) bool { + return r.ID == string(*filter.Name) + }) + } + + if filter.Description != nil { + filters = append(filters, func(r *regions.Region) bool { + return r.Description == *filter.Description + }) + } + + // TODO: + // listOpts := regions.ListOpts{ + // ParentRegionID: ptr.Deref(resourceSpec.ParentRegionID), + // } + + return actuator.listOSResources(ctx, filters, regions.ListOpts{}), nil +} + +func (actuator regionActuator) listOSResources(ctx context.Context, filters []osclients.ResourceFilter[osResourceT], listOpts regions.ListOptsBuilder) iter.Seq2[*osResourceT, error] { + regions := actuator.osClient.ListRegions(ctx, listOpts) + return osclients.Filter(regions, filters...) +} + +func (actuator regionActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, 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")) + } + createOpts := regions.CreateOpts{ + ID: string(resource.Name), + Description: ptr.Deref(resource.Description, ""), + // TODO: + // ParentRegionID: ptr.Deref(resource.ParentRegionID), + } + + osResource, err := actuator.osClient.CreateRegion(ctx, 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) + } + + return osResource, nil +} + +func (actuator regionActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteRegion(ctx, resource.ID)) +} + +func (actuator regionActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + // Should have been caught by API validation + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) + } + + updateOpts := regions.UpdateOpts{} + + handleDescriptionUpdate(&updateOpts, resource, osResource) + + needsUpdate, err := needsUpdate(updateOpts) + if err != nil { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + } + if !needsUpdate { + log.V(logging.Debug).Info("No changes") + return nil + } + + _, err = actuator.osClient.UpdateRegion(ctx, osResource.ID, updateOpts) + + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts regions.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToRegionUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["region"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleDescriptionUpdate(updateOpts *regions.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator regionActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type regionHelperFactory struct{} + +var _ helperFactory = regionHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.Region, controller interfaces.ResourceController) (regionActuator, 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 regionActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return regionActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewRegionClient() + if err != nil { + return regionActuator{}, progress.WrapError(err) + } + + return regionActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (regionHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return regionAdapter{obj} +} + +func (regionHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (regionHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/region/actuator_test.go b/internal/controllers/region/actuator_test.go new file mode 100644 index 000000000..98b26aaff --- /dev/null +++ b/internal/controllers/region/actuator_test.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. +*/ + +package region + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/regions" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "k8s.io/utils/ptr" +) + +func TestNeedsUpdate(t *testing.T) { + testCases := []struct { + name string + updateOpts regions.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: regions.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: regions.UpdateOpts{Description: ptr.To("updated")}, + expectChange: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, _ := needsUpdate(tt.updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleDescriptionUpdate(t *testing.T) { + ptrToDescription := ptr.To[string] + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false}, + {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.RegionResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := regions.UpdateOpts{} + handleDescriptionUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} diff --git a/internal/controllers/region/controller.go b/internal/controllers/region/controller.go new file mode 100644 index 000000000..736547d88 --- /dev/null +++ b/internal/controllers/region/controller.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. +*/ + +package region + +import ( + "context" + "errors" + "time" + + 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 = "region" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=regions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=regions/status,verbs=get;update;patch + +type regionReconcilerConstructor struct { + scopeFactory scope.Factory + defaultResyncPeriod time.Duration +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return ®ionReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (regionReconcilerConstructor) GetName() string { + return controllerName +} + +func (c *regionReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + +// SetupWithManager sets up the controller with the Manager. +func (c *regionReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + For(&orcv1alpha1.Region{}) + + 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, regionHelperFactory{}, regionStatusWriter{}, c.defaultResyncPeriod) + return builder.Complete(&r) +} diff --git a/internal/controllers/region/status.go b/internal/controllers/region/status.go new file mode 100644 index 000000000..9ecebc25e --- /dev/null +++ b/internal/controllers/region/status.go @@ -0,0 +1,60 @@ +/* +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 region + +import ( + "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 regionStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.RegionApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.RegionStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Region, *osResourceT, *objectApplyT, *statusApplyT] = regionStatusWriter{} + +func (regionStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.Region(name, namespace) +} + +func (regionStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Region, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource == nil { + if orcObject.Status.ID == nil { + return metav1.ConditionFalse, nil + } else { + return metav1.ConditionUnknown, nil + } + } + return metav1.ConditionTrue, nil +} + +func (regionStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.RegionResourceStatus(). + WithName(osResource.ID) + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/region/tests/region-create-full/00-assert.yaml b/internal/controllers/region/tests/region-create-full/00-assert.yaml new file mode 100644 index 000000000..d5c75919f --- /dev/null +++ b/internal/controllers/region/tests/region-create-full/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-create-full +status: + resource: + name: region-create-full-override + description: Region from "create full" test + 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: Region + name: region-create-full + ref: region +assertAll: + - celExpr: "region.status.id != ''" + - celExpr: "region.status.id == region.status.resource.name" diff --git a/internal/controllers/region/tests/region-create-full/00-create-resource.yaml b/internal/controllers/region/tests/region-create-full/00-create-resource.yaml new file mode 100644 index 000000000..937fd8034 --- /dev/null +++ b/internal/controllers/region/tests/region-create-full/00-create-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-create-full-override + description: Region from "create full" test diff --git a/internal/controllers/region/tests/region-create-full/00-secret.yaml b/internal/controllers/region/tests/region-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/region/tests/region-create-full/00-secret.yaml @@ -0,0 +1,6 @@ +--- +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/region/tests/region-create-full/README.md b/internal/controllers/region/tests/region-create-full/README.md new file mode 100644 index 000000000..b7629e77a --- /dev/null +++ b/internal/controllers/region/tests/region-create-full/README.md @@ -0,0 +1,11 @@ +# Create a Region with all the options + +## Step 00 + +Create a Region using all available fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name from the spec when it is specified. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/region/tests/region-create-minimal/00-assert.yaml b/internal/controllers/region/tests/region-create-minimal/00-assert.yaml new file mode 100644 index 000000000..7b66e4e5a --- /dev/null +++ b/internal/controllers/region/tests/region-create-minimal/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-create-minimal +status: + resource: + name: TestRegion + 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: Region + name: region-create-minimal + ref: region +assertAll: + - celExpr: "region.status.id != ''" + - celExpr: "region.status.id == region.status.resource.name" diff --git a/internal/controllers/region/tests/region-create-minimal/00-create-resource.yaml b/internal/controllers/region/tests/region-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..b9a187de0 --- /dev/null +++ b/internal/controllers/region/tests/region-create-minimal/00-create-resource.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: TestRegion diff --git a/internal/controllers/region/tests/region-create-minimal/00-secret.yaml b/internal/controllers/region/tests/region-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/region/tests/region-create-minimal/00-secret.yaml @@ -0,0 +1,6 @@ +--- +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/region/tests/region-create-minimal/01-assert.yaml b/internal/controllers/region/tests/region-create-minimal/01-assert.yaml new file mode 100644 index 000000000..a9af6d028 --- /dev/null +++ b/internal/controllers/region/tests/region-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/region' in secret.metadata.finalizers" diff --git a/internal/controllers/region/tests/region-create-minimal/01-delete-secret.yaml b/internal/controllers/region/tests/region-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/region/tests/region-create-minimal/01-delete-secret.yaml @@ -0,0 +1,7 @@ +--- +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/region/tests/region-create-minimal/README.md b/internal/controllers/region/tests/region-create-minimal/README.md new file mode 100644 index 000000000..bca20a8f4 --- /dev/null +++ b/internal/controllers/region/tests/region-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a Region with the minimum options + +## Step 00 + +Create a minimal Region, 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 no name is explicitly specified. + +## 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/region/tests/region-import-error/00-assert.yaml b/internal/controllers/region/tests/region-import-error/00-assert.yaml new file mode 100644 index 000000000..acf48c848 --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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: Region +metadata: + name: region-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/region/tests/region-import-error/00-create-resources.yaml b/internal/controllers/region/tests/region-import-error/00-create-resources.yaml new file mode 100644 index 000000000..68818de9a --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/00-create-resources.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import-error-external-1 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-import-error-external-1 + description: Region from "import error" test +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import-error-external-2 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-import-error-external-2 + description: Region from "import error" test diff --git a/internal/controllers/region/tests/region-import-error/00-secret.yaml b/internal/controllers/region/tests/region-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/00-secret.yaml @@ -0,0 +1,6 @@ +--- +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/region/tests/region-import-error/01-assert.yaml b/internal/controllers/region/tests/region-import-error/01-assert.yaml new file mode 100644 index 000000000..6f357a4a3 --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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/region/tests/region-import-error/01-import-resource.yaml b/internal/controllers/region/tests/region-import-error/01-import-resource.yaml new file mode 100644 index 000000000..514f12ee3 --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: Region from "import error" test diff --git a/internal/controllers/region/tests/region-import-error/README.md b/internal/controllers/region/tests/region-import-error/README.md new file mode 100644 index 000000000..f9afdde02 --- /dev/null +++ b/internal/controllers/region/tests/region-import-error/README.md @@ -0,0 +1,13 @@ +# Import Region with more than one matching resources + +## Step 00 + +Create two Regions with identical specs. + +## Step 01 + +Ensure that an imported Region with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/region/tests/region-import/00-assert.yaml b/internal/controllers/region/tests/region-import/00-assert.yaml new file mode 100644 index 000000000..691a1a209 --- /dev/null +++ b/internal/controllers/region/tests/region-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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/region/tests/region-import/00-import-resource.yaml b/internal/controllers/region/tests/region-import/00-import-resource.yaml new file mode 100644 index 000000000..9fa33521d --- /dev/null +++ b/internal/controllers/region/tests/region-import/00-import-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: region-import-external + description: Region region-import-external from "region-import" test diff --git a/internal/controllers/region/tests/region-import/00-secret.yaml b/internal/controllers/region/tests/region-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/region/tests/region-import/00-secret.yaml @@ -0,0 +1,6 @@ +--- +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/region/tests/region-import/01-assert.yaml b/internal/controllers/region/tests/region-import/01-assert.yaml new file mode 100644 index 000000000..68f90cfba --- /dev/null +++ b/internal/controllers/region/tests/region-import/01-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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: region-import-external-not-this-one + description: Region region-import-external from "region-import" test +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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/region/tests/region-import/01-create-trap-resource.yaml b/internal/controllers/region/tests/region-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..49fca32d8 --- /dev/null +++ b/internal/controllers/region/tests/region-import/01-create-trap-resource.yaml @@ -0,0 +1,16 @@ +--- +# This `region-import-external-not-this-one` resource serves two purposes: +# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) +# - ensure that importing a resource which name is a substring of it will not pick this one. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-import-external-not-this-one + description: Region region-import-external from "region-import" test diff --git a/internal/controllers/region/tests/region-import/02-assert.yaml b/internal/controllers/region/tests/region-import/02-assert.yaml new file mode 100644 index 000000000..41473c3a0 --- /dev/null +++ b/internal/controllers/region/tests/region-import/02-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Region + name: region-import-external + ref: region1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Region + name: region-import-external-not-this-one + ref: region2 +assertAll: + - celExpr: "region1.status.id != region2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-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: region-import-external + description: Region region-import-external from "region-import" test diff --git a/internal/controllers/region/tests/region-import/02-create-resource.yaml b/internal/controllers/region/tests/region-import/02-create-resource.yaml new file mode 100644 index 000000000..1ca9caaa3 --- /dev/null +++ b/internal/controllers/region/tests/region-import/02-create-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-import-external + description: Region region-import-external from "region-import" test diff --git a/internal/controllers/region/tests/region-import/README.md b/internal/controllers/region/tests/region-import/README.md new file mode 100644 index 000000000..e79271aed --- /dev/null +++ b/internal/controllers/region/tests/region-import/README.md @@ -0,0 +1,18 @@ +# Import Region + +## Step 00 + +Import a region that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a region whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. + +## Step 02 + +Create a region matching the filter and verify that the observed status on the imported region corresponds to the spec of the created region. +Also, confirm that it does not adopt any region whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/region/tests/region-update/00-assert.yaml b/internal/controllers/region/tests/region-update/00-assert.yaml new file mode 100644 index 000000000..9a4bc2266 --- /dev/null +++ b/internal/controllers/region/tests/region-update/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Region + name: region-update + ref: region +assertAll: + - celExpr: "!has(region.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-update +status: + resource: + name: region-update + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/region/tests/region-update/00-minimal-resource.yaml b/internal/controllers/region/tests/region-update/00-minimal-resource.yaml new file mode 100644 index 000000000..5807b5644 --- /dev/null +++ b/internal/controllers/region/tests/region-update/00-minimal-resource.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: region-update diff --git a/internal/controllers/region/tests/region-update/00-secret.yaml b/internal/controllers/region/tests/region-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/region/tests/region-update/00-secret.yaml @@ -0,0 +1,6 @@ +--- +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/region/tests/region-update/01-assert.yaml b/internal/controllers/region/tests/region-update/01-assert.yaml new file mode 100644 index 000000000..1d8f8ffcc --- /dev/null +++ b/internal/controllers/region/tests/region-update/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-update +status: + resource: + description: region-update-updated + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/region/tests/region-update/01-updated-resource.yaml b/internal/controllers/region/tests/region-update/01-updated-resource.yaml new file mode 100644 index 000000000..32725e829 --- /dev/null +++ b/internal/controllers/region/tests/region-update/01-updated-resource.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-update +spec: + resource: + description: region-update-updated diff --git a/internal/controllers/region/tests/region-update/02-assert.yaml b/internal/controllers/region/tests/region-update/02-assert.yaml new file mode 100644 index 000000000..9a4bc2266 --- /dev/null +++ b/internal/controllers/region/tests/region-update/02-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Region + name: region-update + ref: region +assertAll: + - celExpr: "!has(region.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Region +metadata: + name: region-update +status: + resource: + name: region-update + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/region/tests/region-update/02-reverted-resource.yaml b/internal/controllers/region/tests/region-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/region/tests/region-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 apply command instead. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl replace -f 00-minimal-resource.yaml + namespaced: true diff --git a/internal/controllers/region/tests/region-update/README.md b/internal/controllers/region/tests/region-update/README.md new file mode 100644 index 000000000..fb60a9bbd --- /dev/null +++ b/internal/controllers/region/tests/region-update/README.md @@ -0,0 +1,17 @@ +# Update Region + +## Step 00 + +Create a Region using only mandatory fields. + +## Step 01 + +Update all mutable fields. + +## Step 02 + +Revert the resource to its original value and verify that the resulting object matches its state when first created. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/controllers/region/zz_generated.adapter.go b/internal/controllers/region/zz_generated.adapter.go new file mode 100644 index 000000000..6f75121b7 --- /dev/null +++ b/internal/controllers/region/zz_generated.adapter.go @@ -0,0 +1,88 @@ +// 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 region + +import ( + 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" +) + +// Fundamental types +type ( + orcObjectT = orcv1alpha1.Region + orcObjectListT = orcv1alpha1.RegionList + resourceSpecT = orcv1alpha1.RegionResourceSpec + filterT = orcv1alpha1.RegionFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = regionAdapter +) + +type regionAdapter struct { + *orcv1alpha1.Region +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.Region +} + +func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return f.Spec.ManagementPolicy +} + +func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return f.Spec.ManagedOptions +} + +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + +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 + } + return f.Spec.Import.ID +} + +func (f adapterT) GetImportFilter() *filterT { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.Filter +} diff --git a/internal/controllers/region/zz_generated.controller.go b/internal/controllers/region/zz_generated.controller.go new file mode 100644 index 000000000..b762d664b --- /dev/null +++ b/internal/controllers/region/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 region + +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 466a548e6..a45bfc509 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -53,6 +53,9 @@ import ( //go:generate mockgen -package mock -destination=keypair.go -source=../keypair.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock KeyPairClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt keypair.go > _keypair.go && mv _keypair.go keypair.go" +//go:generate mockgen -package mock -destination=region.go -source=../region.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RegionClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt region.go > _region.go && mv _region.go region.go" + //go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go" diff --git a/internal/osclients/mock/region.go b/internal/osclients/mock/region.go new file mode 100644 index 000000000..660e304bd --- /dev/null +++ b/internal/osclients/mock/region.go @@ -0,0 +1,131 @@ +/* +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: ../region.go +// +// Generated by this command: +// +// mockgen -package mock -destination=region.go -source=../region.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RegionClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + regions "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/regions" + gomock "go.uber.org/mock/gomock" +) + +// MockRegionClient is a mock of RegionClient interface. +type MockRegionClient struct { + ctrl *gomock.Controller + recorder *MockRegionClientMockRecorder + isgomock struct{} +} + +// MockRegionClientMockRecorder is the mock recorder for MockRegionClient. +type MockRegionClientMockRecorder struct { + mock *MockRegionClient +} + +// NewMockRegionClient creates a new mock instance. +func NewMockRegionClient(ctrl *gomock.Controller) *MockRegionClient { + mock := &MockRegionClient{ctrl: ctrl} + mock.recorder = &MockRegionClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRegionClient) EXPECT() *MockRegionClientMockRecorder { + return m.recorder +} + +// CreateRegion mocks base method. +func (m *MockRegionClient) CreateRegion(ctx context.Context, opts regions.CreateOptsBuilder) (*regions.Region, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRegion", ctx, opts) + ret0, _ := ret[0].(*regions.Region) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRegion indicates an expected call of CreateRegion. +func (mr *MockRegionClientMockRecorder) CreateRegion(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRegion", reflect.TypeOf((*MockRegionClient)(nil).CreateRegion), ctx, opts) +} + +// DeleteRegion mocks base method. +func (m *MockRegionClient) DeleteRegion(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRegion", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRegion indicates an expected call of DeleteRegion. +func (mr *MockRegionClientMockRecorder) DeleteRegion(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRegion", reflect.TypeOf((*MockRegionClient)(nil).DeleteRegion), ctx, resourceID) +} + +// GetRegion mocks base method. +func (m *MockRegionClient) GetRegion(ctx context.Context, resourceID string) (*regions.Region, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRegion", ctx, resourceID) + ret0, _ := ret[0].(*regions.Region) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRegion indicates an expected call of GetRegion. +func (mr *MockRegionClientMockRecorder) GetRegion(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRegion", reflect.TypeOf((*MockRegionClient)(nil).GetRegion), ctx, resourceID) +} + +// ListRegions mocks base method. +func (m *MockRegionClient) ListRegions(ctx context.Context, listOpts regions.ListOptsBuilder) iter.Seq2[*regions.Region, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRegions", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*regions.Region, error]) + return ret0 +} + +// ListRegions indicates an expected call of ListRegions. +func (mr *MockRegionClientMockRecorder) ListRegions(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRegions", reflect.TypeOf((*MockRegionClient)(nil).ListRegions), ctx, listOpts) +} + +// UpdateRegion mocks base method. +func (m *MockRegionClient) UpdateRegion(ctx context.Context, id string, opts regions.UpdateOptsBuilder) (*regions.Region, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRegion", ctx, id, opts) + ret0, _ := ret[0].(*regions.Region) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateRegion indicates an expected call of UpdateRegion. +func (mr *MockRegionClientMockRecorder) UpdateRegion(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRegion", reflect.TypeOf((*MockRegionClient)(nil).UpdateRegion), ctx, id, opts) +} diff --git a/internal/osclients/region.go b/internal/osclients/region.go new file mode 100644 index 000000000..7d1eb2181 --- /dev/null +++ b/internal/osclients/region.go @@ -0,0 +1,104 @@ +/* +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/identity/v3/regions" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type RegionClient interface { + ListRegions(ctx context.Context, listOpts regions.ListOptsBuilder) iter.Seq2[*regions.Region, error] + CreateRegion(ctx context.Context, opts regions.CreateOptsBuilder) (*regions.Region, error) + DeleteRegion(ctx context.Context, resourceID string) error + GetRegion(ctx context.Context, resourceID string) (*regions.Region, error) + UpdateRegion(ctx context.Context, id string, opts regions.UpdateOptsBuilder) (*regions.Region, error) +} + +type regionClient struct{ client *gophercloud.ServiceClient } + +// NewRegionClient returns a new OpenStack client. +func NewRegionClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RegionClient, error) { + client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create region service client: %v", err) + } + + return ®ionClient{client}, nil +} + +func (c regionClient) ListRegions(ctx context.Context, listOpts regions.ListOptsBuilder) iter.Seq2[*regions.Region, error] { + pager := regions.List(c.client, listOpts) + return func(yield func(*regions.Region, error) bool) { + _ = pager.EachPage(ctx, yieldPage(regions.ExtractRegions, yield)) + } +} + +func (c regionClient) CreateRegion(ctx context.Context, opts regions.CreateOptsBuilder) (*regions.Region, error) { + return regions.Create(ctx, c.client, opts).Extract() +} + +func (c regionClient) DeleteRegion(ctx context.Context, resourceID string) error { + return regions.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c regionClient) GetRegion(ctx context.Context, resourceID string) (*regions.Region, error) { + return regions.Get(ctx, c.client, resourceID).Extract() +} + +func (c regionClient) UpdateRegion(ctx context.Context, id string, opts regions.UpdateOptsBuilder) (*regions.Region, error) { + return regions.Update(ctx, c.client, id, opts).Extract() +} + +type regionErrorClient struct{ error } + +// NewRegionErrorClient returns a RegionClient in which every method returns the given error. +func NewRegionErrorClient(e error) RegionClient { + return regionErrorClient{e} +} + +func (e regionErrorClient) ListRegions(_ context.Context, _ regions.ListOptsBuilder) iter.Seq2[*regions.Region, error] { + return func(yield func(*regions.Region, error) bool) { + yield(nil, e.error) + } +} + +func (e regionErrorClient) CreateRegion(_ context.Context, _ regions.CreateOptsBuilder) (*regions.Region, error) { + return nil, e.error +} + +func (e regionErrorClient) DeleteRegion(_ context.Context, _ string) error { + return e.error +} + +func (e regionErrorClient) GetRegion(_ context.Context, _ string) (*regions.Region, error) { + return nil, e.error +} + +func (e regionErrorClient) UpdateRegion(_ context.Context, _ string, _ regions.UpdateOptsBuilder) (*regions.Region, error) { + return nil, e.error +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index e57e4da58..57fa7452f 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -44,6 +44,7 @@ type MockScopeFactory struct { ImageClient *mock.MockImageClient KeyPairClient *mock.MockKeyPairClient NetworkClient *mock.MockNetworkClient + RegionClient *mock.MockRegionClient RoleClient *mock.MockRoleClient RoleAssignmentClient *mock.MockRoleAssignmentClient ServiceClient *mock.MockServiceClient @@ -66,6 +67,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { imageClient := mock.NewMockImageClient(mockCtrl) keypairClient := mock.NewMockKeyPairClient(mockCtrl) networkClient := mock.NewMockNetworkClient(mockCtrl) + regionClient := mock.NewMockRegionClient(mockCtrl) roleClient := mock.NewMockRoleClient(mockCtrl) roleassignmentClient := mock.NewMockRoleAssignmentClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) @@ -85,6 +87,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { ImageClient: imageClient, KeyPairClient: keypairClient, NetworkClient: networkClient, + RegionClient: regionClient, RoleClient: roleClient, RoleAssignmentClient: roleassignmentClient, ServiceClient: serviceClient, @@ -158,6 +161,10 @@ func (f *MockScopeFactory) NewGroupClient() (osclients.GroupClient, error) { return f.GroupClient, nil } +func (f *MockScopeFactory) NewRegionClient() (osclients.RegionClient, error) { + return f.RegionClient, nil +} + func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index f1207bd6c..47c2312cf 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -197,6 +197,10 @@ func (s *providerScope) NewGroupClient() (clients.GroupClient, error) { return clients.NewGroupClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewRegionClient() (clients.RegionClient, error) { + return clients.NewRegionClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewRoleClient() (clients.RoleClient, error) { return clients.NewRoleClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 4f093fe9a..774de61ca 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -58,6 +58,7 @@ type Scope interface { NewImageClient() (osclients.ImageClient, error) NewKeyPairClient() (osclients.KeyPairClient, error) NewNetworkClient() (osclients.NetworkClient, error) + NewRegionClient() (osclients.RegionClient, error) NewRoleClient() (osclients.RoleClient, error) NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) NewServiceClient() (osclients.ServiceClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 90fc98dde..eb8e9dcb8 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -14,6 +14,7 @@ testDirs: - ./internal/controllers/network/tests/ - ./internal/controllers/port/tests/ - ./internal/controllers/project/tests/ +- ./internal/controllers/region/tests/ - ./internal/controllers/role/tests/ - ./internal/controllers/roleassignment/tests/ - ./internal/controllers/router/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/region.go b/pkg/clients/applyconfiguration/api/v1alpha1/region.go new file mode 100644 index 000000000..ecbe9be1e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/region.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" +) + +// RegionApplyConfiguration represents a declarative configuration of the Region type for use +// with apply. +type RegionApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *RegionSpecApplyConfiguration `json:"spec,omitempty"` + Status *RegionStatusApplyConfiguration `json:"status,omitempty"` +} + +// Region constructs a declarative configuration of the Region type for use with +// apply. +func Region(name, namespace string) *RegionApplyConfiguration { + b := &RegionApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Region") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractRegion extracts the applied configuration owned by fieldManager from +// region. If no managedFields are found in region for fieldManager, a +// RegionApplyConfiguration 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. +// region must be a unmodified Region API object that was retrieved from the Kubernetes API. +// ExtractRegion 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 ExtractRegion(region *apiv1alpha1.Region, fieldManager string) (*RegionApplyConfiguration, error) { + return extractRegion(region, fieldManager, "") +} + +// ExtractRegionStatus is the same as ExtractRegion except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractRegionStatus(region *apiv1alpha1.Region, fieldManager string) (*RegionApplyConfiguration, error) { + return extractRegion(region, fieldManager, "status") +} + +func extractRegion(region *apiv1alpha1.Region, fieldManager string, subresource string) (*RegionApplyConfiguration, error) { + b := &RegionApplyConfiguration{} + err := managedfields.ExtractInto(region, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Region"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(region.Name) + b.WithNamespace(region.Namespace) + + b.WithKind("Region") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b RegionApplyConfiguration) 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 *RegionApplyConfiguration) WithKind(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithAPIVersion(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithName(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithGenerateName(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithNamespace(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithUID(value types.UID) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithResourceVersion(value string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithGeneration(value int64) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithCreationTimestamp(value metav1.Time) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithLabels(entries map[string]string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithAnnotations(entries map[string]string) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithFinalizers(values ...string) *RegionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *RegionApplyConfiguration) 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 *RegionApplyConfiguration) WithSpec(value *RegionSpecApplyConfiguration) *RegionApplyConfiguration { + 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 *RegionApplyConfiguration) WithStatus(value *RegionStatusApplyConfiguration) *RegionApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *RegionApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *RegionApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *RegionApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *RegionApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionfilter.go new file mode 100644 index 000000000..e56acce2b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionfilter.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" +) + +// RegionFilterApplyConfiguration represents a declarative configuration of the RegionFilter type for use +// with apply. +type RegionFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +// RegionFilterApplyConfiguration constructs a declarative configuration of the RegionFilter type for use with +// apply. +func RegionFilter() *RegionFilterApplyConfiguration { + return &RegionFilterApplyConfiguration{} +} + +// 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 *RegionFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *RegionFilterApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description 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 Description field is set to the value of the last call. +func (b *RegionFilterApplyConfiguration) WithDescription(value string) *RegionFilterApplyConfiguration { + b.Description = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionimport.go new file mode 100644 index 000000000..0ccd8c40e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionimport.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 + +// RegionImportApplyConfiguration represents a declarative configuration of the RegionImport type for use +// with apply. +type RegionImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *RegionFilterApplyConfiguration `json:"filter,omitempty"` +} + +// RegionImportApplyConfiguration constructs a declarative configuration of the RegionImport type for use with +// apply. +func RegionImport() *RegionImportApplyConfiguration { + return &RegionImportApplyConfiguration{} +} + +// 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 *RegionImportApplyConfiguration) WithID(value string) *RegionImportApplyConfiguration { + b.ID = &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 *RegionImportApplyConfiguration) WithFilter(value *RegionFilterApplyConfiguration) *RegionImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcespec.go new file mode 100644 index 000000000..77186b175 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcespec.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" +) + +// RegionResourceSpecApplyConfiguration represents a declarative configuration of the RegionResourceSpec type for use +// with apply. +type RegionResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +// RegionResourceSpecApplyConfiguration constructs a declarative configuration of the RegionResourceSpec type for use with +// apply. +func RegionResourceSpec() *RegionResourceSpecApplyConfiguration { + return &RegionResourceSpecApplyConfiguration{} +} + +// 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 *RegionResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *RegionResourceSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description 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 Description field is set to the value of the last call. +func (b *RegionResourceSpecApplyConfiguration) WithDescription(value string) *RegionResourceSpecApplyConfiguration { + b.Description = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcestatus.go new file mode 100644 index 000000000..8e9efb197 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionresourcestatus.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 + +// RegionResourceStatusApplyConfiguration represents a declarative configuration of the RegionResourceStatus type for use +// with apply. +type RegionResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +// RegionResourceStatusApplyConfiguration constructs a declarative configuration of the RegionResourceStatus type for use with +// apply. +func RegionResourceStatus() *RegionResourceStatusApplyConfiguration { + return &RegionResourceStatusApplyConfiguration{} +} + +// 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 *RegionResourceStatusApplyConfiguration) WithName(value string) *RegionResourceStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithDescription sets the Description 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 Description field is set to the value of the last call. +func (b *RegionResourceStatusApplyConfiguration) WithDescription(value string) *RegionResourceStatusApplyConfiguration { + b.Description = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionspec.go new file mode 100644 index 000000000..df86a4eb2 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionspec.go @@ -0,0 +1,89 @@ +/* +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" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RegionSpecApplyConfiguration represents a declarative configuration of the RegionSpec type for use +// with apply. +type RegionSpecApplyConfiguration struct { + Import *RegionImportApplyConfiguration `json:"import,omitempty"` + Resource *RegionResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// RegionSpecApplyConfiguration constructs a declarative configuration of the RegionSpec type for use with +// apply. +func RegionSpec() *RegionSpecApplyConfiguration { + return &RegionSpecApplyConfiguration{} +} + +// 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 *RegionSpecApplyConfiguration) WithImport(value *RegionImportApplyConfiguration) *RegionSpecApplyConfiguration { + 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 *RegionSpecApplyConfiguration) WithResource(value *RegionResourceSpecApplyConfiguration) *RegionSpecApplyConfiguration { + 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 *RegionSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *RegionSpecApplyConfiguration { + 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 *RegionSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *RegionSpecApplyConfiguration { + b.ManagedOptions = value + return b +} + +// WithResyncPeriod sets the ResyncPeriod 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 ResyncPeriod field is set to the value of the last call. +func (b *RegionSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *RegionSpecApplyConfiguration { + b.ResyncPeriod = &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 *RegionSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *RegionSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/regionstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/regionstatus.go new file mode 100644 index 000000000..3e032a999 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/regionstatus.go @@ -0,0 +1,76 @@ +/* +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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// RegionStatusApplyConfiguration represents a declarative configuration of the RegionStatus type for use +// with apply. +type RegionStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *RegionResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` +} + +// RegionStatusApplyConfiguration constructs a declarative configuration of the RegionStatus type for use with +// apply. +func RegionStatus() *RegionStatusApplyConfiguration { + return &RegionStatusApplyConfiguration{} +} + +// 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 *RegionStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *RegionStatusApplyConfiguration { + 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 *RegionStatusApplyConfiguration) WithID(value string) *RegionStatusApplyConfiguration { + 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 *RegionStatusApplyConfiguration) WithResource(value *RegionResourceStatusApplyConfiguration) *RegionStatusApplyConfiguration { + b.Resource = value + return b +} + +// WithLastSyncTime sets the LastSyncTime 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 LastSyncTime field is set to the value of the last call. +func (b *RegionStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *RegionStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 9fc18f739..9981ed6c8 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2181,6 +2181,105 @@ var schemaYAML = typed.YAMLObject(`types: - name: segmentationID type: scalar: numeric +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Region + 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.RegionSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionFilter + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionResourceSpec + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionResourceStatus + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionSpec + 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.RegionImport + - 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.RegionResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionStatus + 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: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RegionResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Role map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 22e66aea0..044ebf3e5 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -268,6 +268,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ProjectStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ProviderPropertiesStatus"): return &apiv1alpha1.ProviderPropertiesStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Region"): + return &apiv1alpha1.RegionApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionFilter"): + return &apiv1alpha1.RegionFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionImport"): + return &apiv1alpha1.RegionImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionResourceSpec"): + return &apiv1alpha1.RegionResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionResourceStatus"): + return &apiv1alpha1.RegionResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionSpec"): + return &apiv1alpha1.RegionSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RegionStatus"): + return &apiv1alpha1.RegionStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Role"): return &apiv1alpha1.RoleApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignment"): 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 e39fc5dc9..7b37297bd 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -40,6 +40,7 @@ type OpenstackV1alpha1Interface interface { NetworksGetter PortsGetter ProjectsGetter + RegionsGetter RolesGetter RoleAssignmentsGetter RoutersGetter @@ -109,6 +110,10 @@ func (c *OpenstackV1alpha1Client) Projects(namespace string) ProjectInterface { return newProjects(c, namespace) } +func (c *OpenstackV1alpha1Client) Regions(namespace string) RegionInterface { + return newRegions(c, namespace) +} + func (c *OpenstackV1alpha1Client) Roles(namespace string) RoleInterface { return newRoles(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 5015aed35..61253c6bc 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 @@ -76,6 +76,10 @@ func (c *FakeOpenstackV1alpha1) Projects(namespace string) v1alpha1.ProjectInter return newFakeProjects(c, namespace) } +func (c *FakeOpenstackV1alpha1) Regions(namespace string) v1alpha1.RegionInterface { + return newFakeRegions(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Roles(namespace string) v1alpha1.RoleInterface { return newFakeRoles(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_region.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_region.go new file mode 100644 index 000000000..1441d1cb6 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_region.go @@ -0,0 +1,51 @@ +/* +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" +) + +// fakeRegions implements RegionInterface +type fakeRegions struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.Region, *v1alpha1.RegionList, *apiv1alpha1.RegionApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeRegions(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.RegionInterface { + return &fakeRegions{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.Region, *v1alpha1.RegionList, *apiv1alpha1.RegionApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("regions"), + v1alpha1.SchemeGroupVersion.WithKind("Region"), + func() *v1alpha1.Region { return &v1alpha1.Region{} }, + func() *v1alpha1.RegionList { return &v1alpha1.RegionList{} }, + func(dst, src *v1alpha1.RegionList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.RegionList) []*v1alpha1.Region { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.RegionList, items []*v1alpha1.Region) { + 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 7c5d67d45..d798c9ab6 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -42,6 +42,8 @@ type PortExpansion interface{} type ProjectExpansion interface{} +type RegionExpansion interface{} + type RoleExpansion interface{} type RoleAssignmentExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/region.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/region.go new file mode 100644 index 000000000..78f9807fd --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/region.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" +) + +// RegionsGetter has a method to return a RegionInterface. +// A group's client should implement this interface. +type RegionsGetter interface { + Regions(namespace string) RegionInterface +} + +// RegionInterface has methods to work with Region resources. +type RegionInterface interface { + Create(ctx context.Context, region *apiv1alpha1.Region, opts v1.CreateOptions) (*apiv1alpha1.Region, error) + Update(ctx context.Context, region *apiv1alpha1.Region, opts v1.UpdateOptions) (*apiv1alpha1.Region, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, region *apiv1alpha1.Region, opts v1.UpdateOptions) (*apiv1alpha1.Region, 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.Region, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.RegionList, 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.Region, err error) + Apply(ctx context.Context, region *applyconfigurationapiv1alpha1.RegionApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Region, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, region *applyconfigurationapiv1alpha1.RegionApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Region, err error) + RegionExpansion +} + +// regions implements RegionInterface +type regions struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.Region, *apiv1alpha1.RegionList, *applyconfigurationapiv1alpha1.RegionApplyConfiguration] +} + +// newRegions returns a Regions +func newRegions(c *OpenstackV1alpha1Client, namespace string) *regions { + return ®ions{ + gentype.NewClientWithListAndApply[*apiv1alpha1.Region, *apiv1alpha1.RegionList, *applyconfigurationapiv1alpha1.RegionApplyConfiguration]( + "regions", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.Region { return &apiv1alpha1.Region{} }, + func() *apiv1alpha1.RegionList { return &apiv1alpha1.RegionList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 69d2bdeaf..48dc03298 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -48,6 +48,8 @@ type Interface interface { Ports() PortInformer // Projects returns a ProjectInformer. Projects() ProjectInformer + // Regions returns a RegionInformer. + Regions() RegionInformer // Roles returns a RoleInformer. Roles() RoleInformer // RoleAssignments returns a RoleAssignmentInformer. @@ -149,6 +151,11 @@ func (v *version) Projects() ProjectInformer { return &projectInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Regions returns a RegionInformer. +func (v *version) Regions() RegionInformer { + return ®ionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Roles returns a RoleInformer. func (v *version) Roles() RoleInformer { return &roleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/region.go b/pkg/clients/informers/externalversions/api/v1alpha1/region.go new file mode 100644 index 000000000..4a6c4c3ed --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/region.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" +) + +// RegionInformer provides access to a shared informer and lister for +// Regions. +type RegionInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.RegionLister +} + +type regionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRegionInformer constructs a new informer for Region 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 NewRegionInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRegionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRegionInformer constructs a new informer for Region 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 NewFilteredRegionInformer(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().Regions(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Regions(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Regions(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Regions(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.Region{}, + resyncPeriod, + indexers, + ) +} + +func (f *regionInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRegionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *regionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.Region{}, f.defaultInformer) +} + +func (f *regionInformer) Lister() apiv1alpha1.RegionLister { + return apiv1alpha1.NewRegionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index f58420886..64731e3df 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -77,6 +77,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Ports().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("projects"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Projects().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("regions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Regions().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("roles"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Roles().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("roleassignments"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 76fec1603..1d9371c39 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -114,6 +114,14 @@ type ProjectListerExpansion interface{} // ProjectNamespaceLister. type ProjectNamespaceListerExpansion interface{} +// RegionListerExpansion allows custom methods to be added to +// RegionLister. +type RegionListerExpansion interface{} + +// RegionNamespaceListerExpansion allows custom methods to be added to +// RegionNamespaceLister. +type RegionNamespaceListerExpansion interface{} + // RoleListerExpansion allows custom methods to be added to // RoleLister. type RoleListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/region.go b/pkg/clients/listers/api/v1alpha1/region.go new file mode 100644 index 000000000..2eafd596d --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/region.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" +) + +// RegionLister helps list Regions. +// All objects returned here must be treated as read-only. +type RegionLister interface { + // List lists all Regions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Region, err error) + // Regions returns an object that can list and get Regions. + Regions(namespace string) RegionNamespaceLister + RegionListerExpansion +} + +// regionLister implements the RegionLister interface. +type regionLister struct { + listers.ResourceIndexer[*apiv1alpha1.Region] +} + +// NewRegionLister returns a new RegionLister. +func NewRegionLister(indexer cache.Indexer) RegionLister { + return ®ionLister{listers.New[*apiv1alpha1.Region](indexer, apiv1alpha1.Resource("region"))} +} + +// Regions returns an object that can list and get Regions. +func (s *regionLister) Regions(namespace string) RegionNamespaceLister { + return regionNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Region](s.ResourceIndexer, namespace)} +} + +// RegionNamespaceLister helps list and get Regions. +// All objects returned here must be treated as read-only. +type RegionNamespaceLister interface { + // List lists all Regions in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Region, err error) + // Get retrieves the Region from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.Region, error) + RegionNamespaceListerExpansion +} + +// regionNamespaceLister implements the RegionNamespaceLister +// interface. +type regionNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.Region] +} diff --git a/test/apivalidations/region_test.go b/test/apivalidations/region_test.go new file mode 100644 index 000000000..94284bcf4 --- /dev/null +++ b/test/apivalidations/region_test.go @@ -0,0 +1,105 @@ +/* +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 apivalidations + +import ( + . "github.com/onsi/ginkgo/v2" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +const ( + regionName = "region" + regionID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func regionStub(namespace *corev1.Namespace) *orcv1alpha1.Region { + obj := &orcv1alpha1.Region{} + obj.Name = regionName + obj.Namespace = namespace.Name + return obj +} + +func testRegionResource() *applyconfigv1alpha1.RegionResourceSpecApplyConfiguration { + return applyconfigv1alpha1.RegionResourceSpec().WithName("TestRegion") +} + +func baseRegionPatch(obj client.Object) *applyconfigv1alpha1.RegionApplyConfiguration { + return applyconfigv1alpha1.Region(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.RegionSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testRegionImport() *applyconfigv1alpha1.RegionImportApplyConfiguration { + return applyconfigv1alpha1.RegionImport().WithID(regionID) +} + +var _ = Describe("ORC Region API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.RegionApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return regionStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.RegionApplyConfiguration { + return baseRegionPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithResource(testRegionResource()) + }, + applyImport: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithImport(testRegionImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RegionImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RegionImport().WithFilter(applyconfigv1alpha1.RegionFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RegionImport().WithFilter(applyconfigv1alpha1.RegionFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.RegionApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Region).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Region).Spec.ManagedOptions.OnDelete + }, + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 3cfdb8ac9..84a0025c5 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -22,6 +22,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Network](#network) - [Port](#port) - [Project](#project) +- [Region](#region) - [Role](#role) - [RoleAssignment](#roleassignment) - [Router](#router) @@ -519,6 +520,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RegionSpec](#regionspec) - [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) @@ -2294,6 +2296,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RegionSpec](#regionspec) - [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) @@ -2335,6 +2338,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RegionSpec](#regionspec) - [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) @@ -2644,6 +2648,8 @@ _Appears in:_ - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) - [PortResourceSpec](#portresourcespec) +- [RegionFilter](#regionfilter) +- [RegionResourceSpec](#regionresourcespec) - [RouterFilter](#routerfilter) - [RouterResourceSpec](#routerresourcespec) - [SecurityGroupFilter](#securitygroupfilter) @@ -3124,6 +3130,137 @@ _Appears in:_ | `segmentationID` _integer_ | segmentationID is the ID of the isolated segment on the
physical network. The network_type attribute defines the
segmentation model. For example, if the network_type value is vlan,
this ID is a vlan identifier. If the network_type value is gre, this
ID is a gre key. | | Optional: \{\}
| +#### Region + + + +Region is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `Region` | | | +| `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` _[RegionSpec](#regionspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RegionStatus](#regionstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### RegionFilter + + + +RegionFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [RegionImport](#regionimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| + + +#### RegionImport + + + +RegionImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [RegionSpec](#regionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the name of an existing resource. Note: This resource uses
the resource name as the unique identifier, not a UUID.
When specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | MaxLength: 1024
Optional: \{\}
| +| `filter` _[RegionFilter](#regionfilter)_ | 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: \{\}
| + + +#### RegionResourceSpec + + + +RegionResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [RegionSpec](#regionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the ID of the Region. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Required: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| + + +#### RegionResourceStatus + + + +RegionResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [RegionStatus](#regionstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is the ID for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| + + +#### RegionSpec + + + +RegionSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [Region](#region) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[RegionImport](#regionimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[RegionResourceSpec](#regionresourcespec)_ | 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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| + + +#### RegionStatus + + + +RegionStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [Region](#region) + +| 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` _[RegionResourceStatus](#regionresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| + + #### Role