diff --git a/PROJECT b/PROJECT index 8d6e2c12d..5a68e31e0 100644 --- a/PROJECT +++ b/PROJECT @@ -144,6 +144,14 @@ resources: kind: Subnet path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: User + 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 17597fdbb..4ff4ddcbd 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ kubectl delete -f $ORC_RELEASE | server group | | ✔ | ✔ | | service | | ✔ | ✔ | | subnet | | ◐ | ◐ | +| user | | ◐ | ◐ | | volume | | ◐ | ◐ | | volume type | | ◐ | ◐ | diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go new file mode 100644 index 000000000..a11b5b0e6 --- /dev/null +++ b/api/v1alpha1/user_types.go @@ -0,0 +1,94 @@ +/* +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 + +// UserResourceSpec contains the desired state of the resource. +type UserResourceSpec struct { + // name will be the name of the created resource. If not specified, the + // name of the ORC object will be used. + // +optional + Name *KeystoneName `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"` + + // defaultProjectID is the ID of the default project for the user + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // password is created by the user + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Password *string `json:"password,omitempty"` + + // enabled indicates whether the user is enabled or not + // +kubebuilder:default=true + // +optional + Enabled *bool `json:"enabled,omitempty"` +} + +// UserFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type UserFilter struct { + // name of the existing resource + // +optional + Name *KeystoneName `json:"name,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` +} + +// UserResourceStatus represents the observed state of the resource. +type UserResourceStatus struct { + // name is a Human-readable name for the resource. Might not be unique. + // +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"` + + // defaultProjectID is the ID of the default project for the user + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + DefaultProjectID string `json:"defaultProjectID,omitempty"` + + // domainID is the ID of the Domain to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + DomainID string `json:"domainID,omitempty"` + + // enabled indicates whether the user is enabled or not + // +kubebuilder:default=true + // +optional + Enabled bool `json:"enabled,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 74fd9dcdf..bb7c61bbf 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5014,6 +5014,33 @@ func (in *SubnetStatus) DeepCopy() *SubnetStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *User) DeepCopyInto(out *User) { + *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 User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *User) 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 *UserDataSpec) DeepCopyInto(out *UserDataSpec) { *out = *in @@ -5034,6 +5061,211 @@ func (in *UserDataSpec) DeepCopy() *UserDataSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserFilter) DeepCopyInto(out *UserFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(KeystoneName) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserFilter. +func (in *UserFilter) DeepCopy() *UserFilter { + if in == nil { + return nil + } + out := new(UserFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserImport) DeepCopyInto(out *UserImport) { + *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(UserFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserImport. +func (in *UserImport) DeepCopy() *UserImport { + if in == nil { + return nil + } + out := new(UserImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserList) DeepCopyInto(out *UserList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. +func (in *UserList) DeepCopy() *UserList { + if in == nil { + return nil + } + out := new(UserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UserList) 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 *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(KeystoneName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.DefaultProjectID != nil { + in, out := &in.DefaultProjectID, &out.DefaultProjectID + *out = new(string) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(string) + **out = **in + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceSpec. +func (in *UserResourceSpec) DeepCopy() *UserResourceSpec { + if in == nil { + return nil + } + out := new(UserResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserResourceStatus) DeepCopyInto(out *UserResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceStatus. +func (in *UserResourceStatus) DeepCopy() *UserResourceStatus { + if in == nil { + return nil + } + out := new(UserResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(UserImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(UserResourceSpec) + (*in).DeepCopyInto(*out) + } + if in.ManagedOptions != nil { + in, out := &in.ManagedOptions, &out.ManagedOptions + *out = new(ManagedOptions) + **out = **in + } + out.CloudCredentialsRef = in.CloudCredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserStatus) DeepCopyInto(out *UserStatus) { + *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(UserResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. +func (in *UserStatus) DeepCopy() *UserStatus { + if in == nil { + return nil + } + out := new(UserStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Volume) DeepCopyInto(out *Volume) { *out = *in diff --git a/api/v1alpha1/zz_generated.user-resource.go b/api/v1alpha1/zz_generated.user-resource.go new file mode 100644 index 000000000..219144061 --- /dev/null +++ b/api/v1alpha1/zz_generated.user-resource.go @@ -0,0 +1,177 @@ +// 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" +) + +// UserImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type UserImport struct { + // id contains the unique identifier of an existing OpenStack resource. Note + // that 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. + // +optional + // +kubebuilder:validation:Format:=uuid + ID *string `json:"id,omitempty"` + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *UserFilter `json:"filter,omitempty"` +} + +// UserSpec 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 UserSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *UserImport `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 *UserResourceSpec `json:"resource,omitempty"` + + // managementPolicy defines how ORC will treat the object. Valid values are + // `managed`: ORC will create, update, and delete the resource; `unmanaged`: + // ORC will import an existing resource, and will not apply updates to it or + // delete it. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable" + // +kubebuilder:default:=managed + // +optional + ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"` + + // managedOptions specifies options which may be applied to managed objects. + // +optional + ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + + // cloudCredentialsRef points to a secret containing OpenStack credentials + // +required + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` +} + +// UserStatus defines the observed state of an ORC resource. +type UserStatus 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. + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *UserResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &User{} + +func (i *User) 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" + +// User is the Schema for an ORC resource. +type User struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +optional + Spec UserSpec `json:"spec,omitempty"` + + // status defines the observed state of the resource. + // +optional + Status UserStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// UserList contains a list of User. +type UserList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of User. + // +required + Items []User `json:"items"` +} + +func (l *UserList) GetItems() []User { + return l.Items +} + +func init() { + SchemeBuilder.Register(&User{}, &UserList{}) +} + +func (i *User) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &User{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 293aa2bab..405248e6f 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -45,6 +45,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/servergroup" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/service" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnet" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/user" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volume" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volumetype" internalmanager "github.com/k-orc/openstack-resource-controller/v2/internal/manager" @@ -126,6 +127,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), + user.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3b90d275e..03833c349 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -201,7 +201,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.User": schema_openstack_resource_controller_v2_api_v1alpha1_User(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserFilter": schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserImport": schema_openstack_resource_controller_v2_api_v1alpha1_UserImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserList": schema_openstack_resource_controller_v2_api_v1alpha1_UserList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserStatus": schema_openstack_resource_controller_v2_api_v1alpha1_UserStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Volume": schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeAttachmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeAttachmentStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeFilter(ref), @@ -9741,6 +9749,56 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "User 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.UserSpec"), + }, + }, + "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.UserStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -9760,6 +9818,315 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserFilter 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: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserImport 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 unique identifier of an existing OpenStack resource. Note that 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.UserFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserList contains a list of User.", + 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 User.", + 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.User"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserResourceSpec contains the desired state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name will be the name of the created resource. If not specified, the name of the ORC object will be used.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "defaultProjectID": { + SchemaProps: spec.SchemaProps{ + Description: "defaultProjectID is the ID of the default project for the user", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "password": { + SchemaProps: spec.SchemaProps{ + Description: "password is created by the user", + Type: []string{"string"}, + Format: "", + }, + }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled indicates whether the user is enabled or not", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserResourceStatus represents the observed state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a Human-readable name for the resource. Might not be unique.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "defaultProjectID": { + SchemaProps: spec.SchemaProps{ + Description: "defaultProjectID is the ID of the default project for the user", + Type: []string{"string"}, + Format: "", + }, + }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the ID of the Domain to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled indicates whether the user is enabled or not", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserSpec 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.UserImport"), + }, + }, + "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.UserResourceSpec"), + }, + }, + "managementPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.", + Type: []string{"string"}, + Format: "", + }, + }, + "managedOptions": { + SchemaProps: spec.SchemaProps{ + Description: "managedOptions specifies options which may be applied to managed objects.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), + }, + }, + "cloudCredentialsRef": { + SchemaProps: spec.SchemaProps{ + Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"), + }, + }, + }, + Required: []string{"cloudCredentialsRef"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserStatus 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.UserResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Volume(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 b3f4ece76..38f78cb67 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -144,6 +144,9 @@ var resources []templateFields = []templateFields{ Name: "Subnet", ExistingOSClient: true, }, + { + Name: "User", + }, { Name: "Volume", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml new file mode 100644 index 000000000..4179d1735 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -0,0 +1,325 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: users.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: User + listKind: UserList + plural: users + singular: user + 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: User 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: + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 64 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that 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. + format: uuid + 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: + defaultProjectID: + description: defaultProjectID is the ID of the default project + for the user + maxLength: 255 + minLength: 1 + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: domainRef is immutable + rule: self == oldSelf + enabled: + default: true + description: enabled indicates whether the user is enabled or + not + type: boolean + name: + description: |- + name will be the name of the created resource. If not specified, the + name of the ORC object will be used. + maxLength: 64 + minLength: 1 + type: string + password: + description: password is created by the user + maxLength: 255 + minLength: 1 + type: string + type: object + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + defaultProjectID: + description: defaultProjectID is the ID of the default project + for the user + maxLength: 255 + minLength: 1 + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + domainID: + description: domainID is the ID of the Domain to which the resource + is associated. + maxLength: 1024 + type: string + enabled: + default: true + description: enabled indicates whether the user is enabled or + not + type: boolean + name: + description: name is a Human-readable name for the resource. Might + not be unique. + maxLength: 1024 + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 33b8c85e2..844a3bfbd 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -20,6 +20,7 @@ resources: - bases/openstack.k-orc.cloud_servergroups.yaml - bases/openstack.k-orc.cloud_services.yaml - bases/openstack.k-orc.cloud_subnets.yaml +- bases/openstack.k-orc.cloud_users.yaml - bases/openstack.k-orc.cloud_volumes.yaml - bases/openstack.k-orc.cloud_volumetypes.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 0c5f1c0ea..2280d1a10 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -104,6 +104,11 @@ spec: kind: Subnet name: subnets.openstack.k-orc.cloud version: v1alpha1 + - description: User is the Schema for an ORC resource. + displayName: User + kind: User + name: users.openstack.k-orc.cloud + version: v1alpha1 - description: Volume is the Schema for an ORC resource. displayName: Volume kind: Volume diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5a0a7443b..964f2d263 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -34,6 +34,7 @@ rules: - servers - services - subnets + - users - volumes - volumetypes verbs: @@ -64,6 +65,7 @@ rules: - servers/status - services/status - subnets/status + - users/status - volumes/status - volumetypes/status verbs: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index dac467c69..9f202d67f 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -18,6 +18,7 @@ resources: - openstack_v1alpha1_servergroup.yaml - openstack_v1alpha1_service.yaml - openstack_v1alpha1_subnet.yaml +- openstack_v1alpha1_user.yaml - openstack_v1alpha1_volume.yaml - openstack_v1alpha1_volumetype.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml new file mode 100644 index 000000000..a20132d05 --- /dev/null +++ b/config/samples/openstack_v1alpha1_user.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + enabled: true + defaultProjectID: sample + password: sample + description: Sample User + diff --git a/internal/controllers/group/tests/group-import/01-assert.yaml b/internal/controllers/group/tests/group-import/01-assert.yaml index f405a1228..8a7ecebf0 100644 --- a/internal/controllers/group/tests/group-import/01-assert.yaml +++ b/internal/controllers/group/tests/group-import/01-assert.yaml @@ -16,7 +16,6 @@ status: resource: name: group-import-external-not-this-one description: Group group-import-external from "group-import" test - --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Group diff --git a/internal/controllers/group/tests/group-import/02-assert.yaml b/internal/controllers/group/tests/group-import/02-assert.yaml new file mode 100644 index 000000000..971a42e78 --- /dev/null +++ b/internal/controllers/group/tests/group-import/02-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: group-import-external + ref: group1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: group-import-external-not-this-one + ref: group2 +assertAll: + - celExpr: "group1.status.id != group2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: group-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 + diff --git a/internal/controllers/group/tests/group-import/03-create-resource.yaml b/internal/controllers/group/tests/group-import/02-create-resource.yaml similarity index 100% rename from internal/controllers/group/tests/group-import/03-create-resource.yaml rename to internal/controllers/group/tests/group-import/02-create-resource.yaml diff --git a/internal/controllers/group/tests/group-import/03-assert.yaml b/internal/controllers/group/tests/group-import/03-assert.yaml index 971a42e78..eabc745f4 100644 --- a/internal/controllers/group/tests/group-import/03-assert.yaml +++ b/internal/controllers/group/tests/group-import/03-assert.yaml @@ -3,28 +3,13 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group + kind: Domain name: group-import-external - ref: group1 + ref: domain1 - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group + kind: Domain name: group-import-external-not-this-one - ref: group2 + ref: domain2 assertAll: - - celExpr: "group1.status.id != group2.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: group-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 - + - celExpr: "domain1.status.resource.enabled == false" + - celExpr: "domain1.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/group/tests/group-import/04-disable-domain.yaml b/internal/controllers/group/tests/group-import/03-disable-domain.yaml similarity index 100% rename from internal/controllers/group/tests/group-import/04-disable-domain.yaml rename to internal/controllers/group/tests/group-import/03-disable-domain.yaml diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go new file mode 100644 index 000000000..52a24ed85 --- /dev/null +++ b/internal/controllers/user/actuator.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. +*/ + +package user + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + 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" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = users.User + + 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 userActuator struct { + osClient osclients.UserClient + k8sClient client.Client +} + +var _ createResourceActuator = userActuator{} +var _ deleteResourceActuator = userActuator{} + +func (userActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator userActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetUser(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator userActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + listOpts := users.ListOpts{ + Name: getResourceName(orcObject), + } + + return actuator.osClient.ListUsers(ctx, listOpts), true +} + +func (actuator userActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + + var reconcileStatus progress.ReconcileStatus + + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := users.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + DomainID: ptr.Deref(domain.Status.ID, ""), + } + + return actuator.osClient.ListUsers(ctx, listOpts), reconcileStatus +} + +func (actuator userActuator) 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")) + } + var reconcileStatus progress.ReconcileStatus + + var domainID string + if resource.DomainRef != nil { + domain, domainDepRS := domainDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) + if domain != nil { + domainID = ptr.Deref(domain.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := users.CreateOpts{ + Name: getResourceName(obj), + DefaultProjectID: ptr.Deref(resource.DefaultProjectID, ""), + DomainID: domainID, + Password: ptr.Deref(resource.Password, ""), + Enabled: resource.Enabled, + } + + osResource, err := actuator.osClient.CreateUser(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + 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 userActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteUser(ctx, resource.ID)) +} + +func (actuator userActuator) 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 := users.UpdateOpts{} + + handleNameUpdate(&updateOpts, obj, osResource) + handleDefaultProjectIDUpdate(&updateOpts, resource, osResource) + handleEnabledUpdate(&updateOpts, resource, osResource) + handlePasswordUpdate(&updateOpts, resource) + + 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.UpdateUser(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts users.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToUserUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["user"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *users.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = name + } +} + +func handleDefaultProjectIDUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + defaultProjectID := ptr.Deref(resource.DefaultProjectID, "") + if osResource.DefaultProjectID != defaultProjectID { + updateOpts.DefaultProjectID = defaultProjectID + } +} + +func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + enabled := ptr.Deref(resource.Enabled, true) + if osResource.Enabled != enabled { + updateOpts.Enabled = &enabled + } +} + +// Note: Openstack Identity API & users.User struct does not return passwords, +// we cannot compare the current state with the desired state. +// We will treat this as a "write-only" field and include it in the update if it is defined in the spec. +func handlePasswordUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT) { + if resource.Password != nil { + updateOpts.Password = ptr.Deref(resource.Password, "") + } +} + +func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type userHelperFactory struct{} + +var _ helperFactory = userHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.User, controller interfaces.ResourceController) (userActuator, 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 userActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return userActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewUserClient() + if err != nil { + return userActuator{}, progress.WrapError(err) + } + + return userActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (userHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return userAdapter{obj} +} + +func (userHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (userHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/user/actuator_test.go b/internal/controllers/user/actuator_test.go new file mode 100644 index 000000000..969b00d36 --- /dev/null +++ b/internal/controllers/user/actuator_test.go @@ -0,0 +1,174 @@ +/* +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 user + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + 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 users.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: users.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: users.UpdateOpts{Name: "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 TestHandleNameUpdate(t *testing.T) { + ptrToName := ptr.To[orcv1alpha1.KeystoneName] + testCases := []struct { + name string + newValue *orcv1alpha1.KeystoneName + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false}, + {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true}, + {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false}, + {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.User{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := users.UpdateOpts{} + handleNameUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} + +func TestHandleDefaultProjectIDUpdate(t *testing.T) { + ptrToDefaultProjectID := ptr.To[string] + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToDefaultProjectID("defProID"), existingValue: "defProID", expectChange: false}, + {name: "Different", newValue: ptrToDefaultProjectID("new-defProID"), existingValue: "defProID", expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: "defProID", expectChange: false}, + {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.UserResourceSpec{DefaultProjectID: tt.newValue} + osResource := &osResourceT{DefaultProjectID: tt.existingValue} + + updateOpts := users.UpdateOpts{} + handleDefaultProjectIDUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} + +func TestHandleEnabledUpdate(t *testing.T) { + ptrToBool := ptr.To[bool] + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical", newValue: ptrToBool(true), existingValue: true, expectChange: false}, + {name: "Different", newValue: ptrToBool(false), existingValue: true, expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: true, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.UserResourceSpec{Enabled: tt.newValue} + osResource := &users.User{Enabled: tt.existingValue} + + updateOpts := users.UpdateOpts{} + handleEnabledUpdate(&updateOpts, resource, osResource) + + if got, _ := needsUpdate(updateOpts); got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandlePasswordUpdate(t *testing.T) { + ptrToPassword := ptr.To[string] + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Value provided", newValue: ptrToPassword("new-pwd"), expectChange: true}, + {name: "No value provided", newValue: nil, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.UserResourceSpec{Password: tt.newValue} + + updateOpts := users.UpdateOpts{} + handlePasswordUpdate(&updateOpts, resource) + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go new file mode 100644 index 000000000..f5f6b8585 --- /dev/null +++ b/internal/controllers/user/controller.go @@ -0,0 +1,114 @@ +/* +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 user + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "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" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates" +) + +const controllerName = "user" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=users,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=users/status,verbs=get;update;patch + +type userReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return userReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (userReconcilerConstructor) GetName() string { + return controllerName +} + +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.DomainRef == nil { + return nil + } + return []string{string(*resource.Filter.DomainRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainImportWatchEventHandler, err := domainImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + For(&orcv1alpha1.User{}) + + if err := errors.Join( + domainDependency.AddToManager(ctx, mgr), + domainImportDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, userHelperFactory{}, userStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/user/status.go b/internal/controllers/user/status.go new file mode 100644 index 000000000..0d0f8da51 --- /dev/null +++ b/internal/controllers/user/status.go @@ -0,0 +1,66 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package user + +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 userStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.UserApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.UserStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.User, *osResourceT, *objectApplyT, *statusApplyT] = userStatusWriter{} + +func (userStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.User(name, namespace) +} + +func (userStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.User, 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 (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.UserResourceStatus(). + WithDomainID(osResource.DomainID). + WithName(osResource.Name). + WithEnabled(osResource.Enabled) + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + if osResource.DefaultProjectID != "" { + resourceStatus.WithDefaultProjectID(osResource.DefaultProjectID) + } + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/user/tests/user-create-full/00-assert.yaml b/internal/controllers/user/tests/user-create-full/00-assert.yaml new file mode 100644 index 000000000..e8d99d608 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/00-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-full +status: + resource: + name: user-create-full-override + enabled: True + defaultProjectID: role-create-full + 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: User + name: user-create-full + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-create-full + ref: domain +assertAll: + - celExpr: "user.status.id != ''" + - celExpr: "user.status.resource.domainID == domain.status.id" + diff --git a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml new file mode 100644 index 000000000..6ac6cbfd1 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: user-create-full-override + domainRef: user-create-full + defaultProjectID: role-create-full + enabled: True + + diff --git a/internal/controllers/user/tests/user-create-full/00-secret.yaml b/internal/controllers/user/tests/user-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-full/01-assert.yaml b/internal/controllers/user/tests/user-create-full/01-assert.yaml new file mode 100644 index 000000000..5ec861ec8 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/01-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-create-full + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml b/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml new file mode 100644 index 000000000..cfa5e50ed --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml @@ -0,0 +1,7 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-create-full +spec: + resource: + enabled: false diff --git a/internal/controllers/user/tests/user-create-full/README.md b/internal/controllers/user/tests/user-create-full/README.md new file mode 100644 index 000000000..516ade745 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/README.md @@ -0,0 +1,17 @@ +# Create a User with all the options + +## Step 00 + +Create a User 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. + +## Step 01 + +By default the enabled field is set to true, the enabled field needs to be disabled. + +Disabling the Domain is required before deletion in OpenStack. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml new file mode 100644 index 000000000..ad98e2b17 --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-minimal +status: + resource: + name: user-create-minimal + 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: User + name: user-create-minimal + ref: user +assertAll: + - celExpr: "user.status.id != ''" + - celExpr: "user.status.resource.domainID == 'default'" + - celExpr: "!has(user.status.resource.defaultProjectID)" + - celExpr: "!has(user.spec.resource.password)" + + + + + diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..49cccdc44 --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/user/tests/user-create-minimal/00-secret.yaml b/internal/controllers/user/tests/user-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-minimal/01-assert.yaml b/internal/controllers/user/tests/user-create-minimal/01-assert.yaml new file mode 100644 index 000000000..549b42729 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user' in secret.metadata.finalizers" diff --git a/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml b/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-minimal/README.md b/internal/controllers/user/tests/user-create-minimal/README.md new file mode 100644 index 000000000..4d3dd61ff --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a User with the minimum options + +## Step 00 + +Create a minimal User, 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/user/tests/user-dependency/00-assert.yaml b/internal/controllers/user/tests/user-dependency/00-assert.yaml new file mode 100644 index 000000000..7695e295f --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/user-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/user-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/user-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/user-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..ebc11a3ae --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-dependency + enabled: True + defaultProjectID: "user-dependency" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-secret +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: user-dependency + managementPolicy: managed + resource: + enabled: True + defaultProjectID: "user-dependency" diff --git a/internal/controllers/user/tests/user-dependency/00-secret.yaml b/internal/controllers/user/tests/user-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/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/user/tests/user-dependency/01-assert.yaml b/internal/controllers/user/tests/user-dependency/01-assert.yaml new file mode 100644 index 000000000..066d059fa --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/01-assert.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-secret +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: User +metadata: + name: user-dependency-no-domain +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: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-domain + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-dependency + ref: domain +assertAll: + - celExpr: "user.status.resource.domainID == domain.status.id" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..8630ab1ff --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic user-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} + diff --git a/internal/controllers/user/tests/user-dependency/02-assert.yaml b/internal/controllers/user/tests/user-dependency/02-assert.yaml new file mode 100644 index 000000000..a5a8dd7f7 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/02-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-dependency + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml b/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml new file mode 100644 index 000000000..494189845 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-dependency +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/03-assert.yaml b/internal/controllers/user/tests/user-dependency/03-assert.yaml new file mode 100644 index 000000000..a9809a450 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/03-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: user-dependency + ref: secret +assertAll: + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" diff --git a/internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml b/internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml new file mode 100644 index 000000000..c22da51c9 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete domain.openstack.k-orc.cloud user-dependency --wait=false + namespaced: true + - command: kubectl delete secret user-dependency --wait=false + namespaced: true diff --git a/internal/controllers/user/tests/user-dependency/04-assert.yaml b/internal/controllers/user/tests/user-dependency/04-assert.yaml new file mode 100644 index 000000000..2a2269f16 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/04-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get domain.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret user-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml new file mode 100644 index 000000000..380c7325b --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-domain diff --git a/internal/controllers/user/tests/user-dependency/README.md b/internal/controllers/user/tests/user-dependency/README.md new file mode 100644 index 000000000..e0cd02276 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/README.md @@ -0,0 +1,25 @@ +# Creation and deletion dependencies + +## Step 00 + +Create Users referencing non-existing resources. Each User is dependent on other non-existing resource. Verify that the Users are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the Users are available. + +## Step 02 + +Disable the domain dependency to allow KUTTL to cleanup resources without any issues. + +## Step 03 + +Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. + +## Step 04 + +Delete the Users and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/user/tests/user-import-dependency/00-assert.yaml b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml new file mode 100644 index 000000000..bdd2da07d --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..57710c1ba --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: user-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + domainRef: user-import-dependency diff --git a/internal/controllers/user/tests/user-import-dependency/00-secret.yaml b/internal/controllers/user/tests/user-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/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/user/tests/user-import-dependency/01-assert.yaml b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml new file mode 100644 index 000000000..b15197e11 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency-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 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..7154af7ba --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# This `user-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-dependency-not-this-one \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/02-assert.yaml b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml new file mode 100644 index 000000000..120234b66 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency + ref: user1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency-not-this-one + ref: user2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency + ref: domain +assertAll: + - celExpr: "user1.status.id != user2.status.id" + - celExpr: "user1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +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/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..9c40867fe --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-dependency-external + diff --git a/internal/controllers/user/tests/user-import-dependency/03-assert.yaml b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml new file mode 100644 index 000000000..32c8d86cd --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency-external + ref: domain1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency-not-this-one + ref: domain2 +assertAll: + - celExpr: "domain1.status.resource.enabled == false" + - celExpr: "domain2.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml b/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml new file mode 100644 index 000000000..9265b4158 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-external +spec: + resource: + enabled: false +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-not-this-one +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/04-assert.yaml b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml new file mode 100644 index 000000000..ed9b4d388 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get domain.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml b/internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml new file mode 100644 index 000000000..b94f3e171 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete domain.openstack.k-orc.cloud user-import-dependency + namespaced: true diff --git a/internal/controllers/user/tests/user-import-dependency/05-assert.yaml b/internal/controllers/user/tests/user-import-dependency/05-assert.yaml new file mode 100644 index 000000000..de589e4a8 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/05-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get user.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml b/internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml new file mode 100644 index 000000000..bf2482dc1 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency diff --git a/internal/controllers/user/tests/user-import-dependency/README.md b/internal/controllers/user/tests/user-import-dependency/README.md new file mode 100644 index 000000000..379dcf07f --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/README.md @@ -0,0 +1,33 @@ +# Check dependency handling for imported User + +## Step 00 + +Import a User that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the User is waiting for the dependency to be ready. + +## Step 01 + +Create a User matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a User matching the import filters. + +Verify that the observed status on the imported User corresponds to the spec of the created User. + +## Step 03 + +Disable the domain dependencies so KUTTL can clean the resources without failing. + +## Step 04 + +Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they +were imported resources and we only deleted the ORC representation of it. + +## Step 05 + +Delete the User and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/user/tests/user-import-error/00-assert.yaml b/internal/controllers/user/tests/user-import-error/00-assert.yaml new file mode 100644 index 000000000..87afc5cff --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User +metadata: + name: user-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/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml new file mode 100644 index 000000000..e828b9b40 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-error-external-1 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-error + enabled: True + defaultProjectID: user-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-error-external-2 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-error + enabled: True + defaultProjectID: user-import-error diff --git a/internal/controllers/user/tests/user-import-error/00-secret.yaml b/internal/controllers/user/tests/user-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-import-error/01-assert.yaml b/internal/controllers/user/tests/user-import-error/01-assert.yaml new file mode 100644 index 000000000..c8b93f22e --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import-error/01-import-resource.yaml b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml new file mode 100644 index 000000000..e617e9ad6 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + domainRef: user-import-error diff --git a/internal/controllers/user/tests/user-import-error/02-assert.yaml b/internal/controllers/user/tests/user-import-error/02-assert.yaml new file mode 100644 index 000000000..433b930c5 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/02-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-error + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml b/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml new file mode 100644 index 000000000..b1bf5bc9e --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-error +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/README.md b/internal/controllers/user/tests/user-import-error/README.md new file mode 100644 index 000000000..fa6760d49 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/README.md @@ -0,0 +1,17 @@ +# Import User with more than one matching resources + +## Step 00 + +Create two Users with identical specs. + +## Step 01 + +Ensure that an imported User with a filter matching the resources returns an error. + +## Step 02 + +Disable Domain dependency so KUTTL can cleanup resources without any issues. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/user/tests/user-import/00-assert.yaml b/internal/controllers/user/tests/user-import/00-assert.yaml new file mode 100644 index 000000000..305f139ff --- /dev/null +++ b/internal/controllers/user/tests/user-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml new file mode 100644 index 000000000..1226bfa18 --- /dev/null +++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: user-import-external + domainRef: user-import-external + diff --git a/internal/controllers/user/tests/user-import/00-secret.yaml b/internal/controllers/user/tests/user-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-import/01-assert.yaml b/internal/controllers/user/tests/user-import/01-assert.yaml new file mode 100644 index 000000000..22c089112 --- /dev/null +++ b/internal/controllers/user/tests/user-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: user-import-external-not-this-one + defaultProjectID: user-import-external-not-this-one + enabled: True +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..ef57e2118 --- /dev/null +++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# This `user-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: User +metadata: + name: user-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-external-not-this-one + defaultProjectID: user-import-external-not-this-one + enabled: True diff --git a/internal/controllers/user/tests/user-import/02-assert.yaml b/internal/controllers/user/tests/user-import/02-assert.yaml new file mode 100644 index 000000000..6b7a33e20 --- /dev/null +++ b/internal/controllers/user/tests/user-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-external + ref: user1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-external-not-this-one + ref: user2 +assertAll: + - celExpr: "user1.status.id != user2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: user-import-external + defaultProjectID: user-import-external + enabled: True \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml new file mode 100644 index 000000000..87a950ecf --- /dev/null +++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-external + defaultProjectID: user-import-external + enabled: True \ No newline at end of file diff --git a/internal/controllers/group/tests/group-import/04-assert.yaml b/internal/controllers/user/tests/user-import/03-assert.yaml similarity index 69% rename from internal/controllers/group/tests/group-import/04-assert.yaml rename to internal/controllers/user/tests/user-import/03-assert.yaml index eabc745f4..d4bc16611 100644 --- a/internal/controllers/group/tests/group-import/04-assert.yaml +++ b/internal/controllers/user/tests/user-import/03-assert.yaml @@ -4,12 +4,12 @@ kind: TestAssert resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain - name: group-import-external + name: user-import-external ref: domain1 - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain - name: group-import-external-not-this-one + name: user-import-external-not-this-one ref: domain2 assertAll: - celExpr: "domain1.status.resource.enabled == false" - - celExpr: "domain1.status.resource.enabled == false" \ No newline at end of file + - celExpr: "domain2.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/03-disable-domain.yaml b/internal/controllers/user/tests/user-import/03-disable-domain.yaml new file mode 100644 index 000000000..df756ce20 --- /dev/null +++ b/internal/controllers/user/tests/user-import/03-disable-domain.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external +spec: + resource: + enabled: false +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external-not-this-one +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/README.md b/internal/controllers/user/tests/user-import/README.md new file mode 100644 index 000000000..40e36a7f8 --- /dev/null +++ b/internal/controllers/user/tests/user-import/README.md @@ -0,0 +1,26 @@ +# Import User + +## Step 00 + +Import a user that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a user 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 + +Disable the first domain dependency so it can be deleted without issue by KUTTL during cleanup. + +## Step 03 + +Create a user matching the filter and verify that the observed status on the imported user corresponds to the spec of the created user. +Also, confirm that it does not adopt any user whose name is a superstring of its own. + +## Step 04 + +Disable the second domain dependency so it can be deleted without issue by KUTTL during cleanup. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml new file mode 100644 index 000000000..cc61b3889 --- /dev/null +++ b/internal/controllers/user/tests/user-update/00-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-update + ref: user +assertAll: + - celExpr: "user.status.id != ''" + - celExpr: "user.status.resource.domainID == 'default'" + - celExpr: "!has(user.status.resource.defaultProjectID)" + - celExpr: "!has(user.spec.resource.password)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-update + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml new file mode 100644 index 000000000..961e04d8d --- /dev/null +++ b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/user/tests/user-update/00-prerequisites.yaml b/internal/controllers/user/tests/user-update/00-prerequisites.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-update/00-prerequisites.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/user/tests/user-update/01-assert.yaml b/internal/controllers/user/tests/user-update/01-assert.yaml new file mode 100644 index 000000000..70bce782f --- /dev/null +++ b/internal/controllers/user/tests/user-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-update-updated + enabled: True + defaultProjectID: user-updated + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml new file mode 100644 index 000000000..d760a1407 --- /dev/null +++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +spec: + resource: + name: user-update-updated + enabled: True + defaultProjectID: user-updated diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml new file mode 100644 index 000000000..c25c49807 --- /dev/null +++ b/internal/controllers/user/tests/user-update/02-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-update + ref: user +assertAll: + - celExpr: "user.status.resource.domainID == 'default'" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-update + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/user/tests/user-update/02-reverted-resource.yaml b/internal/controllers/user/tests/user-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-update/README.md b/internal/controllers/user/tests/user-update/README.md new file mode 100644 index 000000000..425c24c0f --- /dev/null +++ b/internal/controllers/user/tests/user-update/README.md @@ -0,0 +1,17 @@ +# Update User + +## Step 00 + +Create a User 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/user/zz_generated.adapter.go b/internal/controllers/user/zz_generated.adapter.go new file mode 100644 index 000000000..718a1ef46 --- /dev/null +++ b/internal/controllers/user/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 user + +import ( + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" +) + +// Fundamental types +type ( + orcObjectT = orcv1alpha1.User + orcObjectListT = orcv1alpha1.UserList + resourceSpecT = orcv1alpha1.UserResourceSpec + filterT = orcv1alpha1.UserFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = userAdapter +) + +type userAdapter struct { + *orcv1alpha1.User +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.User +} + +func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return f.Spec.ManagementPolicy +} + +func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return f.Spec.ManagedOptions +} + +func (f adapterT) GetStatusID() *string { + return f.Status.ID +} + +func (f adapterT) GetResourceSpec() *resourceSpecT { + return f.Spec.Resource +} + +func (f adapterT) GetImportID() *string { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.ID +} + +func (f adapterT) GetImportFilter() *filterT { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.Filter +} + +// getResourceName returns the name of the OpenStack resource we should use. +// This method is not implemented as part of APIObjectAdapter as it is intended +// to be used by resource actuators, which don't use the adapter. +func getResourceName(orcObject orcObjectPT) string { + if orcObject.Spec.Resource.Name != nil { + return string(*orcObject.Spec.Resource.Name) + } + return orcObject.Name +} diff --git a/internal/controllers/user/zz_generated.controller.go b/internal/controllers/user/zz_generated.controller.go new file mode 100644 index 000000000..667cf95e8 --- /dev/null +++ b/internal/controllers/user/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 user + +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 0da3b97ba..c0b1e2527 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -50,6 +50,9 @@ import ( //go:generate mockgen -package mock -destination=service.go -source=../service.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ServiceClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt service.go > _service.go && mv _service.go service.go" +//go:generate mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt user.go > _user.go && mv _user.go user.go" + //go:generate mockgen -package mock -destination=volume.go -source=../volume.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock VolumeClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt volume.go > _volume.go && mv _volume.go volume.go" diff --git a/internal/osclients/mock/user.go b/internal/osclients/mock/user.go new file mode 100644 index 000000000..7ce0e5cd1 --- /dev/null +++ b/internal/osclients/mock/user.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: ../user.go +// +// Generated by this command: +// +// mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + users "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + gomock "go.uber.org/mock/gomock" +) + +// MockUserClient is a mock of UserClient interface. +type MockUserClient struct { + ctrl *gomock.Controller + recorder *MockUserClientMockRecorder + isgomock struct{} +} + +// MockUserClientMockRecorder is the mock recorder for MockUserClient. +type MockUserClientMockRecorder struct { + mock *MockUserClient +} + +// NewMockUserClient creates a new mock instance. +func NewMockUserClient(ctrl *gomock.Controller) *MockUserClient { + mock := &MockUserClient{ctrl: ctrl} + mock.recorder = &MockUserClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUserClient) EXPECT() *MockUserClientMockRecorder { + return m.recorder +} + +// CreateUser mocks base method. +func (m *MockUserClient) CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateUser", ctx, opts) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateUser indicates an expected call of CreateUser. +func (mr *MockUserClientMockRecorder) CreateUser(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockUserClient)(nil).CreateUser), ctx, opts) +} + +// DeleteUser mocks base method. +func (m *MockUserClient) DeleteUser(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteUser", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteUser indicates an expected call of DeleteUser. +func (mr *MockUserClientMockRecorder) DeleteUser(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUser", reflect.TypeOf((*MockUserClient)(nil).DeleteUser), ctx, resourceID) +} + +// GetUser mocks base method. +func (m *MockUserClient) GetUser(ctx context.Context, resourceID string) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", ctx, resourceID) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser. +func (mr *MockUserClientMockRecorder) GetUser(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserClient)(nil).GetUser), ctx, resourceID) +} + +// ListUsers mocks base method. +func (m *MockUserClient) ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUsers", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*users.User, error]) + return ret0 +} + +// ListUsers indicates an expected call of ListUsers. +func (mr *MockUserClientMockRecorder) ListUsers(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserClient)(nil).ListUsers), ctx, listOpts) +} + +// UpdateUser mocks base method. +func (m *MockUserClient) UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUser", ctx, id, opts) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateUser indicates an expected call of UpdateUser. +func (mr *MockUserClientMockRecorder) UpdateUser(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUserClient)(nil).UpdateUser), ctx, id, opts) +} diff --git a/internal/osclients/user.go b/internal/osclients/user.go new file mode 100644 index 000000000..5bf564574 --- /dev/null +++ b/internal/osclients/user.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/users" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type UserClient interface { + ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] + CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) + DeleteUser(ctx context.Context, resourceID string) error + GetUser(ctx context.Context, resourceID string) (*users.User, error) + UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) +} + +type userClient struct{ client *gophercloud.ServiceClient } + +// NewUserClient returns a new OpenStack client. +func NewUserClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (UserClient, 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 user service client: %v", err) + } + + return &userClient{client}, nil +} + +func (c userClient) ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] { + pager := users.List(c.client, listOpts) + return func(yield func(*users.User, error) bool) { + _ = pager.EachPage(ctx, yieldPage(users.ExtractUsers, yield)) + } +} + +func (c userClient) CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) { + return users.Create(ctx, c.client, opts).Extract() +} + +func (c userClient) DeleteUser(ctx context.Context, resourceID string) error { + return users.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c userClient) GetUser(ctx context.Context, resourceID string) (*users.User, error) { + return users.Get(ctx, c.client, resourceID).Extract() +} + +func (c userClient) UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) { + return users.Update(ctx, c.client, id, opts).Extract() +} + +type userErrorClient struct{ error } + +// NewUserErrorClient returns a UserClient in which every method returns the given error. +func NewUserErrorClient(e error) UserClient { + return userErrorClient{e} +} + +func (e userErrorClient) ListUsers(_ context.Context, _ users.ListOptsBuilder) iter.Seq2[*users.User, error] { + return func(yield func(*users.User, error) bool) { + yield(nil, e.error) + } +} + +func (e userErrorClient) CreateUser(_ context.Context, _ users.CreateOptsBuilder) (*users.User, error) { + return nil, e.error +} + +func (e userErrorClient) DeleteUser(_ context.Context, _ string) error { + return e.error +} + +func (e userErrorClient) GetUser(_ context.Context, _ string) (*users.User, error) { + return nil, e.error +} + +func (e userErrorClient) UpdateUser(_ context.Context, _ string, _ users.UpdateOptsBuilder) (*users.User, error) { + return nil, e.error +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index ef959fae5..b69d5bd84 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -34,18 +34,18 @@ import ( // MockScopeFactory implements both the ScopeFactory and ClientScope interfaces. It can be used in place of the default ProviderScopeFactory // when we want to use mocked service clients which do not attempt to connect to a running OpenStack cloud. type MockScopeFactory struct { - ComputeClient *mock.MockComputeClient - DomainClient *mock.MockDomainClient - GroupClient *mock.MockGroupClient - IdentityClient *mock.MockIdentityClient - ImageClient *mock.MockImageClient - KeyPairClient *mock.MockKeyPairClient - NetworkClient *mock.MockNetworkClient - RoleClient *mock.MockRoleClient - ServiceClient *mock.MockServiceClient - VolumeClient *mock.MockVolumeClient - VolumeTypeClient *mock.MockVolumeTypeClient - + ComputeClient *mock.MockComputeClient + DomainClient *mock.MockDomainClient + GroupClient *mock.MockGroupClient + IdentityClient *mock.MockIdentityClient + ImageClient *mock.MockImageClient + KeyPairClient *mock.MockKeyPairClient + NetworkClient *mock.MockNetworkClient + RoleClient *mock.MockRoleClient + ServiceClient *mock.MockServiceClient + UserClient *mock.MockUserClient + VolumeClient *mock.MockVolumeClient + VolumeTypeClient *mock.MockVolumeTypeClient clientScopeCreateError error } @@ -59,6 +59,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { networkClient := mock.NewMockNetworkClient(mockCtrl) roleClient := mock.NewMockRoleClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) + userClient := mock.NewMockUserClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) @@ -72,6 +73,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { NetworkClient: networkClient, RoleClient: roleClient, ServiceClient: serviceClient, + UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, } @@ -132,6 +134,10 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, nil } +func (f *MockScopeFactory) NewUserClient() (osclients.UserClient, error) { + return f.UserClient, nil +} + func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) { return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index 65670ba60..aba7b0a5e 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -181,6 +181,10 @@ func (s *providerScope) NewRoleClient() (clients.RoleClient, error) { return clients.NewRoleClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewUserClient() (clients.UserClient, error) { + return clients.NewUserClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) ExtractToken() (*tokens.Token, error) { client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{}) if err != nil { diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 7da50dc8f..b4e6c46df 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -57,6 +57,7 @@ type Scope interface { NewNetworkClient() (osclients.NetworkClient, error) NewRoleClient() (osclients.RoleClient, error) NewServiceClient() (osclients.ServiceClient, error) + NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) ExtractToken() (*tokens.Token, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d499782e6..1b7d25ce8 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -19,6 +19,7 @@ testDirs: - ./internal/controllers/servergroup/tests/ - ./internal/controllers/service/tests/ - ./internal/controllers/subnet/tests/ +- ./internal/controllers/user/tests/ - ./internal/controllers/volume/tests/ - ./internal/controllers/volumetype/tests/ timeout: 240 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/user.go b/pkg/clients/applyconfiguration/api/v1alpha1/user.go new file mode 100644 index 000000000..8c7077cc4 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/user.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" +) + +// UserApplyConfiguration represents a declarative configuration of the User type for use +// with apply. +type UserApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *UserSpecApplyConfiguration `json:"spec,omitempty"` + Status *UserStatusApplyConfiguration `json:"status,omitempty"` +} + +// User constructs a declarative configuration of the User type for use with +// apply. +func User(name, namespace string) *UserApplyConfiguration { + b := &UserApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("User") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractUser extracts the applied configuration owned by fieldManager from +// user. If no managedFields are found in user for fieldManager, a +// UserApplyConfiguration 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. +// user must be a unmodified User API object that was retrieved from the Kubernetes API. +// ExtractUser 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 ExtractUser(user *apiv1alpha1.User, fieldManager string) (*UserApplyConfiguration, error) { + return extractUser(user, fieldManager, "") +} + +// ExtractUserStatus is the same as ExtractUser except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractUserStatus(user *apiv1alpha1.User, fieldManager string) (*UserApplyConfiguration, error) { + return extractUser(user, fieldManager, "status") +} + +func extractUser(user *apiv1alpha1.User, fieldManager string, subresource string) (*UserApplyConfiguration, error) { + b := &UserApplyConfiguration{} + err := managedfields.ExtractInto(user, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.User"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(user.Name) + b.WithNamespace(user.Namespace) + + b.WithKind("User") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b UserApplyConfiguration) 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 *UserApplyConfiguration) WithKind(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithAPIVersion(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithName(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithGenerateName(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithNamespace(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithUID(value types.UID) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithResourceVersion(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithGeneration(value int64) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithCreationTimestamp(value metav1.Time) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithLabels(entries map[string]string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithAnnotations(entries map[string]string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithFinalizers(values ...string) *UserApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *UserApplyConfiguration) 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 *UserApplyConfiguration) WithSpec(value *UserSpecApplyConfiguration) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithStatus(value *UserStatusApplyConfiguration) *UserApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *UserApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *UserApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *UserApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *UserApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go new file mode 100644 index 000000000..68a58f350 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.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" +) + +// UserFilterApplyConfiguration represents a declarative configuration of the UserFilter type for use +// with apply. +type UserFilterApplyConfiguration struct { + Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// UserFilterApplyConfiguration constructs a declarative configuration of the UserFilter type for use with +// apply. +func UserFilter() *UserFilterApplyConfiguration { + return &UserFilterApplyConfiguration{} +} + +// 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 *UserFilterApplyConfiguration) WithName(value apiv1alpha1.KeystoneName) *UserFilterApplyConfiguration { + b.Name = &value + return b +} + +// WithDomainRef sets the DomainRef 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 DomainRef field is set to the value of the last call. +func (b *UserFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *UserFilterApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/userimport.go new file mode 100644 index 000000000..4497cbde2 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userimport.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 + +// UserImportApplyConfiguration represents a declarative configuration of the UserImport type for use +// with apply. +type UserImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *UserFilterApplyConfiguration `json:"filter,omitempty"` +} + +// UserImportApplyConfiguration constructs a declarative configuration of the UserImport type for use with +// apply. +func UserImport() *UserImportApplyConfiguration { + return &UserImportApplyConfiguration{} +} + +// 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 *UserImportApplyConfiguration) WithID(value string) *UserImportApplyConfiguration { + 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 *UserImportApplyConfiguration) WithFilter(value *UserFilterApplyConfiguration) *UserImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go new file mode 100644 index 000000000..1cf5435b6 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go @@ -0,0 +1,88 @@ +/* +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" +) + +// UserResourceSpecApplyConfiguration represents a declarative configuration of the UserResourceSpec type for use +// with apply. +type UserResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + Password *string `json:"password,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// UserResourceSpecApplyConfiguration constructs a declarative configuration of the UserResourceSpec type for use with +// apply. +func UserResourceSpec() *UserResourceSpecApplyConfiguration { + return &UserResourceSpecApplyConfiguration{} +} + +// 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 *UserResourceSpecApplyConfiguration) WithName(value apiv1alpha1.KeystoneName) *UserResourceSpecApplyConfiguration { + 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 *UserResourceSpecApplyConfiguration) WithDescription(value string) *UserResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithDefaultProjectID sets the DefaultProjectID 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 DefaultProjectID field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithDefaultProjectID(value string) *UserResourceSpecApplyConfiguration { + b.DefaultProjectID = &value + return b +} + +// WithDomainRef sets the DomainRef 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 DomainRef field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *UserResourceSpecApplyConfiguration { + b.DomainRef = &value + return b +} + +// WithPassword sets the Password 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 Password field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithPassword(value string) *UserResourceSpecApplyConfiguration { + b.Password = &value + return b +} + +// WithEnabled sets the Enabled 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 Enabled field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithEnabled(value bool) *UserResourceSpecApplyConfiguration { + b.Enabled = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go new file mode 100644 index 000000000..bbbf071de --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go @@ -0,0 +1,75 @@ +/* +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 + +// UserResourceStatusApplyConfiguration represents a declarative configuration of the UserResourceStatus type for use +// with apply. +type UserResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + DomainID *string `json:"domainID,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// UserResourceStatusApplyConfiguration constructs a declarative configuration of the UserResourceStatus type for use with +// apply. +func UserResourceStatus() *UserResourceStatusApplyConfiguration { + return &UserResourceStatusApplyConfiguration{} +} + +// 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 *UserResourceStatusApplyConfiguration) WithName(value string) *UserResourceStatusApplyConfiguration { + 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 *UserResourceStatusApplyConfiguration) WithDescription(value string) *UserResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithDefaultProjectID sets the DefaultProjectID 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 DefaultProjectID field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithDefaultProjectID(value string) *UserResourceStatusApplyConfiguration { + b.DefaultProjectID = &value + return b +} + +// WithDomainID sets the DomainID 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 DomainID field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithDomainID(value string) *UserResourceStatusApplyConfiguration { + b.DomainID = &value + return b +} + +// WithEnabled sets the Enabled 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 Enabled field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithEnabled(value bool) *UserResourceStatusApplyConfiguration { + b.Enabled = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go new file mode 100644 index 000000000..fadcb620b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// UserSpecApplyConfiguration represents a declarative configuration of the UserSpec type for use +// with apply. +type UserSpecApplyConfiguration struct { + Import *UserImportApplyConfiguration `json:"import,omitempty"` + Resource *UserResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// UserSpecApplyConfiguration constructs a declarative configuration of the UserSpec type for use with +// apply. +func UserSpec() *UserSpecApplyConfiguration { + return &UserSpecApplyConfiguration{} +} + +// 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 *UserSpecApplyConfiguration) WithImport(value *UserImportApplyConfiguration) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithResource(value *UserResourceSpecApplyConfiguration) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *UserSpecApplyConfiguration { + b.ManagedOptions = value + return b +} + +// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CloudCredentialsRef field is set to the value of the last call. +func (b *UserSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *UserSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go new file mode 100644 index 000000000..1aae09224 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go @@ -0,0 +1,66 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// UserStatusApplyConfiguration represents a declarative configuration of the UserStatus type for use +// with apply. +type UserStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *UserResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// UserStatusApplyConfiguration constructs a declarative configuration of the UserStatus type for use with +// apply. +func UserStatus() *UserStatusApplyConfiguration { + return &UserStatusApplyConfiguration{} +} + +// 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 *UserStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *UserStatusApplyConfiguration { + 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 *UserStatusApplyConfiguration) WithID(value string) *UserStatusApplyConfiguration { + 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 *UserStatusApplyConfiguration) WithResource(value *UserResourceStatusApplyConfiguration) *UserStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abaeca27f..ee27d52c0 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2903,12 +2903,126 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.User + 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.UserSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserStatus + default: {} - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserDataSpec map: fields: - name: secretRef type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserFilter + map: + fields: + - name: domainRef + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceSpec + map: + fields: + - name: defaultProjectID + type: + scalar: string + - name: description + type: + scalar: string + - name: domainRef + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: name + type: + scalar: string + - name: password + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus + map: + fields: + - name: defaultProjectID + type: + scalar: string + - name: description + type: + scalar: string + - name: domainID + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserSpec + 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.UserImport + - 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.UserResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: id + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Volume map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5a3990951..8ae2ec8c9 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -340,8 +340,22 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SubnetSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetStatus"): return &apiv1alpha1.SubnetStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("User"): + return &apiv1alpha1.UserApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("UserDataSpec"): return &apiv1alpha1.UserDataSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserFilter"): + return &apiv1alpha1.UserFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserImport"): + return &apiv1alpha1.UserImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserResourceSpec"): + return &apiv1alpha1.UserResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserResourceStatus"): + return &apiv1alpha1.UserResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserSpec"): + return &apiv1alpha1.UserSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserStatus"): + return &apiv1alpha1.UserStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Volume"): return &apiv1alpha1.VolumeApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("VolumeAttachmentStatus"): 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 4317c8aa6..40a13d271 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -45,6 +45,7 @@ type OpenstackV1alpha1Interface interface { ServerGroupsGetter ServicesGetter SubnetsGetter + UsersGetter VolumesGetter VolumeTypesGetter } @@ -122,6 +123,10 @@ func (c *OpenstackV1alpha1Client) Subnets(namespace string) SubnetInterface { return newSubnets(c, namespace) } +func (c *OpenstackV1alpha1Client) Users(namespace string) UserInterface { + return newUsers(c, namespace) +} + func (c *OpenstackV1alpha1Client) Volumes(namespace string) VolumeInterface { return newVolumes(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 595446f05..58bd6b732 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 @@ -96,6 +96,10 @@ func (c *FakeOpenstackV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterfa return newFakeSubnets(c, namespace) } +func (c *FakeOpenstackV1alpha1) Users(namespace string) v1alpha1.UserInterface { + return newFakeUsers(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Volumes(namespace string) v1alpha1.VolumeInterface { return newFakeVolumes(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go new file mode 100644 index 000000000..c3ac6b668 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go @@ -0,0 +1,49 @@ +/* +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" +) + +// fakeUsers implements UserInterface +type fakeUsers struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.User, *v1alpha1.UserList, *apiv1alpha1.UserApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeUsers(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.UserInterface { + return &fakeUsers{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.User, *v1alpha1.UserList, *apiv1alpha1.UserApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("users"), + v1alpha1.SchemeGroupVersion.WithKind("User"), + func() *v1alpha1.User { return &v1alpha1.User{} }, + func() *v1alpha1.UserList { return &v1alpha1.UserList{} }, + func(dst, src *v1alpha1.UserList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.UserList) []*v1alpha1.User { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.UserList, items []*v1alpha1.User) { 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 558e399d0..1dbfae51e 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -52,6 +52,8 @@ type ServiceExpansion interface{} type SubnetExpansion interface{} +type UserExpansion interface{} + type VolumeExpansion interface{} type VolumeTypeExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.go new file mode 100644 index 000000000..d2f6659a7 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.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" +) + +// UsersGetter has a method to return a UserInterface. +// A group's client should implement this interface. +type UsersGetter interface { + Users(namespace string) UserInterface +} + +// UserInterface has methods to work with User resources. +type UserInterface interface { + Create(ctx context.Context, user *apiv1alpha1.User, opts v1.CreateOptions) (*apiv1alpha1.User, error) + Update(ctx context.Context, user *apiv1alpha1.User, opts v1.UpdateOptions) (*apiv1alpha1.User, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, user *apiv1alpha1.User, opts v1.UpdateOptions) (*apiv1alpha1.User, 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.User, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.UserList, 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.User, err error) + Apply(ctx context.Context, user *applyconfigurationapiv1alpha1.UserApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.User, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, user *applyconfigurationapiv1alpha1.UserApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.User, err error) + UserExpansion +} + +// users implements UserInterface +type users struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.User, *apiv1alpha1.UserList, *applyconfigurationapiv1alpha1.UserApplyConfiguration] +} + +// newUsers returns a Users +func newUsers(c *OpenstackV1alpha1Client, namespace string) *users { + return &users{ + gentype.NewClientWithListAndApply[*apiv1alpha1.User, *apiv1alpha1.UserList, *applyconfigurationapiv1alpha1.UserApplyConfiguration]( + "users", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.User { return &apiv1alpha1.User{} }, + func() *apiv1alpha1.UserList { return &apiv1alpha1.UserList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 2e06781ab..a17a8c1cd 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -58,6 +58,8 @@ type Interface interface { Services() ServiceInformer // Subnets returns a SubnetInformer. Subnets() SubnetInformer + // Users returns a UserInformer. + Users() UserInformer // Volumes returns a VolumeInformer. Volumes() VolumeInformer // VolumeTypes returns a VolumeTypeInformer. @@ -160,6 +162,11 @@ func (v *version) Subnets() SubnetInformer { return &subnetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Users returns a UserInformer. +func (v *version) Users() UserInformer { + return &userInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Volumes returns a VolumeInformer. func (v *version) Volumes() VolumeInformer { return &volumeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/user.go b/pkg/clients/informers/externalversions/api/v1alpha1/user.go new file mode 100644 index 000000000..3cdb83f5f --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/user.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" +) + +// UserInformer provides access to a shared informer and lister for +// Users. +type UserInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.UserLister +} + +type userInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewUserInformer constructs a new informer for User 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 NewUserInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredUserInformer constructs a new informer for User 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 NewFilteredUserInformer(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().Users(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.User{}, + resyncPeriod, + indexers, + ) +} + +func (f *userInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *userInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.User{}, f.defaultInformer) +} + +func (f *userInformer) Lister() apiv1alpha1.UserLister { + return apiv1alpha1.NewUserLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 1bb2313e0..b7b3f7596 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -87,6 +87,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Services().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subnets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("users"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Users().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Volumes().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumetypes"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 1380d0372..9c18fa4d4 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -154,6 +154,14 @@ type SubnetListerExpansion interface{} // SubnetNamespaceLister. type SubnetNamespaceListerExpansion interface{} +// UserListerExpansion allows custom methods to be added to +// UserLister. +type UserListerExpansion interface{} + +// UserNamespaceListerExpansion allows custom methods to be added to +// UserNamespaceLister. +type UserNamespaceListerExpansion interface{} + // VolumeListerExpansion allows custom methods to be added to // VolumeLister. type VolumeListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/user.go b/pkg/clients/listers/api/v1alpha1/user.go new file mode 100644 index 000000000..363b6a371 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/user.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" +) + +// UserLister helps list Users. +// All objects returned here must be treated as read-only. +type UserLister interface { + // List lists all Users in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.User, err error) + // Users returns an object that can list and get Users. + Users(namespace string) UserNamespaceLister + UserListerExpansion +} + +// userLister implements the UserLister interface. +type userLister struct { + listers.ResourceIndexer[*apiv1alpha1.User] +} + +// NewUserLister returns a new UserLister. +func NewUserLister(indexer cache.Indexer) UserLister { + return &userLister{listers.New[*apiv1alpha1.User](indexer, apiv1alpha1.Resource("user"))} +} + +// Users returns an object that can list and get Users. +func (s *userLister) Users(namespace string) UserNamespaceLister { + return userNamespaceLister{listers.NewNamespaced[*apiv1alpha1.User](s.ResourceIndexer, namespace)} +} + +// UserNamespaceLister helps list and get Users. +// All objects returned here must be treated as read-only. +type UserNamespaceLister interface { + // List lists all Users in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.User, err error) + // Get retrieves the User from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.User, error) + UserNamespaceListerExpansion +} + +// userNamespaceLister implements the UserNamespaceLister +// interface. +type userNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.User] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 32018f962..43800ee32 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -27,6 +27,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [ServerGroup](#servergroup) - [Service](#service) - [Subnet](#subnet) +- [User](#user) - [Volume](#volume) - [VolumeType](#volumetype) @@ -179,6 +180,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1579,6 +1581,8 @@ _Appears in:_ - [ProjectResourceSpec](#projectresourcespec) - [RoleFilter](#rolefilter) - [RoleResourceSpec](#roleresourcespec) +- [UserFilter](#userfilter) +- [UserResourceSpec](#userresourcespec) @@ -1633,6 +1637,8 @@ _Appears in:_ - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) - [UserDataSpec](#userdataspec) +- [UserFilter](#userfilter) +- [UserResourceSpec](#userresourcespec) - [VolumeResourceSpec](#volumeresourcespec) @@ -1692,6 +1698,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1726,6 +1733,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -3823,6 +3831,25 @@ _Appears in:_ +#### User + + + +User is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `User` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[UserSpec](#userspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[UserStatus](#userstatus)_ | status defines the observed state of the resource. | | | + + #### UserDataSpec @@ -3841,6 +3868,123 @@ _Appears in:_ | `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the user data for this server. | | MaxLength: 253
MinLength: 1
| +#### UserFilter + + + +UserFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [UserImport](#userimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| + + +#### UserImport + + + +UserImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [UserSpec](#userspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that 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. | | Format: uuid
| +| `filter` _[UserFilter](#userfilter)_ | 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
| + + +#### UserResourceSpec + + + +UserResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [UserSpec](#userspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| +| `defaultProjectID` _string_ | defaultProjectID is the ID of the default project for the user | | MaxLength: 255
MinLength: 1
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `password` _string_ | password is created by the user | | MaxLength: 255
MinLength: 1
| +| `enabled` _boolean_ | enabled indicates whether the user is enabled or not | true | | + + +#### UserResourceStatus + + + +UserResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [UserStatus](#userstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| +| `defaultProjectID` _string_ | defaultProjectID is the ID of the default project for the user | | MaxLength: 255
MinLength: 1
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| +| `enabled` _boolean_ | enabled indicates whether the user is enabled or not | true | | + + +#### UserSpec + + + +UserSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [User](#user) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[UserImport](#userimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[UserResourceSpec](#userresourcespec)_ | 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`. | | | +| `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]
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | + + +#### UserStatus + + + +UserStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [User](#user) + +| 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
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `resource` _[UserResourceStatus](#userresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + #### Volume