diff --git a/PROJECT b/PROJECT
index d170fece..dc16ca37 100644
--- a/PROJECT
+++ b/PROJECT
@@ -72,6 +72,14 @@ resources:
kind: Project
path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: true
+ domain: k-orc.cloud
+ group: openstack
+ kind: Role
+ 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 cb8210b9..e9cc7fc4 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ kubectl delete -f $ORC_RELEASE
| volume | | ◐ | ◐ |
| volume type | | ◐ | ◐ |
| domain | | ✔ | ✔ |
-
+| role | | ◐ | ◐ |
✔: mostly implemented
◐: partially implemented
diff --git a/api/v1alpha1/role_types.go b/api/v1alpha1/role_types.go
new file mode 100644
index 00000000..580258da
--- /dev/null
+++ b/api/v1alpha1/role_types.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2025 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
+
+// RoleResourceSpec contains the desired state of the resource.
+type RoleResourceSpec 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"`
+
+ // domainRef is a reference to the ORC Domain this resource is associated with.
+ // Typically, only used by admin.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable"
+ DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`
+}
+
+// RoleFilter defines an existing resource by its properties
+// +kubebuilder:validation:MinProperties:=1
+type RoleFilter struct {
+ // name of the existing resource
+ // +optional
+ Name *KeystoneName `json:"name,omitempty"`
+
+ // domainRef is a reference to the ORC Domain this resource is associated with.
+ // Typically, only used by admin.
+ // +optional
+ DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`
+}
+
+// RoleResourceStatus represents the observed state of the resource.
+type RoleResourceStatus 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"`
+
+ // domainID is the domain owner of the role.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ DomainID string `json:"domainID,omitempty"`
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index eb32d2f3..2096653b 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2676,6 +2676,223 @@ func (in *ProviderPropertiesStatus) DeepCopy() *ProviderPropertiesStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Role) DeepCopyInto(out *Role) {
+ *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 Role.
+func (in *Role) DeepCopy() *Role {
+ if in == nil {
+ return nil
+ }
+ out := new(Role)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Role) 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 *RoleFilter) DeepCopyInto(out *RoleFilter) {
+ *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 RoleFilter.
+func (in *RoleFilter) DeepCopy() *RoleFilter {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleFilter)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RoleImport) DeepCopyInto(out *RoleImport) {
+ *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(RoleFilter)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleImport.
+func (in *RoleImport) DeepCopy() *RoleImport {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleImport)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RoleList) DeepCopyInto(out *RoleList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Role, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleList.
+func (in *RoleList) DeepCopy() *RoleList {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *RoleList) 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 *RoleResourceSpec) DeepCopyInto(out *RoleResourceSpec) {
+ *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.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 RoleResourceSpec.
+func (in *RoleResourceSpec) DeepCopy() *RoleResourceSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleResourceSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RoleResourceStatus) DeepCopyInto(out *RoleResourceStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleResourceStatus.
+func (in *RoleResourceStatus) DeepCopy() *RoleResourceStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleResourceStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RoleSpec) DeepCopyInto(out *RoleSpec) {
+ *out = *in
+ if in.Import != nil {
+ in, out := &in.Import, &out.Import
+ *out = new(RoleImport)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Resource != nil {
+ in, out := &in.Resource, &out.Resource
+ *out = new(RoleResourceSpec)
+ (*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 RoleSpec.
+func (in *RoleSpec) DeepCopy() *RoleSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RoleStatus) DeepCopyInto(out *RoleStatus) {
+ *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(RoleResourceStatus)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleStatus.
+func (in *RoleStatus) DeepCopy() *RoleStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(RoleStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Router) DeepCopyInto(out *Router) {
*out = *in
diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go
new file mode 100644
index 00000000..6161c042
--- /dev/null
+++ b/api/v1alpha1/zz_generated.role-resource.go
@@ -0,0 +1,177 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 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"
+)
+
+// RoleImport specifies an existing resource which will be imported instead of
+// creating a new one
+// +kubebuilder:validation:MinProperties:=1
+// +kubebuilder:validation:MaxProperties:=1
+type RoleImport 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 *RoleFilter `json:"filter,omitempty"`
+}
+
+// RoleSpec 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 RoleSpec struct {
+ // import refers to an existing OpenStack resource which will be imported instead of
+ // creating a new one.
+ // +optional
+ Import *RoleImport `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 *RoleResourceSpec `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"`
+}
+
+// RoleStatus defines the observed state of an ORC resource.
+type RoleStatus 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 *RoleResourceStatus `json:"resource,omitempty"`
+}
+
+var _ ObjectWithConditions = &Role{}
+
+func (i *Role) 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"
+
+// Role is the Schema for an ORC resource.
+type Role 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 RoleSpec `json:"spec,omitempty"`
+
+ // status defines the observed state of the resource.
+ // +optional
+ Status RoleStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// RoleList contains a list of Role.
+type RoleList struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata contains the list metadata
+ // +optional
+ metav1.ListMeta `json:"metadata,omitempty"`
+
+ // items contains a list of Role.
+ // +required
+ Items []Role `json:"items"`
+}
+
+func (l *RoleList) GetItems() []Role {
+ return l.Items
+}
+
+func init() {
+ SchemeBuilder.Register(&Role{}, &RoleList{})
+}
+
+func (i *Role) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) {
+ if i == nil {
+ return nil, nil
+ }
+
+ return &i.Namespace, &i.Spec.CloudCredentialsRef
+}
+
+var _ CloudCredentialsRefProvider = &Role{}
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index 83052bb5..6f044d8f 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -36,6 +36,7 @@ import (
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/network"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/role"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/router"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/routerinterface"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/securitygroup"
@@ -122,6 +123,7 @@ func main() {
domain.New(scopeFactory),
service.New(scopeFactory),
keypair.New(scopeFactory),
+ role.New(scopeFactory),
}
restConfig := ctrl.GetConfigOrDie()
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index 033bbee6..0498ae43 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -121,6 +121,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Router": schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RouterFilter(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterImport": schema_openstack_resource_controller_v2_api_v1alpha1_RouterImport(ref),
@@ -5315,6 +5323,330 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStat
}
}
+func schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "Role 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.RoleSpec"),
+ },
+ },
+ "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.RoleStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleFilter 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 this resource is associated with. Typically, only used by admin.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleImport 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.RoleFilter"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleList contains a list of Role.",
+ 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 Role.",
+ 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.Role"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"items"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleResourceSpec 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: "",
+ },
+ },
+ "domainRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "domainRef is a reference to the ORC Domain this resource is associated with. Typically, only used by admin.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleResourceStatus 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: "",
+ },
+ },
+ "domainID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "domainID is the domain owner of the role.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleSpec 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.RoleImport"),
+ },
+ },
+ "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.RoleResourceSpec"),
+ },
+ },
+ "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.RoleImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "RoleStatus 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.RoleResourceStatus"),
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"},
+ }
+}
+
func schema_openstack_resource_controller_v2_api_v1alpha1_Router(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 687b31ce..a3b68e95 100644
--- a/cmd/resource-generator/main.go
+++ b/cmd/resource-generator/main.go
@@ -123,6 +123,9 @@ var resources []templateFields = []templateFields{
Name: "Project",
ExistingOSClient: true,
},
+ {
+ Name: "Role",
+ },
{
Name: "Router",
ExistingOSClient: true,
diff --git a/config/crd/bases/openstack.k-orc.cloud_roles.yaml b/config/crd/bases/openstack.k-orc.cloud_roles.yaml
new file mode 100644
index 00000000..c1bcc134
--- /dev/null
+++ b/config/crd/bases/openstack.k-orc.cloud_roles.yaml
@@ -0,0 +1,299 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.17.1
+ name: roles.openstack.k-orc.cloud
+spec:
+ group: openstack.k-orc.cloud
+ names:
+ categories:
+ - openstack
+ kind: Role
+ listKind: RoleList
+ plural: roles
+ singular: role
+ 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: Role 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 this resource is associated with.
+ Typically, only used by admin.
+ 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:
+ 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 this resource is associated with.
+ Typically, only used by admin.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: domainRef is immutable
+ rule: self == oldSelf
+ 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
+ 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:
+ description:
+ description: description is a human-readable description for the
+ resource.
+ maxLength: 1024
+ type: string
+ domainID:
+ description: domainID is the domain owner of the role.
+ maxLength: 1024
+ type: string
+ 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 9c1de749..49d14af2 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -11,6 +11,7 @@ resources:
- bases/openstack.k-orc.cloud_networks.yaml
- bases/openstack.k-orc.cloud_ports.yaml
- bases/openstack.k-orc.cloud_projects.yaml
+- bases/openstack.k-orc.cloud_roles.yaml
- bases/openstack.k-orc.cloud_routers.yaml
- bases/openstack.k-orc.cloud_routerinterfaces.yaml
- bases/openstack.k-orc.cloud_securitygroups.yaml
diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml
index eb1c0d7c..abbb875e 100644
--- a/config/manifests/bases/orc.clusterserviceversion.yaml
+++ b/config/manifests/bases/orc.clusterserviceversion.yaml
@@ -39,6 +39,11 @@ spec:
kind: Image
name: images.openstack.k-orc.cloud
version: v1alpha1
+ - description: KeyPair is the Schema for an ORC resource.
+ displayName: Key Pair
+ kind: KeyPair
+ name: keypairs.openstack.k-orc.cloud
+ version: v1alpha1
- description: Network is the Schema for an ORC resource.
displayName: Network
kind: Network
@@ -54,6 +59,11 @@ spec:
kind: Project
name: projects.openstack.k-orc.cloud
version: v1alpha1
+ - description: Role is the Schema for an ORC resource.
+ displayName: Role
+ kind: Role
+ name: roles.openstack.k-orc.cloud
+ version: v1alpha1
- description: RouterInterface is the Schema for an ORC resource.
displayName: Router Interface
kind: RouterInterface
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index a90ffdde..44cd0441 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -25,6 +25,7 @@ rules:
- networks
- ports
- projects
+ - roles
- routerinterfaces
- routers
- securitygroups
@@ -53,6 +54,7 @@ rules:
- networks/status
- ports/status
- projects/status
+ - roles/status
- routerinterfaces/status
- routers/status
- securitygroups/status
diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml
index ac867fa9..4c3344d3 100644
--- a/config/samples/kustomization.yaml
+++ b/config/samples/kustomization.yaml
@@ -9,6 +9,7 @@ resources:
- openstack_v1alpha1_network.yaml
- openstack_v1alpha1_port.yaml
- openstack_v1alpha1_project.yaml
+- openstack_v1alpha1_role.yaml
- openstack_v1alpha1_router.yaml
- openstack_v1alpha1_routerinterface.yaml
- openstack_v1alpha1_securitygroup.yaml
diff --git a/config/samples/openstack_v1alpha1_role.yaml b/config/samples/openstack_v1alpha1_role.yaml
new file mode 100644
index 00000000..606cf864
--- /dev/null
+++ b/config/samples/openstack_v1alpha1_role.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-example
+spec:
+ cloudCredentialsRef:
+ cloudName: devstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ domainRef: role-example-20-nov
+
diff --git a/internal/controllers/role/actuator.go b/internal/controllers/role/actuator.go
new file mode 100644
index 00000000..5cbde15c
--- /dev/null
+++ b/internal/controllers/role/actuator.go
@@ -0,0 +1,275 @@
+/*
+Copyright 2025 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 role
+
+import (
+ "context"
+ "fmt"
+ "iter"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles"
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/utils/ptr"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/logging"
+ "github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
+ orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
+)
+
+// OpenStack resource types
+type (
+ osResourceT = roles.Role
+
+ 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 roleActuator struct {
+ osClient osclients.RoleClient
+ k8sClient client.Client
+}
+
+var _ createResourceActuator = roleActuator{}
+var _ deleteResourceActuator = roleActuator{}
+
+func (roleActuator) GetResourceID(osResource *osResourceT) string {
+ return osResource.ID
+}
+
+func (actuator roleActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) {
+ resource, err := actuator.osClient.GetRole(ctx, id)
+ if err != nil {
+ return nil, progress.WrapError(err)
+ }
+ return resource, nil
+}
+
+func (actuator roleActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) {
+ resourceSpec := orcObject.Spec.Resource
+ if resourceSpec == nil {
+ return nil, false
+ }
+
+ listOpts := roles.ListOpts{
+ Name: getResourceName(orcObject),
+ }
+
+ return actuator.osClient.ListRoles(ctx, listOpts), true
+}
+
+func (actuator roleActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
+ var reconcileStatus progress.ReconcileStatus
+
+ domain := &orcv1alpha1.Domain{}
+ if filter.DomainRef != nil {
+ domainKey := client.ObjectKey{Name: string(*filter.DomainRef), Namespace: obj.Namespace}
+ if err := actuator.k8sClient.Get(ctx, domainKey, domain); err != nil {
+ if apierrors.IsNotFound(err) {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnCreation))
+ } else {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WrapError(fmt.Errorf("fetching domain %s: %w", domainKey.Name, err)))
+ }
+ } else {
+ if !orcv1alpha1.IsAvailable(domain) || domain.Status.ID == nil {
+ reconcileStatus = reconcileStatus.WithReconcileStatus(
+ progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnReady))
+ }
+ }
+ }
+
+ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
+ return nil, reconcileStatus
+ }
+
+ listOpts := roles.ListOpts{
+ Name: string(ptr.Deref(filter.Name, "")),
+ DomainID: ptr.Deref(domain.Status.ID, ""),
+ }
+
+ return actuator.osClient.ListRoles(ctx, listOpts), nil
+}
+
+func (actuator roleActuator) 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 := roles.CreateOpts{
+ Name: getResourceName(obj),
+ Description: ptr.Deref(resource.Description, ""),
+ DomainID: domainID,
+ }
+
+ osResource, err := actuator.osClient.CreateRole(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 roleActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
+ return progress.WrapError(actuator.osClient.DeleteRole(ctx, resource.ID))
+}
+
+func (actuator roleActuator) 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 := roles.UpdateOpts{}
+
+ handleNameUpdate(&updateOpts, obj, osResource)
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+
+ needsUpdate, err := needsUpdate(updateOpts)
+ if err != nil {
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
+ }
+ if !needsUpdate {
+ log.V(logging.Debug).Info("No changes")
+ return nil
+ }
+
+ _, err = actuator.osClient.UpdateRole(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 roles.UpdateOpts) (bool, error) {
+ updateOptsMap, err := updateOpts.ToRoleUpdateMap()
+ if err != nil {
+ return false, err
+ }
+
+ updateMap, ok := updateOptsMap["role"].(map[string]any)
+ if !ok {
+ updateMap = make(map[string]any)
+ }
+
+ return len(updateMap) > 0, nil
+}
+
+func handleNameUpdate(updateOpts *roles.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
+ name := getResourceName(obj)
+ if osResource.Name != name {
+ updateOpts.Name = name
+ }
+}
+
+func handleDescriptionUpdate(updateOpts *roles.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
+ description := ptr.Deref(resource.Description, "")
+ if osResource.Description != description {
+ updateOpts.Description = &description
+ }
+}
+
+func (actuator roleActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
+ return []resourceReconciler{
+ actuator.updateResource,
+ }, nil
+}
+
+type roleHelperFactory struct{}
+
+var _ helperFactory = roleHelperFactory{}
+
+func newActuator(ctx context.Context, orcObject *orcv1alpha1.Role, controller interfaces.ResourceController) (roleActuator, 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 roleActuator{}, reconcileStatus
+ }
+
+ clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject)
+ if err != nil {
+ return roleActuator{}, progress.WrapError(err)
+ }
+ osClient, err := clientScope.NewRoleClient()
+ if err != nil {
+ return roleActuator{}, progress.WrapError(err)
+ }
+
+ return roleActuator{
+ osClient: osClient,
+ k8sClient: controller.GetK8sClient(),
+ }, nil
+}
+
+func (roleHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI {
+ return roleAdapter{obj}
+}
+
+func (roleHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
+
+func (roleHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) {
+ return newActuator(ctx, orcObject, controller)
+}
diff --git a/internal/controllers/role/actuator_test.go b/internal/controllers/role/actuator_test.go
new file mode 100644
index 00000000..3e39dbb6
--- /dev/null
+++ b/internal/controllers/role/actuator_test.go
@@ -0,0 +1,119 @@
+/*
+Copyright 2025 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 role
+
+import (
+ "testing"
+
+ "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles"
+ 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 roles.UpdateOpts
+ expectChange bool
+ }{
+ {
+ name: "Empty base opts",
+ updateOpts: roles.UpdateOpts{},
+ expectChange: false,
+ },
+ {
+ name: "Updated opts",
+ updateOpts: roles.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.Role{}
+ resource.Name = "object-name"
+ resource.Spec = orcv1alpha1.RoleSpec{
+ Resource: &orcv1alpha1.RoleResourceSpec{Name: tt.newValue},
+ }
+ osResource := &osResourceT{Name: tt.existingValue}
+
+ updateOpts := roles.UpdateOpts{}
+ handleNameUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
+
+func TestHandleDescriptionUpdate(t *testing.T) {
+ ptrToDescription := ptr.To[string]
+ testCases := []struct {
+ name string
+ newValue *string
+ existingValue string
+ expectChange bool
+ }{
+ {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false},
+ {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true},
+ {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false},
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ resource := &orcv1alpha1.RoleResourceSpec{Description: tt.newValue}
+ osResource := &osResourceT{Description: tt.existingValue}
+
+ updateOpts := roles.UpdateOpts{}
+ handleDescriptionUpdate(&updateOpts, resource, osResource)
+
+ got, _ := needsUpdate(updateOpts)
+ if got != tt.expectChange {
+ t.Errorf("Expected change: %v, got: %v", tt.expectChange, got)
+ }
+ })
+
+ }
+}
diff --git a/internal/controllers/role/controller.go b/internal/controllers/role/controller.go
new file mode 100644
index 00000000..fde1420d
--- /dev/null
+++ b/internal/controllers/role/controller.go
@@ -0,0 +1,93 @@
+/*
+Copyright 2025 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 role
+
+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/pkg/predicates"
+
+ "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"
+)
+
+const controllerName = "role"
+
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roles,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roles/status,verbs=get;update;patch
+
+type roleReconcilerConstructor struct {
+ scopeFactory scope.Factory
+}
+
+func New(scopeFactory scope.Factory) interfaces.Controller {
+ return roleReconcilerConstructor{scopeFactory: scopeFactory}
+}
+
+func (roleReconcilerConstructor) GetName() string {
+ return controllerName
+}
+
+var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleList, *orcv1alpha1.Domain](
+ "spec.resource.domainRef",
+ func(role *orcv1alpha1.Role) []string {
+ resource := role.Spec.Resource
+ if resource == nil || resource.DomainRef == nil {
+ return nil
+ }
+ return []string{string(*resource.DomainRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
+// SetupWithManager sets up the controller with the Manager.
+func (c roleReconcilerConstructor) 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
+ }
+
+ builder := ctrl.NewControllerManagedBy(mgr).
+ WithOptions(options).
+ For(&orcv1alpha1.Role{}).
+ Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
+ )
+
+ if err := errors.Join(
+ domainDependency.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, roleHelperFactory{}, roleStatusWriter{})
+ return builder.Complete(&r)
+}
diff --git a/internal/controllers/role/status.go b/internal/controllers/role/status.go
new file mode 100644
index 00000000..3b6d8edd
--- /dev/null
+++ b/internal/controllers/role/status.go
@@ -0,0 +1,61 @@
+/*
+Copyright 2025 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 role
+
+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 roleStatusWriter struct{}
+
+type objectApplyT = orcapplyconfigv1alpha1.RoleApplyConfiguration
+type statusApplyT = orcapplyconfigv1alpha1.RoleStatusApplyConfiguration
+
+var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Role, *osResourceT, *objectApplyT, *statusApplyT] = roleStatusWriter{}
+
+func (roleStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT {
+ return orcapplyconfigv1alpha1.Role(name, namespace)
+}
+
+func (roleStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Role, 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 (roleStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) {
+ resourceStatus := orcapplyconfigv1alpha1.RoleResourceStatus().
+ WithName(osResource.Name).
+ WithDomainID(osResource.DomainID)
+
+ if osResource.Description != "" {
+ resourceStatus.WithDescription(osResource.Description)
+ }
+
+ statusApply.WithResource(resourceStatus)
+}
diff --git a/internal/controllers/role/tests/role-create-full/00-assert.yaml b/internal/controllers/role/tests/role-create-full/00-assert.yaml
new file mode 100644
index 00000000..2b6ffdfe
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-full/00-assert.yaml
@@ -0,0 +1,32 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-create-full
+status:
+ resource:
+ name: role-create-full-override
+ description: Role from "create full" test
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-create-full
+ ref: role
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Domain
+ name: role-create-full
+ ref: domain
+assertAll:
+ - celExpr: "role.status.id != ''"
+ - celExpr: "role.status.resource.domainID == domain.status.id"
+
diff --git a/internal/controllers/role/tests/role-create-full/00-create-resource.yaml b/internal/controllers/role/tests/role-create-full/00-create-resource.yaml
new file mode 100644
index 00000000..acbe319e
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-full/00-create-resource.yaml
@@ -0,0 +1,25 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-create-full
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: role-create-full-override
+ description: Role from "create full" test
+ domainRef: role-create-full
diff --git a/internal/controllers/role/tests/role-create-full/00-secret.yaml b/internal/controllers/role/tests/role-create-full/00-secret.yaml
new file mode 100644
index 00000000..045711ee
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-create-full/01-assert.yaml b/internal/controllers/role/tests/role-create-full/01-assert.yaml
new file mode 100644
index 00000000..6cbedbcc
--- /dev/null
+++ b/internal/controllers/role/tests/role-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: role-create-full
+ ref: domain
+assertAll:
+ - celExpr: "domain.status.resource.enabled == false"
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-create-full/01-disable-domain.yaml b/internal/controllers/role/tests/role-create-full/01-disable-domain.yaml
new file mode 100644
index 00000000..44f39d6b
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-full/01-disable-domain.yaml
@@ -0,0 +1,7 @@
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-create-full
+spec:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-create-full/README.md b/internal/controllers/role/tests/role-create-full/README.md
new file mode 100644
index 00000000..bd28daf7
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-full/README.md
@@ -0,0 +1,18 @@
+# Create a Role with all the options
+
+## Step 00
+
+Create a Role 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/role/tests/role-create-minimal/00-assert.yaml b/internal/controllers/role/tests/role-create-minimal/00-assert.yaml
new file mode 100644
index 00000000..53f88823
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-minimal/00-assert.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-create-minimal
+status:
+ resource:
+ name: role-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: Role
+ name: role-create-minimal
+ ref: role
+assertAll:
+ - celExpr: "role.status.id != ''"
+ - celExpr: "!has(role.status.resource.description)"
+ - celExpr: "role.status.resource.domainID == ''"
diff --git a/internal/controllers/role/tests/role-create-minimal/00-create-resource.yaml b/internal/controllers/role/tests/role-create-minimal/00-create-resource.yaml
new file mode 100644
index 00000000..6752a835
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-minimal/00-create-resource.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-create-minimal
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
diff --git a/internal/controllers/role/tests/role-create-minimal/00-secret.yaml b/internal/controllers/role/tests/role-create-minimal/00-secret.yaml
new file mode 100644
index 00000000..045711ee
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-create-minimal/01-assert.yaml b/internal/controllers/role/tests/role-create-minimal/01-assert.yaml
new file mode 100644
index 00000000..2aa0543e
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role' in secret.metadata.finalizers"
diff --git a/internal/controllers/role/tests/role-create-minimal/01-delete-secret.yaml b/internal/controllers/role/tests/role-create-minimal/01-delete-secret.yaml
new file mode 100644
index 00000000..1620791b
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-create-minimal/README.md b/internal/controllers/role/tests/role-create-minimal/README.md
new file mode 100644
index 00000000..7f6b3653
--- /dev/null
+++ b/internal/controllers/role/tests/role-create-minimal/README.md
@@ -0,0 +1,15 @@
+# Create a Role with the minimum options
+
+## Step 00
+
+Create a minimal Role, 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 it is not 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/role/tests/role-dependency/00-assert.yaml b/internal/controllers/role/tests/role-dependency/00-assert.yaml
new file mode 100644
index 00000000..d44df4a6
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/00-assert.yaml
@@ -0,0 +1,31 @@
+
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-dependency-no-secret
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Secret/role-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Secret/role-dependency to be created
+ status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-dependency-no-domain
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Domain/role-dependency to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Domain/role-dependency to be created
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/role/tests/role-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/role/tests/role-dependency/00-create-resources-missing-deps.yaml
new file mode 100644
index 00000000..045f343c
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/00-create-resources-missing-deps.yaml
@@ -0,0 +1,25 @@
+
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-dependency-no-domain
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ domainRef: role-dependency
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-dependency-no-secret
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: role-dependency
+ managementPolicy: managed
+ resource: {}
+
diff --git a/internal/controllers/role/tests/role-dependency/00-secret.yaml b/internal/controllers/role/tests/role-dependency/00-secret.yaml
new file mode 100644
index 00000000..ef517f51
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/00-secret.yaml
@@ -0,0 +1,5 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-dependency/01-assert.yaml b/internal/controllers/role/tests/role-dependency/01-assert.yaml
new file mode 100644
index 00000000..c3780daf
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/01-assert.yaml
@@ -0,0 +1,31 @@
+
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-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: Role
+metadata:
+ name: role-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
diff --git a/internal/controllers/role/tests/role-dependency/01-create-dependencies.yaml b/internal/controllers/role/tests/role-dependency/01-create-dependencies.yaml
new file mode 100644
index 00000000..05ca0fa0
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/01-create-dependencies.yaml
@@ -0,0 +1,18 @@
+
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic role-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-dependency
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
diff --git a/internal/controllers/role/tests/role-dependency/02-assert.yaml b/internal/controllers/role/tests/role-dependency/02-assert.yaml
new file mode 100644
index 00000000..b6c7b3b3
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/02-assert.yaml
@@ -0,0 +1,14 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Domain
+ name: role-dependency
+ ref: domain
+ - apiVersion: v1
+ kind: Secret
+ name: role-dependency
+ ref: secret
+assertAll:
+ - celExpr: "domain.status.resource.enabled == false"
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-dependency/02-disable-domain.yaml b/internal/controllers/role/tests/role-dependency/02-disable-domain.yaml
new file mode 100644
index 00000000..32d5e69a
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/02-disable-domain.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-dependency
+spec:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-dependency/03-assert.yaml b/internal/controllers/role/tests/role-dependency/03-assert.yaml
new file mode 100644
index 00000000..8094e209
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/03-assert.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Domain
+ name: role-dependency
+ ref: domain
+assertAll:
+ - celExpr: "domain.status.resource.enabled == false"
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-dependency/03-delete-dependencies.yaml b/internal/controllers/role/tests/role-dependency/03-delete-dependencies.yaml
new file mode 100644
index 00000000..72c053fc
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/03-delete-dependencies.yaml
@@ -0,0 +1,10 @@
+
+---
+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 role-dependency --wait=false
+ namespaced: true
+ - command: kubectl delete secret role-dependency --wait=false
+ namespaced: true
diff --git a/internal/controllers/role/tests/role-dependency/04-assert.yaml b/internal/controllers/role/tests/role-dependency/04-assert.yaml
new file mode 100644
index 00000000..67315a17
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/04-assert.yaml
@@ -0,0 +1,10 @@
+
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+commands:
+# Dependencies that were prevented deletion before should now be gone
+- script: "! kubectl get domain role-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
+- script: "! kubectl get secret role-dependency --namespace $NAMESPACE"
+ skipLogOutput: true
diff --git a/internal/controllers/role/tests/role-dependency/04-delete-resources.yaml b/internal/controllers/role/tests/role-dependency/04-delete-resources.yaml
new file mode 100644
index 00000000..35e2dc88
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/04-delete-resources.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+delete:
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-dependency-no-secret
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-dependency-no-domain
diff --git a/internal/controllers/role/tests/role-dependency/README.md b/internal/controllers/role/tests/role-dependency/README.md
new file mode 100644
index 00000000..fbeb68b1
--- /dev/null
+++ b/internal/controllers/role/tests/role-dependency/README.md
@@ -0,0 +1,25 @@
+# Creation and deletion dependencies
+
+## Step 00
+
+Create roles referencing non-existing resources. Each role is dependent on other non-existing resource. Verify that the roles are waiting for the needed resources to be created externally.
+
+## Step 01
+
+Create the missing dependencies and make and verify all the roles 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 roles and validate that all resources are gone.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#dependency
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import-error/00-assert.yaml b/internal/controllers/role/tests/role-import-error/00-assert.yaml
new file mode 100644
index 00000000..3aa69a5b
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/00-assert.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-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: Role
+metadata:
+ name: role-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/role/tests/role-import-error/00-create-resources.yaml b/internal/controllers/role/tests/role-import-error/00-create-resources.yaml
new file mode 100644
index 00000000..672fa2cb
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/00-create-resources.yaml
@@ -0,0 +1,38 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import-error-external-1
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ description: Role from "import error" test
+ domainRef: role-import-error
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import-error-external-2
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ description: Role from "import error" test
+ domainRef: role-import-error
+
diff --git a/internal/controllers/role/tests/role-import-error/00-secret.yaml b/internal/controllers/role/tests/role-import-error/00-secret.yaml
new file mode 100644
index 00000000..045711ee
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-import-error/01-assert.yaml b/internal/controllers/role/tests/role-import-error/01-assert.yaml
new file mode 100644
index 00000000..9758e060
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/01-assert.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-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/role/tests/role-import-error/01-import-resource.yaml b/internal/controllers/role/tests/role-import-error/01-import-resource.yaml
new file mode 100644
index 00000000..a8f94b6d
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/01-import-resource.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import-error
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ domainRef: role-import-error
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import-error/02-assert.yaml b/internal/controllers/role/tests/role-import-error/02-assert.yaml
new file mode 100644
index 00000000..2a555ab1
--- /dev/null
+++ b/internal/controllers/role/tests/role-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: role-import-error
+ ref: domain
+assertAll:
+ - celExpr: "domain.status.resource.enabled == false"
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import-error/02-disable-domain.yaml b/internal/controllers/role/tests/role-import-error/02-disable-domain.yaml
new file mode 100644
index 00000000..0f4a432c
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/02-disable-domain.yaml
@@ -0,0 +1,7 @@
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-error
+spec:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import-error/README.md b/internal/controllers/role/tests/role-import-error/README.md
new file mode 100644
index 00000000..600430df
--- /dev/null
+++ b/internal/controllers/role/tests/role-import-error/README.md
@@ -0,0 +1,17 @@
+# Import Role with more than one matching resources
+
+## Step 00
+
+Create two Roles with identical specs.
+
+## Step 01
+
+Ensure that an imported Role 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/role/tests/role-import/00-assert.yaml b/internal/controllers/role/tests/role-import/00-assert.yaml
new file mode 100644
index 00000000..073abd64
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/00-assert.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import
+status:
+ conditions:
+ - type: Available
+ status: "False"
+ reason: Progressing
+ message: Waiting for Domain/role-import-external to be ready
+ - type: Progressing
+ status: "True"
+ reason: Progressing
+ message: Waiting for Domain/role-import-external to be ready
+
diff --git a/internal/controllers/role/tests/role-import/00-import-resource.yaml b/internal/controllers/role/tests/role-import/00-import-resource.yaml
new file mode 100644
index 00000000..5c91479f
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/00-import-resource.yaml
@@ -0,0 +1,25 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: unmanaged
+ import:
+ filter:
+ name: role-import-external
+ domainRef: role-import-external
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/00-secret.yaml b/internal/controllers/role/tests/role-import/00-secret.yaml
new file mode 100644
index 00000000..045711ee
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-import/01-assert.yaml b/internal/controllers/role/tests/role-import/01-assert.yaml
new file mode 100644
index 00000000..261ef157
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/01-assert.yaml
@@ -0,0 +1,34 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-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: role-import-external-not-this-one
+ description: Role role-import-external from "role-import" test
+
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Domain/role-import-external to be ready
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Domain/role-import-external to be ready
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/role/tests/role-import/01-create-trap-resource.yaml b/internal/controllers/role/tests/role-import/01-create-trap-resource.yaml
new file mode 100644
index 00000000..dbbb6427
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/01-create-trap-resource.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-external-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+# This `role-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: Role
+metadata:
+ name: role-import-external-not-this-one
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ description: Role role-import-external from "role-import" test
+ domainRef: role-import-external-not-this-one
diff --git a/internal/controllers/role/tests/role-import/02-assert.yaml b/internal/controllers/role/tests/role-import/02-assert.yaml
new file mode 100644
index 00000000..b9ba47ae
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/02-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+assert:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Domain
+ name: role-import-external-not-this-one
+ status:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/02-disable-domain.yaml b/internal/controllers/role/tests/role-import/02-disable-domain.yaml
new file mode 100644
index 00000000..bc2ce181
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/02-disable-domain.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-external-not-this-one
+spec:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/03-assert.yaml b/internal/controllers/role/tests/role-import/03-assert.yaml
new file mode 100644
index 00000000..9931590f
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/03-assert.yaml
@@ -0,0 +1,29 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-import-external
+ ref: role1
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-import-external-not-this-one
+ ref: role2
+assertAll:
+ - celExpr: "role1.status.id != role2.status.id"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import
+status:
+ conditions:
+ - type: Available
+ status: "False"
+ reason: Progressing
+ message: Waiting for Domain/role-import-external to be ready
+ - type: Progressing
+ status: "True"
+ reason: Progressing
+ message: Waiting for Domain/role-import-external to be ready
diff --git a/internal/controllers/role/tests/role-import/03-create-resource.yaml b/internal/controllers/role/tests/role-import/03-create-resource.yaml
new file mode 100644
index 00000000..dcb4714b
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/03-create-resource.yaml
@@ -0,0 +1,24 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-import-external
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ description: Role role-import-external from "role-import" test
+ domainRef: role-import-external
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/04-assert.yaml b/internal/controllers/role/tests/role-import/04-assert.yaml
new file mode 100644
index 00000000..b8cf640d
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/04-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+assert:
+ - apiVersion: openstack.k-orc.cloud.v1alpha1
+ kind: Domain
+ name: role-import-external
+ status:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/04-disable-domain.yaml b/internal/controllers/role/tests/role-import/04-disable-domain.yaml
new file mode 100644
index 00000000..813c2262
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/04-disable-domain.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: role-import-external
+spec:
+ resource:
+ enabled: false
\ No newline at end of file
diff --git a/internal/controllers/role/tests/role-import/README.md b/internal/controllers/role/tests/role-import/README.md
new file mode 100644
index 00000000..572a8be7
--- /dev/null
+++ b/internal/controllers/role/tests/role-import/README.md
@@ -0,0 +1,26 @@
+# Import Role
+
+## Step 00
+
+Import a role, matching all of the available filter's fields, and verify it is waiting for the external resource to be created.
+
+## Step 01
+
+Create a role which name is a superstring of the one specified in the import filter, and 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 role matching the filter and verify that the observed status on the imported role corresponds to the spec of the created role.
+Also verify that the created role didn't adopt the one which name is a superstring of it.
+
+## 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/role/tests/role-update/00-assert.yaml b/internal/controllers/role/tests/role-update/00-assert.yaml
new file mode 100644
index 00000000..842754d5
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/00-assert.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-update
+ ref: role
+assertAll:
+ - celExpr: "!has(role.status.resource.description)"
+ - celExpr: "role.status.resource.domainID == ''"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-update
+status:
+ resource:
+ name: role-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/role/tests/role-update/00-minimal-resource.yaml b/internal/controllers/role/tests/role-update/00-minimal-resource.yaml
new file mode 100644
index 00000000..999987b6
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/00-minimal-resource.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-update
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
diff --git a/internal/controllers/role/tests/role-update/00-prerequisites.yaml b/internal/controllers/role/tests/role-update/00-prerequisites.yaml
new file mode 100644
index 00000000..045711ee
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-update/01-assert.yaml b/internal/controllers/role/tests/role-update/01-assert.yaml
new file mode 100644
index 00000000..38d478a3
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/01-assert.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-update
+status:
+ resource:
+ name: role-update-updated
+ description: role-update-updated
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/role/tests/role-update/01-updated-resource.yaml b/internal/controllers/role/tests/role-update/01-updated-resource.yaml
new file mode 100644
index 00000000..dc91827e
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/01-updated-resource.yaml
@@ -0,0 +1,9 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-update
+spec:
+ resource:
+ name: role-update-updated
+ description: role-update-updated
diff --git a/internal/controllers/role/tests/role-update/02-assert.yaml b/internal/controllers/role/tests/role-update/02-assert.yaml
new file mode 100644
index 00000000..842754d5
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/02-assert.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Role
+ name: role-update
+ ref: role
+assertAll:
+ - celExpr: "!has(role.status.resource.description)"
+ - celExpr: "role.status.resource.domainID == ''"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Role
+metadata:
+ name: role-update
+status:
+ resource:
+ name: role-update
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
diff --git a/internal/controllers/role/tests/role-update/02-reverted-resource.yaml b/internal/controllers/role/tests/role-update/02-reverted-resource.yaml
new file mode 100644
index 00000000..2c6c253f
--- /dev/null
+++ b/internal/controllers/role/tests/role-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/role/tests/role-update/README.md b/internal/controllers/role/tests/role-update/README.md
new file mode 100644
index 00000000..333e06e2
--- /dev/null
+++ b/internal/controllers/role/tests/role-update/README.md
@@ -0,0 +1,17 @@
+# Update Role
+
+## Step 00
+
+Create a Role using only mandatory fields.
+
+## Step 01
+
+Update all mutable fields.
+
+## Step 02
+
+Revert the resource to its original value and verify the resulting object is similar to when if was first created.
+
+## Reference
+
+https://k-orc.cloud/development/writing-tests/#update
diff --git a/internal/controllers/role/zz_generated.adapter.go b/internal/controllers/role/zz_generated.adapter.go
new file mode 100644
index 00000000..3c98f6ec
--- /dev/null
+++ b/internal/controllers/role/zz_generated.adapter.go
@@ -0,0 +1,88 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 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 role
+
+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.Role
+ orcObjectListT = orcv1alpha1.RoleList
+ resourceSpecT = orcv1alpha1.RoleResourceSpec
+ filterT = orcv1alpha1.RoleFilter
+)
+
+// Derived types
+type (
+ orcObjectPT = *orcObjectT
+ adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT]
+ adapterT = roleAdapter
+)
+
+type roleAdapter struct {
+ *orcv1alpha1.Role
+}
+
+var _ adapterI = &adapterT{}
+
+func (f adapterT) GetObject() orcObjectPT {
+ return f.Role
+}
+
+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/role/zz_generated.controller.go b/internal/controllers/role/zz_generated.controller.go
new file mode 100644
index 00000000..e3caa1f3
--- /dev/null
+++ b/internal/controllers/role/zz_generated.controller.go
@@ -0,0 +1,45 @@
+// Code generated by resource-generator. DO NOT EDIT.
+/*
+Copyright 2025 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 role
+
+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 e599c1e8..09c3e441 100644
--- a/internal/osclients/mock/doc.go
+++ b/internal/osclients/mock/doc.go
@@ -41,6 +41,9 @@ import (
//go:generate mockgen -package mock -destination=keypair.go -source=../keypair.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock KeyPairClient
//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt keypair.go > _keypair.go && mv _keypair.go keypair.go"
+//go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient
+//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go"
+
//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"
diff --git a/internal/osclients/mock/role.go b/internal/osclients/mock/role.go
new file mode 100644
index 00000000..3108304d
--- /dev/null
+++ b/internal/osclients/mock/role.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2025 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: ../role.go
+//
+// Generated by this command:
+//
+// mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient
+//
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ context "context"
+ iter "iter"
+ reflect "reflect"
+
+ roles "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockRoleClient is a mock of RoleClient interface.
+type MockRoleClient struct {
+ ctrl *gomock.Controller
+ recorder *MockRoleClientMockRecorder
+ isgomock struct{}
+}
+
+// MockRoleClientMockRecorder is the mock recorder for MockRoleClient.
+type MockRoleClientMockRecorder struct {
+ mock *MockRoleClient
+}
+
+// NewMockRoleClient creates a new mock instance.
+func NewMockRoleClient(ctrl *gomock.Controller) *MockRoleClient {
+ mock := &MockRoleClient{ctrl: ctrl}
+ mock.recorder = &MockRoleClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRoleClient) EXPECT() *MockRoleClientMockRecorder {
+ return m.recorder
+}
+
+// CreateRole mocks base method.
+func (m *MockRoleClient) CreateRole(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.Role, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CreateRole", ctx, opts)
+ ret0, _ := ret[0].(*roles.Role)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CreateRole indicates an expected call of CreateRole.
+func (mr *MockRoleClientMockRecorder) CreateRole(ctx, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRole", reflect.TypeOf((*MockRoleClient)(nil).CreateRole), ctx, opts)
+}
+
+// DeleteRole mocks base method.
+func (m *MockRoleClient) DeleteRole(ctx context.Context, resourceID string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteRole", ctx, resourceID)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteRole indicates an expected call of DeleteRole.
+func (mr *MockRoleClientMockRecorder) DeleteRole(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRole", reflect.TypeOf((*MockRoleClient)(nil).DeleteRole), ctx, resourceID)
+}
+
+// GetRole mocks base method.
+func (m *MockRoleClient) GetRole(ctx context.Context, resourceID string) (*roles.Role, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetRole", ctx, resourceID)
+ ret0, _ := ret[0].(*roles.Role)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetRole indicates an expected call of GetRole.
+func (mr *MockRoleClientMockRecorder) GetRole(ctx, resourceID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRole", reflect.TypeOf((*MockRoleClient)(nil).GetRole), ctx, resourceID)
+}
+
+// ListRoles mocks base method.
+func (m *MockRoleClient) ListRoles(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.Role, error] {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ListRoles", ctx, listOpts)
+ ret0, _ := ret[0].(iter.Seq2[*roles.Role, error])
+ return ret0
+}
+
+// ListRoles indicates an expected call of ListRoles.
+func (mr *MockRoleClientMockRecorder) ListRoles(ctx, listOpts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoles", reflect.TypeOf((*MockRoleClient)(nil).ListRoles), ctx, listOpts)
+}
+
+// UpdateRole mocks base method.
+func (m *MockRoleClient) UpdateRole(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.Role, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateRole", ctx, id, opts)
+ ret0, _ := ret[0].(*roles.Role)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateRole indicates an expected call of UpdateRole.
+func (mr *MockRoleClientMockRecorder) UpdateRole(ctx, id, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRole", reflect.TypeOf((*MockRoleClient)(nil).UpdateRole), ctx, id, opts)
+}
diff --git a/internal/osclients/role.go b/internal/osclients/role.go
new file mode 100644
index 00000000..c99dcb71
--- /dev/null
+++ b/internal/osclients/role.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2025 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/roles"
+ "github.com/gophercloud/utils/v2/openstack/clientconfig"
+)
+
+type RoleClient interface {
+ ListRoles(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.Role, error]
+ CreateRole(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.Role, error)
+ DeleteRole(ctx context.Context, resourceID string) error
+ GetRole(ctx context.Context, resourceID string) (*roles.Role, error)
+ UpdateRole(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.Role, error)
+}
+
+type roleClient struct{ client *gophercloud.ServiceClient }
+
+// NewRoleClient returns a new OpenStack client.
+func NewRoleClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RoleClient, 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 role service client: %v", err)
+ }
+
+ return &roleClient{client}, nil
+}
+
+func (c roleClient) ListRoles(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.Role, error] {
+ pager := roles.List(c.client, listOpts)
+ return func(yield func(*roles.Role, error) bool) {
+ _ = pager.EachPage(ctx, yieldPage(roles.ExtractRoles, yield))
+ }
+}
+
+func (c roleClient) CreateRole(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.Role, error) {
+ return roles.Create(ctx, c.client, opts).Extract()
+}
+
+func (c roleClient) DeleteRole(ctx context.Context, resourceID string) error {
+ return roles.Delete(ctx, c.client, resourceID).ExtractErr()
+}
+
+func (c roleClient) GetRole(ctx context.Context, resourceID string) (*roles.Role, error) {
+ return roles.Get(ctx, c.client, resourceID).Extract()
+}
+
+func (c roleClient) UpdateRole(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.Role, error) {
+ return roles.Update(ctx, c.client, id, opts).Extract()
+}
+
+type roleErrorClient struct{ error }
+
+// NewRoleErrorClient returns a RoleClient in which every method returns the given error.
+func NewRoleErrorClient(e error) RoleClient {
+ return roleErrorClient{e}
+}
+
+func (e roleErrorClient) ListRoles(_ context.Context, _ roles.ListOptsBuilder) iter.Seq2[*roles.Role, error] {
+ return func(yield func(*roles.Role, error) bool) {
+ yield(nil, e.error)
+ }
+}
+
+func (e roleErrorClient) CreateRole(_ context.Context, _ roles.CreateOptsBuilder) (*roles.Role, error) {
+ return nil, e.error
+}
+
+func (e roleErrorClient) DeleteRole(_ context.Context, _ string) error {
+ return e.error
+}
+
+func (e roleErrorClient) GetRole(_ context.Context, _ string) (*roles.Role, error) {
+ return nil, e.error
+}
+
+func (e roleErrorClient) UpdateRole(_ context.Context, _ string, _ roles.UpdateOptsBuilder) (*roles.Role, error) {
+ return nil, e.error
+}
diff --git a/internal/scope/mock.go b/internal/scope/mock.go
index 085e49bc..b5ce1163 100644
--- a/internal/scope/mock.go
+++ b/internal/scope/mock.go
@@ -43,6 +43,7 @@ type MockScopeFactory struct {
VolumeTypeClient *mock.MockVolumeTypeClient
DomainClient *mock.MockDomainClient
ServiceClient *mock.MockServiceClient
+ RoleClient *mock.MockRoleClient
clientScopeCreateError error
}
@@ -56,6 +57,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory {
volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl)
domainClient := mock.NewMockDomainClient(mockCtrl)
serviceClient := mock.NewMockServiceClient(mockCtrl)
+ roleClient := mock.NewMockRoleClient(mockCtrl)
return &MockScopeFactory{
ComputeClient: computeClient,
@@ -66,6 +68,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory {
VolumeTypeClient: volumetypeClient,
DomainClient: domainClient,
ServiceClient: serviceClient,
+ RoleClient: roleClient,
}
}
@@ -116,6 +119,10 @@ func (f *MockScopeFactory) NewKeyPairClient() (osclients.KeyPairClient, error) {
return f.KeyPairClient, nil
}
+func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) {
+ return f.RoleClient, 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 0e8e0071..76d36af0 100644
--- a/internal/scope/provider.go
+++ b/internal/scope/provider.go
@@ -172,6 +172,9 @@ func (s *providerScope) NewServiceClient() (clients.ServiceClient, error) {
func (s *providerScope) NewKeyPairClient() (clients.KeyPairClient, error) {
return clients.NewKeyPairClient(s.providerClient, s.providerClientOpts)
}
+func (s *providerScope) NewRoleClient() (clients.RoleClient, error) {
+ return clients.NewRoleClient(s.providerClient, s.providerClientOpts)
+}
func (s *providerScope) ExtractToken() (*tokens.Token, error) {
client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{})
diff --git a/internal/scope/scope.go b/internal/scope/scope.go
index 62e505b4..7a0f93a9 100644
--- a/internal/scope/scope.go
+++ b/internal/scope/scope.go
@@ -56,6 +56,7 @@ type Scope interface {
NewVolumeTypeClient() (osclients.VolumeTypeClient, error)
NewDomainClient() (osclients.DomainClient, error)
NewServiceClient() (osclients.ServiceClient, error)
+ NewRoleClient() (osclients.RoleClient, error)
ExtractToken() (*tokens.Token, error)
NewKeyPairClient() (osclients.KeyPairClient, error)
}
diff --git a/kuttl-test.yaml b/kuttl-test.yaml
index c71ea7c5..7fd6cb76 100644
--- a/kuttl-test.yaml
+++ b/kuttl-test.yaml
@@ -10,6 +10,7 @@ testDirs:
- ./internal/controllers/network/tests/
- ./internal/controllers/port/tests/
- ./internal/controllers/project/tests/
+- ./internal/controllers/role/tests/
- ./internal/controllers/router/tests/
- ./internal/controllers/routerinterface/tests/
- ./internal/controllers/securitygroup/tests/
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/role.go b/pkg/clients/applyconfiguration/api/v1alpha1/role.go
new file mode 100644
index 00000000..14fcda79
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/role.go
@@ -0,0 +1,281 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleApplyConfiguration represents a declarative configuration of the Role type for use
+// with apply.
+type RoleApplyConfiguration struct {
+ v1.TypeMetaApplyConfiguration `json:",inline"`
+ *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
+ Spec *RoleSpecApplyConfiguration `json:"spec,omitempty"`
+ Status *RoleStatusApplyConfiguration `json:"status,omitempty"`
+}
+
+// Role constructs a declarative configuration of the Role type for use with
+// apply.
+func Role(name, namespace string) *RoleApplyConfiguration {
+ b := &RoleApplyConfiguration{}
+ b.WithName(name)
+ b.WithNamespace(namespace)
+ b.WithKind("Role")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b
+}
+
+// ExtractRole extracts the applied configuration owned by fieldManager from
+// role. If no managedFields are found in role for fieldManager, a
+// RoleApplyConfiguration 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.
+// role must be a unmodified Role API object that was retrieved from the Kubernetes API.
+// ExtractRole 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 ExtractRole(role *apiv1alpha1.Role, fieldManager string) (*RoleApplyConfiguration, error) {
+ return extractRole(role, fieldManager, "")
+}
+
+// ExtractRoleStatus is the same as ExtractRole except
+// that it extracts the status subresource applied configuration.
+// Experimental!
+func ExtractRoleStatus(role *apiv1alpha1.Role, fieldManager string) (*RoleApplyConfiguration, error) {
+ return extractRole(role, fieldManager, "status")
+}
+
+func extractRole(role *apiv1alpha1.Role, fieldManager string, subresource string) (*RoleApplyConfiguration, error) {
+ b := &RoleApplyConfiguration{}
+ err := managedfields.ExtractInto(role, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Role"), fieldManager, b, subresource)
+ if err != nil {
+ return nil, err
+ }
+ b.WithName(role.Name)
+ b.WithNamespace(role.Namespace)
+
+ b.WithKind("Role")
+ b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1")
+ return b, nil
+}
+func (b RoleApplyConfiguration) 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 *RoleApplyConfiguration) WithKind(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithAPIVersion(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithName(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithGenerateName(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithNamespace(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithUID(value types.UID) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithResourceVersion(value string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithGeneration(value int64) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithCreationTimestamp(value metav1.Time) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithLabels(entries map[string]string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithAnnotations(entries map[string]string) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithFinalizers(values ...string) *RoleApplyConfiguration {
+ b.ensureObjectMetaApplyConfigurationExists()
+ for i := range values {
+ b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
+ }
+ return b
+}
+
+func (b *RoleApplyConfiguration) 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 *RoleApplyConfiguration) WithSpec(value *RoleSpecApplyConfiguration) *RoleApplyConfiguration {
+ 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 *RoleApplyConfiguration) WithStatus(value *RoleStatusApplyConfiguration) *RoleApplyConfiguration {
+ b.Status = value
+ return b
+}
+
+// GetKind retrieves the value of the Kind field in the declarative configuration.
+func (b *RoleApplyConfiguration) GetKind() *string {
+ return b.TypeMetaApplyConfiguration.Kind
+}
+
+// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
+func (b *RoleApplyConfiguration) GetAPIVersion() *string {
+ return b.TypeMetaApplyConfiguration.APIVersion
+}
+
+// GetName retrieves the value of the Name field in the declarative configuration.
+func (b *RoleApplyConfiguration) GetName() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Name
+}
+
+// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
+func (b *RoleApplyConfiguration) GetNamespace() *string {
+ b.ensureObjectMetaApplyConfigurationExists()
+ return b.ObjectMetaApplyConfiguration.Namespace
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go
new file mode 100644
index 00000000..d27c0d29
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go
@@ -0,0 +1,52 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleFilterApplyConfiguration represents a declarative configuration of the RoleFilter type for use
+// with apply.
+type RoleFilterApplyConfiguration struct {
+ Name *apiv1alpha1.KeystoneName `json:"name,omitempty"`
+ DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"`
+}
+
+// RoleFilterApplyConfiguration constructs a declarative configuration of the RoleFilter type for use with
+// apply.
+func RoleFilter() *RoleFilterApplyConfiguration {
+ return &RoleFilterApplyConfiguration{}
+}
+
+// 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 *RoleFilterApplyConfiguration) WithName(value apiv1alpha1.KeystoneName) *RoleFilterApplyConfiguration {
+ 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 *RoleFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleFilterApplyConfiguration {
+ b.DomainRef = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go
new file mode 100644
index 00000000..da25d500
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2025 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
+
+// RoleImportApplyConfiguration represents a declarative configuration of the RoleImport type for use
+// with apply.
+type RoleImportApplyConfiguration struct {
+ ID *string `json:"id,omitempty"`
+ Filter *RoleFilterApplyConfiguration `json:"filter,omitempty"`
+}
+
+// RoleImportApplyConfiguration constructs a declarative configuration of the RoleImport type for use with
+// apply.
+func RoleImport() *RoleImportApplyConfiguration {
+ return &RoleImportApplyConfiguration{}
+}
+
+// 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 *RoleImportApplyConfiguration) WithID(value string) *RoleImportApplyConfiguration {
+ 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 *RoleImportApplyConfiguration) WithFilter(value *RoleFilterApplyConfiguration) *RoleImportApplyConfiguration {
+ b.Filter = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go
new file mode 100644
index 00000000..4f3fad0c
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go
@@ -0,0 +1,61 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleResourceSpecApplyConfiguration represents a declarative configuration of the RoleResourceSpec type for use
+// with apply.
+type RoleResourceSpecApplyConfiguration struct {
+ Name *apiv1alpha1.KeystoneName `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"`
+}
+
+// RoleResourceSpecApplyConfiguration constructs a declarative configuration of the RoleResourceSpec type for use with
+// apply.
+func RoleResourceSpec() *RoleResourceSpecApplyConfiguration {
+ return &RoleResourceSpecApplyConfiguration{}
+}
+
+// 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 *RoleResourceSpecApplyConfiguration) WithName(value apiv1alpha1.KeystoneName) *RoleResourceSpecApplyConfiguration {
+ 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 *RoleResourceSpecApplyConfiguration) WithDescription(value string) *RoleResourceSpecApplyConfiguration {
+ b.Description = &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 *RoleResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleResourceSpecApplyConfiguration {
+ b.DomainRef = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go
new file mode 100644
index 00000000..847f97c2
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2025 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
+
+// RoleResourceStatusApplyConfiguration represents a declarative configuration of the RoleResourceStatus type for use
+// with apply.
+type RoleResourceStatusApplyConfiguration struct {
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ DomainID *string `json:"domainID,omitempty"`
+}
+
+// RoleResourceStatusApplyConfiguration constructs a declarative configuration of the RoleResourceStatus type for use with
+// apply.
+func RoleResourceStatus() *RoleResourceStatusApplyConfiguration {
+ return &RoleResourceStatusApplyConfiguration{}
+}
+
+// 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 *RoleResourceStatusApplyConfiguration) WithName(value string) *RoleResourceStatusApplyConfiguration {
+ 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 *RoleResourceStatusApplyConfiguration) WithDescription(value string) *RoleResourceStatusApplyConfiguration {
+ b.Description = &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 *RoleResourceStatusApplyConfiguration) WithDomainID(value string) *RoleResourceStatusApplyConfiguration {
+ b.DomainID = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go
new file mode 100644
index 00000000..92fa5027
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleSpecApplyConfiguration represents a declarative configuration of the RoleSpec type for use
+// with apply.
+type RoleSpecApplyConfiguration struct {
+ Import *RoleImportApplyConfiguration `json:"import,omitempty"`
+ Resource *RoleResourceSpecApplyConfiguration `json:"resource,omitempty"`
+ ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"`
+ ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"`
+ CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"`
+}
+
+// RoleSpecApplyConfiguration constructs a declarative configuration of the RoleSpec type for use with
+// apply.
+func RoleSpec() *RoleSpecApplyConfiguration {
+ return &RoleSpecApplyConfiguration{}
+}
+
+// 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 *RoleSpecApplyConfiguration) WithImport(value *RoleImportApplyConfiguration) *RoleSpecApplyConfiguration {
+ 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 *RoleSpecApplyConfiguration) WithResource(value *RoleResourceSpecApplyConfiguration) *RoleSpecApplyConfiguration {
+ 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 *RoleSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *RoleSpecApplyConfiguration {
+ 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 *RoleSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *RoleSpecApplyConfiguration {
+ 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 *RoleSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *RoleSpecApplyConfiguration {
+ b.CloudCredentialsRef = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go
new file mode 100644
index 00000000..cc11f42f
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleStatusApplyConfiguration represents a declarative configuration of the RoleStatus type for use
+// with apply.
+type RoleStatusApplyConfiguration struct {
+ Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
+ ID *string `json:"id,omitempty"`
+ Resource *RoleResourceStatusApplyConfiguration `json:"resource,omitempty"`
+}
+
+// RoleStatusApplyConfiguration constructs a declarative configuration of the RoleStatus type for use with
+// apply.
+func RoleStatus() *RoleStatusApplyConfiguration {
+ return &RoleStatusApplyConfiguration{}
+}
+
+// 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 *RoleStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *RoleStatusApplyConfiguration {
+ 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 *RoleStatusApplyConfiguration) WithID(value string) *RoleStatusApplyConfiguration {
+ 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 *RoleStatusApplyConfiguration) WithResource(value *RoleResourceStatusApplyConfiguration) *RoleStatusApplyConfiguration {
+ b.Resource = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go
index 5096d31f..4bf75e01 100644
--- a/pkg/clients/applyconfiguration/internal/internal.go
+++ b/pkg/clients/applyconfiguration/internal/internal.go
@@ -1504,6 +1504,105 @@ var schemaYAML = typed.YAMLObject(`types:
- name: segmentationID
type:
scalar: numeric
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Role
+ 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.RoleSpec
+ default: {}
+ - name: status
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleStatus
+ default: {}
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleFilter
+ map:
+ fields:
+ - name: domainRef
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleImport
+ map:
+ fields:
+ - name: filter
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleFilter
+ - name: id
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleResourceSpec
+ map:
+ fields:
+ - name: description
+ type:
+ scalar: string
+ - name: domainRef
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleResourceStatus
+ map:
+ fields:
+ - name: description
+ type:
+ scalar: string
+ - name: domainID
+ type:
+ scalar: string
+ - name: name
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleSpec
+ 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.RoleImport
+ - 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.RoleResourceSpec
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleStatus
+ 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.RoleResourceStatus
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Router
map:
fields:
diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go
index 9739b525..7745c056 100644
--- a/pkg/clients/applyconfiguration/utils.go
+++ b/pkg/clients/applyconfiguration/utils.go
@@ -198,6 +198,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1alpha1.ProjectStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ProviderPropertiesStatus"):
return &apiv1alpha1.ProviderPropertiesStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("Role"):
+ return &apiv1alpha1.RoleApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleFilter"):
+ return &apiv1alpha1.RoleFilterApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleImport"):
+ return &apiv1alpha1.RoleImportApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleResourceSpec"):
+ return &apiv1alpha1.RoleResourceSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleResourceStatus"):
+ return &apiv1alpha1.RoleResourceStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleSpec"):
+ return &apiv1alpha1.RoleSpecApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("RoleStatus"):
+ return &apiv1alpha1.RoleStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("Router"):
return &apiv1alpha1.RouterApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("RouterFilter"):
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 21e46355..8e15c979 100644
--- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go
@@ -36,6 +36,7 @@ type OpenstackV1alpha1Interface interface {
NetworksGetter
PortsGetter
ProjectsGetter
+ RolesGetter
RoutersGetter
RouterInterfacesGetter
SecurityGroupsGetter
@@ -84,6 +85,10 @@ func (c *OpenstackV1alpha1Client) Projects(namespace string) ProjectInterface {
return newProjects(c, namespace)
}
+func (c *OpenstackV1alpha1Client) Roles(namespace string) RoleInterface {
+ return newRoles(c, namespace)
+}
+
func (c *OpenstackV1alpha1Client) Routers(namespace string) RouterInterface {
return newRouters(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 279e08c0..5388e3f0 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
@@ -60,6 +60,10 @@ func (c *FakeOpenstackV1alpha1) Projects(namespace string) v1alpha1.ProjectInter
return newFakeProjects(c, namespace)
}
+func (c *FakeOpenstackV1alpha1) Roles(namespace string) v1alpha1.RoleInterface {
+ return newFakeRoles(c, namespace)
+}
+
func (c *FakeOpenstackV1alpha1) Routers(namespace string) v1alpha1.RouterInterface {
return newFakeRouters(c, namespace)
}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go
new file mode 100644
index 00000000..e7701df2
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2025 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"
+)
+
+// fakeRoles implements RoleInterface
+type fakeRoles struct {
+ *gentype.FakeClientWithListAndApply[*v1alpha1.Role, *v1alpha1.RoleList, *apiv1alpha1.RoleApplyConfiguration]
+ Fake *FakeOpenstackV1alpha1
+}
+
+func newFakeRoles(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.RoleInterface {
+ return &fakeRoles{
+ gentype.NewFakeClientWithListAndApply[*v1alpha1.Role, *v1alpha1.RoleList, *apiv1alpha1.RoleApplyConfiguration](
+ fake.Fake,
+ namespace,
+ v1alpha1.SchemeGroupVersion.WithResource("roles"),
+ v1alpha1.SchemeGroupVersion.WithKind("Role"),
+ func() *v1alpha1.Role { return &v1alpha1.Role{} },
+ func() *v1alpha1.RoleList { return &v1alpha1.RoleList{} },
+ func(dst, src *v1alpha1.RoleList) { dst.ListMeta = src.ListMeta },
+ func(list *v1alpha1.RoleList) []*v1alpha1.Role { return gentype.ToPointerSlice(list.Items) },
+ func(list *v1alpha1.RoleList, items []*v1alpha1.Role) { 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 63f95de9..4e84a1ab 100644
--- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go
@@ -34,6 +34,8 @@ type PortExpansion interface{}
type ProjectExpansion interface{}
+type RoleExpansion interface{}
+
type RouterExpansion interface{}
type RouterInterfaceExpansion interface{}
diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go
new file mode 100644
index 00000000..77970806
--- /dev/null
+++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go
@@ -0,0 +1,74 @@
+/*
+Copyright 2025 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"
+)
+
+// RolesGetter has a method to return a RoleInterface.
+// A group's client should implement this interface.
+type RolesGetter interface {
+ Roles(namespace string) RoleInterface
+}
+
+// RoleInterface has methods to work with Role resources.
+type RoleInterface interface {
+ Create(ctx context.Context, role *apiv1alpha1.Role, opts v1.CreateOptions) (*apiv1alpha1.Role, error)
+ Update(ctx context.Context, role *apiv1alpha1.Role, opts v1.UpdateOptions) (*apiv1alpha1.Role, error)
+ // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+ UpdateStatus(ctx context.Context, role *apiv1alpha1.Role, opts v1.UpdateOptions) (*apiv1alpha1.Role, 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.Role, error)
+ List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.RoleList, 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.Role, err error)
+ Apply(ctx context.Context, role *applyconfigurationapiv1alpha1.RoleApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Role, err error)
+ // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
+ ApplyStatus(ctx context.Context, role *applyconfigurationapiv1alpha1.RoleApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Role, err error)
+ RoleExpansion
+}
+
+// roles implements RoleInterface
+type roles struct {
+ *gentype.ClientWithListAndApply[*apiv1alpha1.Role, *apiv1alpha1.RoleList, *applyconfigurationapiv1alpha1.RoleApplyConfiguration]
+}
+
+// newRoles returns a Roles
+func newRoles(c *OpenstackV1alpha1Client, namespace string) *roles {
+ return &roles{
+ gentype.NewClientWithListAndApply[*apiv1alpha1.Role, *apiv1alpha1.RoleList, *applyconfigurationapiv1alpha1.RoleApplyConfiguration](
+ "roles",
+ c.RESTClient(),
+ scheme.ParameterCodec,
+ namespace,
+ func() *apiv1alpha1.Role { return &apiv1alpha1.Role{} },
+ func() *apiv1alpha1.RoleList { return &apiv1alpha1.RoleList{} },
+ ),
+ }
+}
diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
index 40656a98..1f9fdc15 100644
--- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
+++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go
@@ -40,6 +40,8 @@ type Interface interface {
Ports() PortInformer
// Projects returns a ProjectInformer.
Projects() ProjectInformer
+ // Roles returns a RoleInformer.
+ Roles() RoleInformer
// Routers returns a RouterInformer.
Routers() RouterInformer
// RouterInterfaces returns a RouterInterfaceInformer.
@@ -111,6 +113,11 @@ func (v *version) Projects() ProjectInformer {
return &projectInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
+// Roles returns a RoleInformer.
+func (v *version) Roles() RoleInformer {
+ return &roleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
+
// Routers returns a RouterInformer.
func (v *version) Routers() RouterInformer {
return &routerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/role.go b/pkg/clients/informers/externalversions/api/v1alpha1/role.go
new file mode 100644
index 00000000..cfdb7de7
--- /dev/null
+++ b/pkg/clients/informers/externalversions/api/v1alpha1/role.go
@@ -0,0 +1,102 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleInformer provides access to a shared informer and lister for
+// Roles.
+type RoleInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() apiv1alpha1.RoleLister
+}
+
+type roleInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewRoleInformer constructs a new informer for Role 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 NewRoleInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredRoleInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredRoleInformer constructs a new informer for Role 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 NewFilteredRoleInformer(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().Roles(namespace).List(context.Background(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Roles(namespace).Watch(context.Background(), options)
+ },
+ ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Roles(namespace).List(ctx, options)
+ },
+ WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.OpenstackV1alpha1().Roles(namespace).Watch(ctx, options)
+ },
+ },
+ &v2apiv1alpha1.Role{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *roleInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredRoleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *roleInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&v2apiv1alpha1.Role{}, f.defaultInformer)
+}
+
+func (f *roleInformer) Lister() apiv1alpha1.RoleLister {
+ return apiv1alpha1.NewRoleLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go
index ff0389c3..7b5c45f2 100644
--- a/pkg/clients/informers/externalversions/generic.go
+++ b/pkg/clients/informers/externalversions/generic.go
@@ -69,6 +69,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Ports().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("projects"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Projects().Informer()}, nil
+ case v1alpha1.SchemeGroupVersion.WithResource("roles"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Roles().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("routers"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Routers().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("routerinterfaces"):
diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go
index 4bc2c49b..960012d3 100644
--- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go
+++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go
@@ -82,6 +82,14 @@ type ProjectListerExpansion interface{}
// ProjectNamespaceLister.
type ProjectNamespaceListerExpansion interface{}
+// RoleListerExpansion allows custom methods to be added to
+// RoleLister.
+type RoleListerExpansion interface{}
+
+// RoleNamespaceListerExpansion allows custom methods to be added to
+// RoleNamespaceLister.
+type RoleNamespaceListerExpansion interface{}
+
// RouterListerExpansion allows custom methods to be added to
// RouterLister.
type RouterListerExpansion interface{}
diff --git a/pkg/clients/listers/api/v1alpha1/role.go b/pkg/clients/listers/api/v1alpha1/role.go
new file mode 100644
index 00000000..2068923b
--- /dev/null
+++ b/pkg/clients/listers/api/v1alpha1/role.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2025 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"
+)
+
+// RoleLister helps list Roles.
+// All objects returned here must be treated as read-only.
+type RoleLister interface {
+ // List lists all Roles in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.Role, err error)
+ // Roles returns an object that can list and get Roles.
+ Roles(namespace string) RoleNamespaceLister
+ RoleListerExpansion
+}
+
+// roleLister implements the RoleLister interface.
+type roleLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.Role]
+}
+
+// NewRoleLister returns a new RoleLister.
+func NewRoleLister(indexer cache.Indexer) RoleLister {
+ return &roleLister{listers.New[*apiv1alpha1.Role](indexer, apiv1alpha1.Resource("role"))}
+}
+
+// Roles returns an object that can list and get Roles.
+func (s *roleLister) Roles(namespace string) RoleNamespaceLister {
+ return roleNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Role](s.ResourceIndexer, namespace)}
+}
+
+// RoleNamespaceLister helps list and get Roles.
+// All objects returned here must be treated as read-only.
+type RoleNamespaceLister interface {
+ // List lists all Roles in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*apiv1alpha1.Role, err error)
+ // Get retrieves the Role from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*apiv1alpha1.Role, error)
+ RoleNamespaceListerExpansion
+}
+
+// roleNamespaceLister implements the RoleNamespaceLister
+// interface.
+type roleNamespaceLister struct {
+ listers.ResourceIndexer[*apiv1alpha1.Role]
+}
diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md
index 7b03fc83..3dc91fde 100644
--- a/website/docs/crd-reference.md
+++ b/website/docs/crd-reference.md
@@ -18,6 +18,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API
- [Network](#network)
- [Port](#port)
- [Project](#project)
+- [Role](#role)
- [Router](#router)
- [RouterInterface](#routerinterface)
- [SecurityGroup](#securitygroup)
@@ -169,6 +170,7 @@ _Appears in:_
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
+- [RoleSpec](#rolespec)
- [RouterSpec](#routerspec)
- [SecurityGroupSpec](#securitygroupspec)
- [ServerGroupSpec](#servergroupspec)
@@ -1440,6 +1442,8 @@ _Appears in:_
- [DomainResourceSpec](#domainresourcespec)
- [ProjectFilter](#projectfilter)
- [ProjectResourceSpec](#projectresourcespec)
+- [RoleFilter](#rolefilter)
+- [RoleResourceSpec](#roleresourcespec)
@@ -1479,6 +1483,8 @@ _Appears in:_
- [NetworkResourceSpec](#networkresourcespec)
- [PortFilter](#portfilter)
- [PortResourceSpec](#portresourcespec)
+- [RoleFilter](#rolefilter)
+- [RoleResourceSpec](#roleresourcespec)
- [RouterFilter](#routerfilter)
- [RouterInterfaceSpec](#routerinterfacespec)
- [RouterResourceSpec](#routerresourcespec)
@@ -1541,6 +1547,7 @@ _Appears in:_
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
+- [RoleSpec](#rolespec)
- [RouterSpec](#routerspec)
- [SecurityGroupSpec](#securitygroupspec)
- [ServerGroupSpec](#servergroupspec)
@@ -1573,6 +1580,7 @@ _Appears in:_
- [NetworkSpec](#networkspec)
- [PortSpec](#portspec)
- [ProjectSpec](#projectspec)
+- [RoleSpec](#rolespec)
- [RouterSpec](#routerspec)
- [SecurityGroupSpec](#securitygroupspec)
- [ServerGroupSpec](#servergroupspec)
@@ -2306,6 +2314,137 @@ _Appears in:_
| `segmentationID` _integer_ | segmentationID is the ID of the isolated segment on the
physical network. The network_type attribute defines the
segmentation model. For example, if the network_type value is vlan,
this ID is a vlan identifier. If the network_type value is gre, this
ID is a gre key. | | |
+#### Role
+
+
+
+Role is the Schema for an ORC resource.
+
+
+
+
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | |
+| `kind` _string_ | `Role` | | |
+| `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` _[RoleSpec](#rolespec)_ | spec specifies the desired state of the resource. | | |
+| `status` _[RoleStatus](#rolestatus)_ | status defines the observed state of the resource. | | |
+
+
+#### RoleFilter
+
+
+
+RoleFilter defines an existing resource by its properties
+
+_Validation:_
+- MinProperties: 1
+
+_Appears in:_
+- [RoleImport](#roleimport)
+
+| 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 this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
|
+
+
+#### RoleImport
+
+
+
+RoleImport specifies an existing resource which will be imported instead of
+creating a new one
+
+_Validation:_
+- MaxProperties: 1
+- MinProperties: 1
+
+_Appears in:_
+- [RoleSpec](#rolespec)
+
+| 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` _[RoleFilter](#rolefilter)_ | 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
|
+
+
+#### RoleResourceSpec
+
+
+
+RoleResourceSpec contains the desired state of the resource.
+
+
+
+_Appears in:_
+- [RoleSpec](#rolespec)
+
+| 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
|
+| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
|
+
+
+#### RoleResourceStatus
+
+
+
+RoleResourceStatus represents the observed state of the resource.
+
+
+
+_Appears in:_
+- [RoleStatus](#rolestatus)
+
+| 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
|
+| `domainID` _string_ | domainID is the domain owner of the role. | | MaxLength: 1024
|
+
+
+#### RoleSpec
+
+
+
+RoleSpec defines the desired state of an ORC object.
+
+
+
+_Appears in:_
+- [Role](#role)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `import` _[RoleImport](#roleimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
|
+| `resource` _[RoleResourceSpec](#roleresourcespec)_ | 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 | | |
+
+
+#### RoleStatus
+
+
+
+RoleStatus defines the observed state of an ORC resource.
+
+
+
+_Appears in:_
+- [Role](#role)
+
+| 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` _[RoleResourceStatus](#roleresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | |
+
+
#### Router