diff --git a/PROJECT b/PROJECT index 53ffa1a48..3abeff6ae 100644 --- a/PROJECT +++ b/PROJECT @@ -24,6 +24,14 @@ resources: kind: FloatingIP path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: HostAggregate + 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 90673e1da..1dd3efbdb 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ kubectl delete -f $ORC_RELEASE |:---------------------------:|:-------:|:-------:|:--------:| | flavor | | ✔ | ✔ | | floating ip | | ◐ | ◐ | +| host aggregate | | ◐ | ◐ | | image | ✔ | ✔ | ✔ | | network | | ◐ | ◐ | | port | | ◐ | ◐ | diff --git a/api/v1alpha1/hostaggregate_types.go b/api/v1alpha1/hostaggregate_types.go new file mode 100644 index 000000000..79d15aae3 --- /dev/null +++ b/api/v1alpha1/hostaggregate_types.go @@ -0,0 +1,56 @@ +/* +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 + +// HostAggregateResourceSpec contains the desired state of the resource. +type HostAggregateResourceSpec struct { + // TODO(stephenfin): Enforce that the name should not contain a colon. + + // name will be the name of the created resource. If not specified, the + // name of the ORC object will be used. + // +optional + Name *OpenStackName `json:"name"` + + // availabilityZone is the availability zone of the host aggregate. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="availabilityZone is immutable" + // +optional + AvailabilityZone *string `json:"availabilityZone,omitempty"` +} + +// HostAggregateFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type HostAggregateFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` +} + +// HostAggregateResourceStatus represents the observed state of the resource. +type HostAggregateResourceStatus struct { + // availabilityZone is the availability zone of the host aggregate. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + AvailabilityZone string `json:"availabilityZone"` + + // name is a Human-readable name for the resource. Might not be unique. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Name string `json:"name"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b1bfe6771..41a903f42 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -796,6 +796,213 @@ func (in *FloatingIPStatus) DeepCopy() *FloatingIPStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregate) DeepCopyInto(out *HostAggregate) { + *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 HostAggregate. +func (in *HostAggregate) DeepCopy() *HostAggregate { + if in == nil { + return nil + } + out := new(HostAggregate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HostAggregate) 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 *HostAggregateFilter) DeepCopyInto(out *HostAggregateFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateFilter. +func (in *HostAggregateFilter) DeepCopy() *HostAggregateFilter { + if in == nil { + return nil + } + out := new(HostAggregateFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregateImport) DeepCopyInto(out *HostAggregateImport) { + *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(HostAggregateFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateImport. +func (in *HostAggregateImport) DeepCopy() *HostAggregateImport { + if in == nil { + return nil + } + out := new(HostAggregateImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregateList) DeepCopyInto(out *HostAggregateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HostAggregate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateList. +func (in *HostAggregateList) DeepCopy() *HostAggregateList { + if in == nil { + return nil + } + out := new(HostAggregateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HostAggregateList) 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 *HostAggregateResourceSpec) DeepCopyInto(out *HostAggregateResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.AvailabilityZone != nil { + in, out := &in.AvailabilityZone, &out.AvailabilityZone + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateResourceSpec. +func (in *HostAggregateResourceSpec) DeepCopy() *HostAggregateResourceSpec { + if in == nil { + return nil + } + out := new(HostAggregateResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregateResourceStatus) DeepCopyInto(out *HostAggregateResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateResourceStatus. +func (in *HostAggregateResourceStatus) DeepCopy() *HostAggregateResourceStatus { + if in == nil { + return nil + } + out := new(HostAggregateResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregateSpec) DeepCopyInto(out *HostAggregateSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(HostAggregateImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(HostAggregateResourceSpec) + (*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 HostAggregateSpec. +func (in *HostAggregateSpec) DeepCopy() *HostAggregateSpec { + if in == nil { + return nil + } + out := new(HostAggregateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAggregateStatus) DeepCopyInto(out *HostAggregateStatus) { + *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(HostAggregateResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAggregateStatus. +func (in *HostAggregateStatus) DeepCopy() *HostAggregateStatus { + if in == nil { + return nil + } + out := new(HostAggregateStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostRoute) DeepCopyInto(out *HostRoute) { *out = *in diff --git a/api/v1alpha1/zz_generated.hostaggregate-resource.go b/api/v1alpha1/zz_generated.hostaggregate-resource.go new file mode 100644 index 000000000..037c5d09c --- /dev/null +++ b/api/v1alpha1/zz_generated.hostaggregate-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" +) + +// HostAggregateImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type HostAggregateImport 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 *HostAggregateFilter `json:"filter,omitempty"` +} + +// HostAggregateSpec 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 HostAggregateSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *HostAggregateImport `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 *HostAggregateResourceSpec `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"` +} + +// HostAggregateStatus defines the observed state of an ORC resource. +type HostAggregateStatus 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 *HostAggregateResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &HostAggregate{} + +func (i *HostAggregate) 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" + +// HostAggregate is the Schema for an ORC resource. +type HostAggregate 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 HostAggregateSpec `json:"spec,omitempty"` + + // status defines the observed state of the resource. + // +optional + Status HostAggregateStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// HostAggregateList contains a list of HostAggregate. +type HostAggregateList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of HostAggregate. + // +required + Items []HostAggregate `json:"items"` +} + +func (l *HostAggregateList) GetItems() []HostAggregate { + return l.Items +} + +func init() { + SchemeBuilder.Register(&HostAggregate{}, &HostAggregateList{}) +} + +func (i *HostAggregate) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &HostAggregate{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 6ed4a08e8..7e1fdc233 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -30,6 +30,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/flavor" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/floatingip" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/hostaggregate" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/image" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/network" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port" @@ -116,6 +117,7 @@ func main() { project.New(scopeFactory), volume.New(scopeFactory), volumetype.New(scopeFactory), + hostaggregate.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index da6eabd1b..e47f1bea2 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -58,6 +58,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregate": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregate(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateFilter": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateImport": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateList": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateSpec": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateStatus": schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRoute": schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRouteStatus": schema_openstack_resource_controller_v2_api_v1alpha1_HostRouteStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.IPv6Options": schema_openstack_resource_controller_v2_api_v1alpha1_IPv6Options(ref), @@ -1947,6 +1955,311 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref c } } +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregate 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.HostAggregateSpec"), + }, + }, + "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.HostAggregateStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateFilter 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: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateImport 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.HostAggregateFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateList contains a list of HostAggregate.", + 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 HostAggregate.", + 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.HostAggregate"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregate", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateResourceSpec 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: "", + }, + }, + "availabilityZone": { + SchemaProps: spec.SchemaProps{ + Description: "availabilityZone is the availability zone of the host aggregate.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateResourceStatus represents the observed state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "availabilityZone": { + SchemaProps: spec.SchemaProps{ + Description: "availabilityZone is the availability zone of the host aggregate.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a Human-readable name for the resource. Might not be unique.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateSpec 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.HostAggregateImport"), + }, + }, + "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.HostAggregateResourceSpec"), + }, + }, + "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.HostAggregateImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_HostAggregateStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAggregateStatus 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.HostAggregateResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostAggregateResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(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 3c4052978..479574080 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -85,6 +85,9 @@ var resources []templateFields = []templateFields{ IsNotNamed: true, // FloatingIP is not named in OpenStack ExistingOSClient: true, }, + { + Name: "HostAggregate", + }, { Name: "Image", SpecExtraValidations: []specExtraValidation{ diff --git a/config/crd/bases/openstack.k-orc.cloud_hostaggregates.yaml b/config/crd/bases/openstack.k-orc.cloud_hostaggregates.yaml new file mode 100644 index 000000000..089523b78 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_hostaggregates.yaml @@ -0,0 +1,284 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: hostaggregates.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: HostAggregate + listKind: HostAggregateList + plural: hostaggregates + singular: hostaggregate + 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: HostAggregate is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + 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: + availabilityZone: + description: availabilityZone is the availability zone of the + host aggregate. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: availabilityZone 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: 255 + minLength: 1 + pattern: ^[^,]+$ + 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: + availabilityZone: + description: availabilityZone is the availability zone of the + host aggregate. + maxLength: 255 + minLength: 1 + 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 0c66ee900..b5475a201 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,6 +5,7 @@ resources: - bases/openstack.k-orc.cloud_flavors.yaml - bases/openstack.k-orc.cloud_floatingips.yaml +- bases/openstack.k-orc.cloud_hostaggregates.yaml - bases/openstack.k-orc.cloud_images.yaml - bases/openstack.k-orc.cloud_networks.yaml - bases/openstack.k-orc.cloud_ports.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2cbfb27ab..d3f4df0fa 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -19,6 +19,7 @@ rules: resources: - flavors - floatingips + - hostaggregates - images - networks - ports @@ -44,6 +45,7 @@ rules: resources: - flavors/status - floatingips/status + - hostaggregates/status - images/status - networks/status - ports/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 7f233e00e..aae9ea8e6 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -3,6 +3,7 @@ resources: - openstack_v1alpha1_flavor.yaml - openstack_v1alpha1_floatingip.yaml +- openstack_v1alpha1_hostaggregate.yaml - openstack_v1alpha1_image.yaml - openstack_v1alpha1_network.yaml - openstack_v1alpha1_port.yaml diff --git a/config/samples/openstack_v1alpha1_hostaggregate.yaml b/config/samples/openstack_v1alpha1_hostaggregate.yaml new file mode 100644 index 000000000..1d9758bbc --- /dev/null +++ b/config/samples/openstack_v1alpha1_hostaggregate.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + availabilityZone: sample-az1 diff --git a/internal/controllers/hostaggregate/actuator.go b/internal/controllers/hostaggregate/actuator.go new file mode 100644 index 000000000..32d1b50a3 --- /dev/null +++ b/internal/controllers/hostaggregate/actuator.go @@ -0,0 +1,242 @@ +/* +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 hostaggregate + +import ( + "context" + "iter" + "strconv" + + "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates" + corev1 "k8s.io/api/core/v1" + 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 = aggregates.Aggregate + + 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 hostaggregateActuator struct { + osClient osclients.HostAggregateClient + k8sClient client.Client +} + +var _ createResourceActuator = hostaggregateActuator{} +var _ deleteResourceActuator = hostaggregateActuator{} + +// TODO(stephenfin): I suspect we need to change the interface since Nova expects integer IDs +func (hostaggregateActuator) GetResourceID(osResource *osResourceT) string { + return strconv.Itoa(osResource.ID) +} + +func (actuator hostaggregateActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + iid, err := strconv.Atoi(id) + if err != nil { + return nil, progress.WrapError(err) + } + resource, err := actuator.osClient.GetHostAggregate(ctx, iid) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator hostaggregateActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + var filters []osclients.ResourceFilter[osResourceT] + + // NOTE: The API doesn't allow filtering by name or description, we'll have to do it client-side. + filters = append(filters, + func(f *aggregates.Aggregate) bool { + name := getResourceName(orcObject) + // Compare non-pointer values + return f.Name == name + }, + ) + + return actuator.listOSResources(ctx, filters), true +} + +func (actuator hostaggregateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + var filters []osclients.ResourceFilter[osResourceT] + + // NOTE: The API doesn't allow filtering by name or description, we'll have to do it client-side. + if filter.Name != nil { + filters = append(filters, func(f *aggregates.Aggregate) bool { + return f.Name == string(*filter.Name) + }) + } + + return actuator.listOSResources(ctx, filters), nil +} + +func (actuator hostaggregateActuator) listOSResources(ctx context.Context, filters []osclients.ResourceFilter[osResourceT]) iter.Seq2[*aggregates.Aggregate, error] { + volumetypes := actuator.osClient.ListHostAggregates(ctx) + return osclients.Filter(volumetypes, filters...) +} + +func (actuator hostaggregateActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + createOpts := aggregates.CreateOpts{ + Name: getResourceName(obj), + } + + osResource, err := actuator.osClient.CreateHostAggregate(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 hostaggregateActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteHostAggregate(ctx, resource.ID)) +} + +func (actuator hostaggregateActuator) 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 := aggregates.UpdateOpts{} + + handleNameUpdate(&updateOpts, obj, osResource) + + // TODO(scaffolding): add handler for all fields supporting mutability + + 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.UpdateHostAggregate(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 aggregates.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToAggregatesUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["aggregate"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *aggregates.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = name + } +} + +func (actuator hostaggregateActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type hostaggregateHelperFactory struct{} + +var _ helperFactory = hostaggregateHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.HostAggregate, controller interfaces.ResourceController) (hostaggregateActuator, 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 hostaggregateActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return hostaggregateActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewHostAggregateClient() + if err != nil { + return hostaggregateActuator{}, progress.WrapError(err) + } + + return hostaggregateActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (hostaggregateHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return hostaggregateAdapter{obj} +} + +func (hostaggregateHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (hostaggregateHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/hostaggregate/actuator_test.go b/internal/controllers/hostaggregate/actuator_test.go new file mode 100644 index 000000000..516cf962a --- /dev/null +++ b/internal/controllers/hostaggregate/actuator_test.go @@ -0,0 +1,88 @@ +/* +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 hostaggregate + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates" + 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 aggregates.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: aggregates.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: aggregates.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.OpenStackName] + testCases := []struct { + name string + newValue *orcv1alpha1.OpenStackName + 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.HostAggregate{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.HostAggregateSpec{ + Resource: &orcv1alpha1.HostAggregateResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := aggregates.UpdateOpts{} + handleNameUpdate(&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/hostaggregate/controller.go b/internal/controllers/hostaggregate/controller.go new file mode 100644 index 000000000..6074e51f6 --- /dev/null +++ b/internal/controllers/hostaggregate/controller.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 hostaggregate + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials" +) + +const controllerName = "hostaggregate" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=hostaggregates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=hostaggregates/status,verbs=get;update;patch + +type hostaggregateReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return hostaggregateReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (hostaggregateReconcilerConstructor) GetName() string { + return controllerName +} + +// SetupWithManager sets up the controller with the Manager. +func (c hostaggregateReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + For(&orcv1alpha1.HostAggregate{}) + + if err := errors.Join( + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, hostaggregateHelperFactory{}, hostaggregateStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/hostaggregate/status.go b/internal/controllers/hostaggregate/status.go new file mode 100644 index 000000000..4bb8aab86 --- /dev/null +++ b/internal/controllers/hostaggregate/status.go @@ -0,0 +1,56 @@ +/* +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 hostaggregate + +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 hostaggregateStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.HostAggregateApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.HostAggregateStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.HostAggregate, *osResourceT, *objectApplyT, *statusApplyT] = hostaggregateStatusWriter{} + +func (hostaggregateStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.HostAggregate(name, namespace) +} + +func (hostaggregateStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.HostAggregate, 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 (hostaggregateStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.HostAggregateResourceStatus(). + WithName(osResource.Name) + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-assert.yaml new file mode 100644 index 000000000..850e548bb --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-create-full +status: + resource: + name: hostaggregate-create-full-override + 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: HostAggregate + name: hostaggregate-create-full + ref: hostaggregate +assertAll: + - celExpr: "hostaggregate.status.id != ''" diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-create-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-create-resource.yaml new file mode 100644 index 000000000..dde283421 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-create-resource.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: hostaggregate-create-full-override diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-secret.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-create-full/README.md b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/README.md new file mode 100644 index 000000000..5e525f2ed --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-full/README.md @@ -0,0 +1,11 @@ +# Create a HostAggregate with all the options + +## Step 00 + +Create a HostAggregate using all available fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name from the spec when it is specified. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-assert.yaml new file mode 100644 index 000000000..0c2af9b65 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-create-minimal +status: + resource: + name: hostaggregate-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: HostAggregate + name: hostaggregate-create-minimal + ref: hostaggregate +assertAll: + - celExpr: "hostaggregate.status.id != ''" diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-create-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..6c54df1fc --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-create-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-secret.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-create-minimal/01-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/01-assert.yaml new file mode 100644 index 000000000..bdb55c539 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate' in secret.metadata.finalizers" diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/01-delete-secret.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-create-minimal/README.md b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/README.md new file mode 100644 index 000000000..46fc4fb3b --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a host aggregate with the minimum options + +## Step 00 + +Create a minimal host aggregate, 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/hostaggregate/tests/hostaggregate-import-error/00-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-assert.yaml new file mode 100644 index 000000000..09782f941 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-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: HostAggregate +metadata: + name: hostaggregate-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/hostaggregate/tests/hostaggregate-import-error/00-create-resources.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-create-resources.yaml new file mode 100644 index 000000000..a680c01f4 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-create-resources.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import-error-external-1 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import-error-external-2 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-secret.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-import-error/01-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/01-assert.yaml new file mode 100644 index 000000000..ac3d4bd02 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-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/hostaggregate/tests/hostaggregate-import-error/01-import-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/01-import-resource.yaml new file mode 100644 index 000000000..cb80cd159 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: HostAggregate from "import error" test diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import-error/README.md b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/README.md new file mode 100644 index 000000000..2e68b200f --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import-error/README.md @@ -0,0 +1,13 @@ +# Import HostAggregate with more than one matching resources + +## Step 00 + +Create two HostAggregates with identical specs. + +## Step 01 + +Ensure that an imported HostAggregate with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/00-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/00-assert.yaml new file mode 100644 index 000000000..b47275d76 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/00-import-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/00-import-resource.yaml new file mode 100644 index 000000000..45e6fab06 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/00-import-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: hostaggregate-import-external + availabilityZone: test-az diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/00-secret.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-import/01-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/01-assert.yaml new file mode 100644 index 000000000..f1d1e312b --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/01-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-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: hostaggregate-import-external-not-this-one + availabilityZone: test-az +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/01-create-trap-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..6bb5ca872 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/01-create-trap-resource.yaml @@ -0,0 +1,15 @@ +--- +# This `hostaggregate-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: HostAggregate +metadata: + name: hostaggregate-import-external-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + availabilityZone: test-az diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/02-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/02-assert.yaml new file mode 100644 index 000000000..a8b119b99 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/02-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: HostAggregate + name: hostaggregate-import-external + ref: hostaggregate1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: HostAggregate + name: hostaggregate-import-external-not-this-one + ref: hostaggregate2 +assertAll: + - celExpr: "hostaggregate1.status.id != hostaggregate2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: hostaggregate-import-external + availabilityZone: test-az diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/02-create-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-import/02-create-resource.yaml new file mode 100644 index 000000000..58d39f608 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/02-create-resource.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + availabilityZone: test-az diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-import/README.md b/internal/controllers/hostaggregate/tests/hostaggregate-import/README.md new file mode 100644 index 000000000..5093755bd --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-import/README.md @@ -0,0 +1,18 @@ +# Import HostAggregate + +## Step 00 + +Import a host aggregate, matching all of the available filter's fields, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a host aggregate 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 + +Create a host aggregate matching the filter and verify that the observed status on the imported host aggregate corresponds to the spec of the created host aggregate. +Also verify that the created host aggregate didn't adopt the one which name is a superstring of it. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/00-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/00-assert.yaml new file mode 100644 index 000000000..ba3c06432 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: HostAggregate + name: hostaggregate-update + ref: hostaggregate +assertAll: [] +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-update +status: + resource: + name: hostaggregate-update + # TODO(scaffolding): Add matches for more fields + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/00-minimal-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/00-minimal-resource.yaml new file mode 100644 index 000000000..ae79736f0 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/00-minimal-resource.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/00-prerequisites.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/00-prerequisites.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-update/01-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/01-assert.yaml new file mode 100644 index 000000000..c89b3e17a --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-update +status: + resource: + name: hostaggregate-update-updated + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/01-updated-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/01-updated-resource.yaml new file mode 100644 index 000000000..cb4aa8893 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/01-updated-resource.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-update +spec: + resource: + name: hostaggregate-update-updated diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/02-assert.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/02-assert.yaml new file mode 100644 index 000000000..a18a21e79 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/02-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: HostAggregate + name: hostaggregate-update + ref: hostaggregate +assertAll: [] +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: HostAggregate +metadata: + name: hostaggregate-update +status: + resource: + name: hostaggregate-update + # TODO(scaffolding): validate that updated fields were all reverted to their original value + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/hostaggregate/tests/hostaggregate-update/02-reverted-resource.yaml b/internal/controllers/hostaggregate/tests/hostaggregate-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-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/hostaggregate/tests/hostaggregate-update/README.md b/internal/controllers/hostaggregate/tests/hostaggregate-update/README.md new file mode 100644 index 000000000..58d74f2e3 --- /dev/null +++ b/internal/controllers/hostaggregate/tests/hostaggregate-update/README.md @@ -0,0 +1,17 @@ +# Update HostAggregate + +## Step 00 + +Create a HostAggregate 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/hostaggregate/zz_generated.adapter.go b/internal/controllers/hostaggregate/zz_generated.adapter.go new file mode 100644 index 000000000..1bc603309 --- /dev/null +++ b/internal/controllers/hostaggregate/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 hostaggregate + +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.HostAggregate + orcObjectListT = orcv1alpha1.HostAggregateList + resourceSpecT = orcv1alpha1.HostAggregateResourceSpec + filterT = orcv1alpha1.HostAggregateFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = hostaggregateAdapter +) + +type hostaggregateAdapter struct { + *orcv1alpha1.HostAggregate +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.HostAggregate +} + +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/hostaggregate/zz_generated.controller.go b/internal/controllers/hostaggregate/zz_generated.controller.go new file mode 100644 index 000000000..d47204ea9 --- /dev/null +++ b/internal/controllers/hostaggregate/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 hostaggregate + +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/hostaggregate.go b/internal/osclients/hostaggregate.go new file mode 100644 index 000000000..2b86d6893 --- /dev/null +++ b/internal/osclients/hostaggregate.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/compute/v2/aggregates" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type HostAggregateClient interface { + ListHostAggregates(ctx context.Context) iter.Seq2[*aggregates.Aggregate, error] + CreateHostAggregate(ctx context.Context, opts aggregates.CreateOptsBuilder) (*aggregates.Aggregate, error) + DeleteHostAggregate(ctx context.Context, resourceID int) error + GetHostAggregate(ctx context.Context, resourceID int) (*aggregates.Aggregate, error) + UpdateHostAggregate(ctx context.Context, id int, opts aggregates.UpdateOptsBuilder) (*aggregates.Aggregate, error) +} + +type hostaggregateClient struct{ client *gophercloud.ServiceClient } + +// NewHostAggregateClient returns a new OpenStack client. +func NewHostAggregateClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (HostAggregateClient, error) { + client, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create hostaggregate service client: %v", err) + } + + return &hostaggregateClient{client}, nil +} + +func (c hostaggregateClient) ListHostAggregates(ctx context.Context) iter.Seq2[*aggregates.Aggregate, error] { + pager := aggregates.List(c.client) + return func(yield func(*aggregates.Aggregate, error) bool) { + _ = pager.EachPage(ctx, yieldPage(aggregates.ExtractAggregates, yield)) + } +} + +func (c hostaggregateClient) CreateHostAggregate(ctx context.Context, opts aggregates.CreateOptsBuilder) (*aggregates.Aggregate, error) { + return aggregates.Create(ctx, c.client, opts).Extract() +} + +func (c hostaggregateClient) DeleteHostAggregate(ctx context.Context, resourceID int) error { + return aggregates.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c hostaggregateClient) GetHostAggregate(ctx context.Context, resourceID int) (*aggregates.Aggregate, error) { + return aggregates.Get(ctx, c.client, resourceID).Extract() +} + +func (c hostaggregateClient) UpdateHostAggregate(ctx context.Context, resourceID int, opts aggregates.UpdateOptsBuilder) (*aggregates.Aggregate, error) { + return aggregates.Update(ctx, c.client, resourceID, opts).Extract() +} + +type hostaggregateErrorClient struct{ error } + +// NewHostAggregateErrorClient returns a HostAggregateClient in which every method returns the given error. +func NewHostAggregateErrorClient(e error) HostAggregateClient { + return hostaggregateErrorClient{e} +} + +func (e hostaggregateErrorClient) ListHostAggregates(_ context.Context) iter.Seq2[*aggregates.Aggregate, error] { + return func(yield func(*aggregates.Aggregate, error) bool) { + yield(nil, e.error) + } +} + +func (e hostaggregateErrorClient) CreateHostAggregate(_ context.Context, _ aggregates.CreateOptsBuilder) (*aggregates.Aggregate, error) { + return nil, e.error +} + +func (e hostaggregateErrorClient) DeleteHostAggregate(_ context.Context, _ int) error { + return e.error +} + +func (e hostaggregateErrorClient) GetHostAggregate(_ context.Context, _ int) (*aggregates.Aggregate, error) { + return nil, e.error +} + +func (e hostaggregateErrorClient) UpdateHostAggregate(_ context.Context, _ int, _ aggregates.UpdateOptsBuilder) (*aggregates.Aggregate, error) { + return nil, e.error +} diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index fa9985c52..3643faabd 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -35,6 +35,9 @@ import ( //go:generate mockgen -package mock -destination=identity.go -source=../identity.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock IdentityClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt identity.go > _identity.go && mv _identity.go identity.go" +//go:generate mockgen -package mock -destination=hostaggregate.go -source=../hostaggregate.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock HostAggregateClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt hostaggregate.go > _hostaggregate.go && mv _hostaggregate.go hostaggregate.go" + //go:generate mockgen -package mock -destination=volume.go -source=../volume.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock VolumeClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt volume.go > _volume.go && mv _volume.go volume.go" diff --git a/internal/osclients/mock/hostaggregate.go b/internal/osclients/mock/hostaggregate.go new file mode 100644 index 000000000..e8043c71a --- /dev/null +++ b/internal/osclients/mock/hostaggregate.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: ../hostaggregate.go +// +// Generated by this command: +// +// mockgen -package mock -destination=hostaggregate.go -source=../hostaggregate.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock HostAggregateClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + aggregates "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates" + gomock "go.uber.org/mock/gomock" +) + +// MockHostAggregateClient is a mock of HostAggregateClient interface. +type MockHostAggregateClient struct { + ctrl *gomock.Controller + recorder *MockHostAggregateClientMockRecorder + isgomock struct{} +} + +// MockHostAggregateClientMockRecorder is the mock recorder for MockHostAggregateClient. +type MockHostAggregateClientMockRecorder struct { + mock *MockHostAggregateClient +} + +// NewMockHostAggregateClient creates a new mock instance. +func NewMockHostAggregateClient(ctrl *gomock.Controller) *MockHostAggregateClient { + mock := &MockHostAggregateClient{ctrl: ctrl} + mock.recorder = &MockHostAggregateClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHostAggregateClient) EXPECT() *MockHostAggregateClientMockRecorder { + return m.recorder +} + +// CreateHostAggregate mocks base method. +func (m *MockHostAggregateClient) CreateHostAggregate(ctx context.Context, opts aggregates.CreateOptsBuilder) (*aggregates.Aggregate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateHostAggregate", ctx, opts) + ret0, _ := ret[0].(*aggregates.Aggregate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateHostAggregate indicates an expected call of CreateHostAggregate. +func (mr *MockHostAggregateClientMockRecorder) CreateHostAggregate(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHostAggregate", reflect.TypeOf((*MockHostAggregateClient)(nil).CreateHostAggregate), ctx, opts) +} + +// DeleteHostAggregate mocks base method. +func (m *MockHostAggregateClient) DeleteHostAggregate(ctx context.Context, resourceID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteHostAggregate", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteHostAggregate indicates an expected call of DeleteHostAggregate. +func (mr *MockHostAggregateClientMockRecorder) DeleteHostAggregate(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteHostAggregate", reflect.TypeOf((*MockHostAggregateClient)(nil).DeleteHostAggregate), ctx, resourceID) +} + +// GetHostAggregate mocks base method. +func (m *MockHostAggregateClient) GetHostAggregate(ctx context.Context, resourceID int) (*aggregates.Aggregate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHostAggregate", ctx, resourceID) + ret0, _ := ret[0].(*aggregates.Aggregate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHostAggregate indicates an expected call of GetHostAggregate. +func (mr *MockHostAggregateClientMockRecorder) GetHostAggregate(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHostAggregate", reflect.TypeOf((*MockHostAggregateClient)(nil).GetHostAggregate), ctx, resourceID) +} + +// ListHostAggregates mocks base method. +func (m *MockHostAggregateClient) ListHostAggregates(ctx context.Context) iter.Seq2[*aggregates.Aggregate, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListHostAggregates", ctx) + ret0, _ := ret[0].(iter.Seq2[*aggregates.Aggregate, error]) + return ret0 +} + +// ListHostAggregates indicates an expected call of ListHostAggregates. +func (mr *MockHostAggregateClientMockRecorder) ListHostAggregates(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListHostAggregates", reflect.TypeOf((*MockHostAggregateClient)(nil).ListHostAggregates), ctx) +} + +// UpdateHostAggregate mocks base method. +func (m *MockHostAggregateClient) UpdateHostAggregate(ctx context.Context, id int, opts aggregates.UpdateOptsBuilder) (*aggregates.Aggregate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateHostAggregate", ctx, id, opts) + ret0, _ := ret[0].(*aggregates.Aggregate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateHostAggregate indicates an expected call of UpdateHostAggregate. +func (mr *MockHostAggregateClientMockRecorder) UpdateHostAggregate(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHostAggregate", reflect.TypeOf((*MockHostAggregateClient)(nil).UpdateHostAggregate), ctx, id, opts) +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 0033895bc..e3c556062 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -34,12 +34,13 @@ import ( // MockScopeFactory implements both the ScopeFactory and ClientScope interfaces. It can be used in place of the default ProviderScopeFactory // when we want to use mocked service clients which do not attempt to connect to a running OpenStack cloud. type MockScopeFactory struct { - ComputeClient *mock.MockComputeClient - NetworkClient *mock.MockNetworkClient - ImageClient *mock.MockImageClient - IdentityClient *mock.MockIdentityClient - VolumeClient *mock.MockVolumeClient - VolumeTypeClient *mock.MockVolumeTypeClient + ComputeClient *mock.MockComputeClient + NetworkClient *mock.MockNetworkClient + ImageClient *mock.MockImageClient + IdentityClient *mock.MockIdentityClient + VolumeClient *mock.MockVolumeClient + VolumeTypeClient *mock.MockVolumeTypeClient + HostAggregateClient *mock.MockHostAggregateClient clientScopeCreateError error } @@ -51,14 +52,16 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { identityClient := mock.NewMockIdentityClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) + hostAggregateClient := mock.NewMockHostAggregateClient(mockCtrl) return &MockScopeFactory{ - ComputeClient: computeClient, - ImageClient: imageClient, - NetworkClient: networkClient, - IdentityClient: identityClient, - VolumeClient: volumeClient, - VolumeTypeClient: volumetypeClient, + ComputeClient: computeClient, + ImageClient: imageClient, + NetworkClient: networkClient, + IdentityClient: identityClient, + VolumeClient: volumeClient, + VolumeTypeClient: volumetypeClient, + HostAggregateClient: hostAggregateClient, } } @@ -97,6 +100,10 @@ func (f *MockScopeFactory) NewVolumeTypeClient() (osclients.VolumeTypeClient, er return f.VolumeTypeClient, nil } +func (f *MockScopeFactory) NewHostAggregateClient() (osclients.HostAggregateClient, error) { + return f.HostAggregateClient, 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 4f711f1b4..46be21b4c 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -161,6 +161,10 @@ func (s *providerScope) NewVolumeTypeClient() (clients.VolumeTypeClient, error) return clients.NewVolumeTypeClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewHostAggregateClient() (clients.HostAggregateClient, error) { + return clients.NewHostAggregateClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) ExtractToken() (*tokens.Token, error) { client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{}) if err != nil { diff --git a/internal/scope/scope.go b/internal/scope/scope.go index c5eeec0f8..e76601b18 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -54,6 +54,7 @@ type Scope interface { NewIdentityClient() (osclients.IdentityClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) + NewHostAggregateClient() (osclients.HostAggregateClient, error) ExtractToken() (*tokens.Token, error) } diff --git a/kuttl-test.yaml b/kuttl-test.yaml index b6ad71ea5..4729a1008 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -4,6 +4,7 @@ kind: TestSuite testDirs: - ./internal/controllers/flavor/tests/ - ./internal/controllers/floatingip/tests/ +- ./internal/controllers/hostaggregate/tests/ - ./internal/controllers/image/tests/ - ./internal/controllers/network/tests/ - ./internal/controllers/port/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregate.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregate.go new file mode 100644 index 000000000..3de2c1b35 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregate.go @@ -0,0 +1,264 @@ +/* +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" +) + +// HostAggregateApplyConfiguration represents a declarative configuration of the HostAggregate type for use +// with apply. +type HostAggregateApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *HostAggregateSpecApplyConfiguration `json:"spec,omitempty"` + Status *HostAggregateStatusApplyConfiguration `json:"status,omitempty"` +} + +// HostAggregate constructs a declarative configuration of the HostAggregate type for use with +// apply. +func HostAggregate(name, namespace string) *HostAggregateApplyConfiguration { + b := &HostAggregateApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("HostAggregate") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractHostAggregate extracts the applied configuration owned by fieldManager from +// hostAggregate. If no managedFields are found in hostAggregate for fieldManager, a +// HostAggregateApplyConfiguration 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. +// hostAggregate must be a unmodified HostAggregate API object that was retrieved from the Kubernetes API. +// ExtractHostAggregate 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 ExtractHostAggregate(hostAggregate *apiv1alpha1.HostAggregate, fieldManager string) (*HostAggregateApplyConfiguration, error) { + return extractHostAggregate(hostAggregate, fieldManager, "") +} + +// ExtractHostAggregateStatus is the same as ExtractHostAggregate except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractHostAggregateStatus(hostAggregate *apiv1alpha1.HostAggregate, fieldManager string) (*HostAggregateApplyConfiguration, error) { + return extractHostAggregate(hostAggregate, fieldManager, "status") +} + +func extractHostAggregate(hostAggregate *apiv1alpha1.HostAggregate, fieldManager string, subresource string) (*HostAggregateApplyConfiguration, error) { + b := &HostAggregateApplyConfiguration{} + err := managedfields.ExtractInto(hostAggregate, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregate"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(hostAggregate.Name) + b.WithNamespace(hostAggregate.Namespace) + + b.WithKind("HostAggregate") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} + +// 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 *HostAggregateApplyConfiguration) WithKind(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithAPIVersion(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithName(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithGenerateName(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithNamespace(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithUID(value types.UID) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithResourceVersion(value string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithGeneration(value int64) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithCreationTimestamp(value metav1.Time) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithLabels(entries map[string]string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithAnnotations(entries map[string]string) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithFinalizers(values ...string) *HostAggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *HostAggregateApplyConfiguration) 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 *HostAggregateApplyConfiguration) WithSpec(value *HostAggregateSpecApplyConfiguration) *HostAggregateApplyConfiguration { + 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 *HostAggregateApplyConfiguration) WithStatus(value *HostAggregateStatusApplyConfiguration) *HostAggregateApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *HostAggregateApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatefilter.go new file mode 100644 index 000000000..4415c71c7 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatefilter.go @@ -0,0 +1,43 @@ +/* +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" +) + +// HostAggregateFilterApplyConfiguration represents a declarative configuration of the HostAggregateFilter type for use +// with apply. +type HostAggregateFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` +} + +// HostAggregateFilterApplyConfiguration constructs a declarative configuration of the HostAggregateFilter type for use with +// apply. +func HostAggregateFilter() *HostAggregateFilterApplyConfiguration { + return &HostAggregateFilterApplyConfiguration{} +} + +// 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 *HostAggregateFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *HostAggregateFilterApplyConfiguration { + b.Name = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateimport.go new file mode 100644 index 000000000..17c4599e0 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateimport.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 + +// HostAggregateImportApplyConfiguration represents a declarative configuration of the HostAggregateImport type for use +// with apply. +type HostAggregateImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *HostAggregateFilterApplyConfiguration `json:"filter,omitempty"` +} + +// HostAggregateImportApplyConfiguration constructs a declarative configuration of the HostAggregateImport type for use with +// apply. +func HostAggregateImport() *HostAggregateImportApplyConfiguration { + return &HostAggregateImportApplyConfiguration{} +} + +// 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 *HostAggregateImportApplyConfiguration) WithID(value string) *HostAggregateImportApplyConfiguration { + 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 *HostAggregateImportApplyConfiguration) WithFilter(value *HostAggregateFilterApplyConfiguration) *HostAggregateImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcespec.go new file mode 100644 index 000000000..65529a91a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcespec.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" +) + +// HostAggregateResourceSpecApplyConfiguration represents a declarative configuration of the HostAggregateResourceSpec type for use +// with apply. +type HostAggregateResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + AvailabilityZone *string `json:"availabilityZone,omitempty"` +} + +// HostAggregateResourceSpecApplyConfiguration constructs a declarative configuration of the HostAggregateResourceSpec type for use with +// apply. +func HostAggregateResourceSpec() *HostAggregateResourceSpecApplyConfiguration { + return &HostAggregateResourceSpecApplyConfiguration{} +} + +// 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 *HostAggregateResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *HostAggregateResourceSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithAvailabilityZone sets the AvailabilityZone 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 AvailabilityZone field is set to the value of the last call. +func (b *HostAggregateResourceSpecApplyConfiguration) WithAvailabilityZone(value string) *HostAggregateResourceSpecApplyConfiguration { + b.AvailabilityZone = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcestatus.go new file mode 100644 index 000000000..14bce7fac --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregateresourcestatus.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 + +// HostAggregateResourceStatusApplyConfiguration represents a declarative configuration of the HostAggregateResourceStatus type for use +// with apply. +type HostAggregateResourceStatusApplyConfiguration struct { + AvailabilityZone *string `json:"availabilityZone,omitempty"` + Name *string `json:"name,omitempty"` +} + +// HostAggregateResourceStatusApplyConfiguration constructs a declarative configuration of the HostAggregateResourceStatus type for use with +// apply. +func HostAggregateResourceStatus() *HostAggregateResourceStatusApplyConfiguration { + return &HostAggregateResourceStatusApplyConfiguration{} +} + +// WithAvailabilityZone sets the AvailabilityZone 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 AvailabilityZone field is set to the value of the last call. +func (b *HostAggregateResourceStatusApplyConfiguration) WithAvailabilityZone(value string) *HostAggregateResourceStatusApplyConfiguration { + b.AvailabilityZone = &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 *HostAggregateResourceStatusApplyConfiguration) WithName(value string) *HostAggregateResourceStatusApplyConfiguration { + b.Name = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatespec.go new file mode 100644 index 000000000..37cd712b1 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatespec.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" +) + +// HostAggregateSpecApplyConfiguration represents a declarative configuration of the HostAggregateSpec type for use +// with apply. +type HostAggregateSpecApplyConfiguration struct { + Import *HostAggregateImportApplyConfiguration `json:"import,omitempty"` + Resource *HostAggregateResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// HostAggregateSpecApplyConfiguration constructs a declarative configuration of the HostAggregateSpec type for use with +// apply. +func HostAggregateSpec() *HostAggregateSpecApplyConfiguration { + return &HostAggregateSpecApplyConfiguration{} +} + +// 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 *HostAggregateSpecApplyConfiguration) WithImport(value *HostAggregateImportApplyConfiguration) *HostAggregateSpecApplyConfiguration { + 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 *HostAggregateSpecApplyConfiguration) WithResource(value *HostAggregateResourceSpecApplyConfiguration) *HostAggregateSpecApplyConfiguration { + 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 *HostAggregateSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *HostAggregateSpecApplyConfiguration { + 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 *HostAggregateSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *HostAggregateSpecApplyConfiguration { + 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 *HostAggregateSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *HostAggregateSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatestatus.go new file mode 100644 index 000000000..34eb72b0e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostaggregatestatus.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" +) + +// HostAggregateStatusApplyConfiguration represents a declarative configuration of the HostAggregateStatus type for use +// with apply. +type HostAggregateStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *HostAggregateResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// HostAggregateStatusApplyConfiguration constructs a declarative configuration of the HostAggregateStatus type for use with +// apply. +func HostAggregateStatus() *HostAggregateStatusApplyConfiguration { + return &HostAggregateStatusApplyConfiguration{} +} + +// 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 *HostAggregateStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *HostAggregateStatusApplyConfiguration { + 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 *HostAggregateStatusApplyConfiguration) WithID(value string) *HostAggregateStatusApplyConfiguration { + 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 *HostAggregateStatusApplyConfiguration) WithResource(value *HostAggregateResourceStatusApplyConfiguration) *HostAggregateStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 0b538069b..f5dc07992 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -445,6 +445,98 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FloatingIPResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregate + 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.HostAggregateSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateFilter + map: + fields: + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateResourceSpec + map: + fields: + - name: availabilityZone + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateResourceStatus + map: + fields: + - name: availabilityZone + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateSpec + 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.HostAggregateImport + - 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.HostAggregateResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostAggregateStatus + 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.HostAggregateResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostRoute map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 348f6a8c3..7d4ec2565 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -84,6 +84,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.FloatingIPSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("FloatingIPStatus"): return &apiv1alpha1.FloatingIPStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregate"): + return &apiv1alpha1.HostAggregateApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateFilter"): + return &apiv1alpha1.HostAggregateFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateImport"): + return &apiv1alpha1.HostAggregateImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateResourceSpec"): + return &apiv1alpha1.HostAggregateResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateResourceStatus"): + return &apiv1alpha1.HostAggregateResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateSpec"): + return &apiv1alpha1.HostAggregateSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostAggregateStatus"): + return &apiv1alpha1.HostAggregateStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRoute"): return &apiv1alpha1.HostRouteApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRouteStatus"): 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 2395c4f8d..c399a49ba 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -30,6 +30,7 @@ type OpenstackV1alpha1Interface interface { RESTClient() rest.Interface FlavorsGetter FloatingIPsGetter + HostAggregatesGetter ImagesGetter NetworksGetter PortsGetter @@ -57,6 +58,10 @@ func (c *OpenstackV1alpha1Client) FloatingIPs(namespace string) FloatingIPInterf return newFloatingIPs(c, namespace) } +func (c *OpenstackV1alpha1Client) HostAggregates(namespace string) HostAggregateInterface { + return newHostAggregates(c, namespace) +} + func (c *OpenstackV1alpha1Client) Images(namespace string) ImageInterface { return newImages(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 6214cb2fe..e6ed51b17 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 @@ -36,6 +36,10 @@ func (c *FakeOpenstackV1alpha1) FloatingIPs(namespace string) v1alpha1.FloatingI return newFakeFloatingIPs(c, namespace) } +func (c *FakeOpenstackV1alpha1) HostAggregates(namespace string) v1alpha1.HostAggregateInterface { + return newFakeHostAggregates(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Images(namespace string) v1alpha1.ImageInterface { return newFakeImages(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_hostaggregate.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_hostaggregate.go new file mode 100644 index 000000000..c1c30c7fc --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_hostaggregate.go @@ -0,0 +1,53 @@ +/* +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" +) + +// fakeHostAggregates implements HostAggregateInterface +type fakeHostAggregates struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.HostAggregate, *v1alpha1.HostAggregateList, *apiv1alpha1.HostAggregateApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeHostAggregates(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.HostAggregateInterface { + return &fakeHostAggregates{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.HostAggregate, *v1alpha1.HostAggregateList, *apiv1alpha1.HostAggregateApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("hostaggregates"), + v1alpha1.SchemeGroupVersion.WithKind("HostAggregate"), + func() *v1alpha1.HostAggregate { return &v1alpha1.HostAggregate{} }, + func() *v1alpha1.HostAggregateList { return &v1alpha1.HostAggregateList{} }, + func(dst, src *v1alpha1.HostAggregateList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.HostAggregateList) []*v1alpha1.HostAggregate { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.HostAggregateList, items []*v1alpha1.HostAggregate) { + 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 f2f582633..4595bfa25 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -22,6 +22,8 @@ type FlavorExpansion interface{} type FloatingIPExpansion interface{} +type HostAggregateExpansion interface{} + type ImageExpansion interface{} type NetworkExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/hostaggregate.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/hostaggregate.go new file mode 100644 index 000000000..c16d0b753 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/hostaggregate.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" +) + +// HostAggregatesGetter has a method to return a HostAggregateInterface. +// A group's client should implement this interface. +type HostAggregatesGetter interface { + HostAggregates(namespace string) HostAggregateInterface +} + +// HostAggregateInterface has methods to work with HostAggregate resources. +type HostAggregateInterface interface { + Create(ctx context.Context, hostAggregate *apiv1alpha1.HostAggregate, opts v1.CreateOptions) (*apiv1alpha1.HostAggregate, error) + Update(ctx context.Context, hostAggregate *apiv1alpha1.HostAggregate, opts v1.UpdateOptions) (*apiv1alpha1.HostAggregate, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, hostAggregate *apiv1alpha1.HostAggregate, opts v1.UpdateOptions) (*apiv1alpha1.HostAggregate, 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.HostAggregate, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.HostAggregateList, 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.HostAggregate, err error) + Apply(ctx context.Context, hostAggregate *applyconfigurationapiv1alpha1.HostAggregateApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.HostAggregate, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, hostAggregate *applyconfigurationapiv1alpha1.HostAggregateApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.HostAggregate, err error) + HostAggregateExpansion +} + +// hostAggregates implements HostAggregateInterface +type hostAggregates struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.HostAggregate, *apiv1alpha1.HostAggregateList, *applyconfigurationapiv1alpha1.HostAggregateApplyConfiguration] +} + +// newHostAggregates returns a HostAggregates +func newHostAggregates(c *OpenstackV1alpha1Client, namespace string) *hostAggregates { + return &hostAggregates{ + gentype.NewClientWithListAndApply[*apiv1alpha1.HostAggregate, *apiv1alpha1.HostAggregateList, *applyconfigurationapiv1alpha1.HostAggregateApplyConfiguration]( + "hostaggregates", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.HostAggregate { return &apiv1alpha1.HostAggregate{} }, + func() *apiv1alpha1.HostAggregateList { return &apiv1alpha1.HostAggregateList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/hostaggregate.go b/pkg/clients/informers/externalversions/api/v1alpha1/hostaggregate.go new file mode 100644 index 000000000..0f100c94d --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/hostaggregate.go @@ -0,0 +1,90 @@ +/* +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" +) + +// HostAggregateInformer provides access to a shared informer and lister for +// HostAggregates. +type HostAggregateInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.HostAggregateLister +} + +type hostAggregateInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewHostAggregateInformer constructs a new informer for HostAggregate 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 NewHostAggregateInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredHostAggregateInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredHostAggregateInformer constructs a new informer for HostAggregate 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 NewFilteredHostAggregateInformer(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().HostAggregates(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().HostAggregates(namespace).Watch(context.TODO(), options) + }, + }, + &v2apiv1alpha1.HostAggregate{}, + resyncPeriod, + indexers, + ) +} + +func (f *hostAggregateInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredHostAggregateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *hostAggregateInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.HostAggregate{}, f.defaultInformer) +} + +func (f *hostAggregateInformer) Lister() apiv1alpha1.HostAggregateLister { + return apiv1alpha1.NewHostAggregateLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 89caabd38..e700e74a3 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -28,6 +28,8 @@ type Interface interface { Flavors() FlavorInformer // FloatingIPs returns a FloatingIPInformer. FloatingIPs() FloatingIPInformer + // HostAggregates returns a HostAggregateInformer. + HostAggregates() HostAggregateInformer // Images returns a ImageInformer. Images() ImageInformer // Networks returns a NetworkInformer. @@ -75,6 +77,11 @@ func (v *version) FloatingIPs() FloatingIPInformer { return &floatingIPInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// HostAggregates returns a HostAggregateInformer. +func (v *version) HostAggregates() HostAggregateInformer { + return &hostAggregateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Images returns a ImageInformer. func (v *version) Images() ImageInformer { return &imageInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index bbbcadc08..8125ce2e8 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -57,6 +57,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Flavors().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("floatingips"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().FloatingIPs().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("hostaggregates"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().HostAggregates().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("images"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Images().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("networks"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 0ee736357..41c7993b7 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -34,6 +34,14 @@ type FloatingIPListerExpansion interface{} // FloatingIPNamespaceLister. type FloatingIPNamespaceListerExpansion interface{} +// HostAggregateListerExpansion allows custom methods to be added to +// HostAggregateLister. +type HostAggregateListerExpansion interface{} + +// HostAggregateNamespaceListerExpansion allows custom methods to be added to +// HostAggregateNamespaceLister. +type HostAggregateNamespaceListerExpansion interface{} + // ImageListerExpansion allows custom methods to be added to // ImageLister. type ImageListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/hostaggregate.go b/pkg/clients/listers/api/v1alpha1/hostaggregate.go new file mode 100644 index 000000000..92a598927 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/hostaggregate.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" +) + +// HostAggregateLister helps list HostAggregates. +// All objects returned here must be treated as read-only. +type HostAggregateLister interface { + // List lists all HostAggregates in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.HostAggregate, err error) + // HostAggregates returns an object that can list and get HostAggregates. + HostAggregates(namespace string) HostAggregateNamespaceLister + HostAggregateListerExpansion +} + +// hostAggregateLister implements the HostAggregateLister interface. +type hostAggregateLister struct { + listers.ResourceIndexer[*apiv1alpha1.HostAggregate] +} + +// NewHostAggregateLister returns a new HostAggregateLister. +func NewHostAggregateLister(indexer cache.Indexer) HostAggregateLister { + return &hostAggregateLister{listers.New[*apiv1alpha1.HostAggregate](indexer, apiv1alpha1.Resource("hostaggregate"))} +} + +// HostAggregates returns an object that can list and get HostAggregates. +func (s *hostAggregateLister) HostAggregates(namespace string) HostAggregateNamespaceLister { + return hostAggregateNamespaceLister{listers.NewNamespaced[*apiv1alpha1.HostAggregate](s.ResourceIndexer, namespace)} +} + +// HostAggregateNamespaceLister helps list and get HostAggregates. +// All objects returned here must be treated as read-only. +type HostAggregateNamespaceLister interface { + // List lists all HostAggregates in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.HostAggregate, err error) + // Get retrieves the HostAggregate from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.HostAggregate, error) + HostAggregateNamespaceListerExpansion +} + +// hostAggregateNamespaceLister implements the HostAggregateNamespaceLister +// interface. +type hostAggregateNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.HostAggregate] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 181103780..3f46bbd36 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -12,6 +12,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API ### Resource Types - [Flavor](#flavor) - [FloatingIP](#floatingip) +- [HostAggregate](#hostaggregate) - [Image](#image) - [Network](#network) - [Port](#port) @@ -160,6 +161,7 @@ CloudCredentialsReference is a reference to a secret containing OpenStack creden _Appears in:_ - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) +- [HostAggregateSpec](#hostaggregatespec) - [ImageSpec](#imagespec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) @@ -620,6 +622,134 @@ _Appears in:_ | `resource` _[FloatingIPResourceStatus](#floatingipresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +#### HostAggregate + + + +HostAggregate is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `HostAggregate` | | | +| `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` _[HostAggregateSpec](#hostaggregatespec)_ | spec specifies the desired state of the resource. | | | +| `status` _[HostAggregateStatus](#hostaggregatestatus)_ | status defines the observed state of the resource. | | | + + +#### HostAggregateFilter + + + +HostAggregateFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [HostAggregateImport](#hostaggregateimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| + + +#### HostAggregateImport + + + +HostAggregateImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [HostAggregateSpec](#hostaggregatespec) + +| 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` _[HostAggregateFilter](#hostaggregatefilter)_ | 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
| + + +#### HostAggregateResourceSpec + + + +HostAggregateResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [HostAggregateSpec](#hostaggregatespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `availabilityZone` _string_ | availabilityZone is the availability zone of the host aggregate. | | MaxLength: 255
MinLength: 1
| + + +#### HostAggregateResourceStatus + + + +HostAggregateResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [HostAggregateStatus](#hostaggregatestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `availabilityZone` _string_ | availabilityZone is the availability zone of the host aggregate. | | MaxLength: 255
MinLength: 1
| +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| + + +#### HostAggregateSpec + + + +HostAggregateSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [HostAggregate](#hostaggregate) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[HostAggregateImport](#hostaggregateimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[HostAggregateResourceSpec](#hostaggregateresourcespec)_ | 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 | | | + + +#### HostAggregateStatus + + + +HostAggregateStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [HostAggregate](#hostaggregate) + +| 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` _[HostAggregateResourceStatus](#hostaggregateresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + #### HostRoute @@ -1265,6 +1395,7 @@ _Appears in:_ _Appears in:_ - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) +- [HostAggregateSpec](#hostaggregatespec) - [ImageSpec](#imagespec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) @@ -1294,6 +1425,7 @@ _Validation:_ _Appears in:_ - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) +- [HostAggregateSpec](#hostaggregatespec) - [ImageSpec](#imagespec) - [NetworkSpec](#networkspec) - [PortSpec](#portspec) @@ -1582,6 +1714,8 @@ _Validation:_ _Appears in:_ - [FlavorFilter](#flavorfilter) - [FlavorResourceSpec](#flavorresourcespec) +- [HostAggregateFilter](#hostaggregatefilter) +- [HostAggregateResourceSpec](#hostaggregateresourcespec) - [ImageFilter](#imagefilter) - [ImageResourceSpec](#imageresourcespec) - [NetworkFilter](#networkfilter)