Skip to content

Commit feb33e0

Browse files
committed
Foundational Layer: Scaffolder created skeleton for new Share Type Controller
go run ./cmd/scaffold-controller -interactive=false \ -kind=ShareType \ -gophercloud-client=NewSharedFilesystemV2 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes \ -gophercloud-type=ShareType \ -openstack-json-object=share_type Signed-off-by: Daniel Lawton <dlawton@redhat.com>
1 parent 46a7c89 commit feb33e0

40 files changed

Lines changed: 1305 additions & 0 deletions

api/v1alpha1/sharetype_types.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright The ORC Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
// ShareTypeResourceSpec contains the desired state of the resource.
20+
type ShareTypeResourceSpec struct {
21+
// name will be the name of the created resource. If not specified, the
22+
// name of the ORC object will be used.
23+
// +optional
24+
Name *OpenStackName `json:"name,omitempty"`
25+
26+
// description is a human-readable description for the resource.
27+
// +kubebuilder:validation:MinLength:=1
28+
// +kubebuilder:validation:MaxLength:=255
29+
// +optional
30+
Description *string `json:"description,omitempty"`
31+
32+
// TODO(scaffolding): Add more types.
33+
// To see what is supported, you can take inspiration from the CreateOpts structure from
34+
// github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes
35+
//
36+
// Until you have implemented mutability for the field, you must add a CEL validation
37+
// preventing the field being modified:
38+
// `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="<fieldname> is immutable"`
39+
}
40+
41+
// ShareTypeFilter defines an existing resource by its properties
42+
// +kubebuilder:validation:MinProperties:=1
43+
type ShareTypeFilter struct {
44+
// name of the existing resource
45+
// +optional
46+
Name *OpenStackName `json:"name,omitempty"`
47+
48+
// description of the existing resource
49+
// +kubebuilder:validation:MinLength:=1
50+
// +kubebuilder:validation:MaxLength:=255
51+
// +optional
52+
Description *string `json:"description,omitempty"`
53+
54+
// TODO(scaffolding): Add more types.
55+
// To see what is supported, you can take inspiration from the ListOpts structure from
56+
// github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes
57+
}
58+
59+
// ShareTypeResourceStatus represents the observed state of the resource.
60+
type ShareTypeResourceStatus struct {
61+
// name is a Human-readable name for the resource. Might not be unique.
62+
// +kubebuilder:validation:MaxLength=1024
63+
// +optional
64+
Name string `json:"name,omitempty"`
65+
66+
// description is a human-readable description for the resource.
67+
// +kubebuilder:validation:MaxLength=1024
68+
// +optional
69+
Description string `json:"description,omitempty"`
70+
71+
// TODO(scaffolding): Add more types.
72+
// To see what is supported, you can take inspiration from the ShareType structure from
73+
// github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes
74+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
apiVersion: openstack.k-orc.cloud/v1alpha1
3+
kind: ShareType
4+
metadata:
5+
name: sharetype-sample
6+
spec:
7+
cloudCredentialsRef:
8+
# TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created
9+
cloudName: openstack
10+
secretName: openstack-clouds
11+
managementPolicy: managed
12+
resource:
13+
description: Sample ShareType
14+
# TODO(scaffolding): Add all fields the resource supports
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
Copyright The ORC Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package sharetype
18+
19+
import (
20+
"context"
21+
"iter"
22+
23+
"github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes"
24+
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/utils/ptr"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
30+
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
31+
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
32+
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
33+
"github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
34+
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
35+
)
36+
37+
// OpenStack resource types
38+
type (
39+
osResourceT = sharetypes.ShareType
40+
41+
createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT]
42+
deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT]
43+
resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT]
44+
helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT]
45+
)
46+
47+
type sharetypeActuator struct {
48+
osClient osclients.ShareTypeClient
49+
k8sClient client.Client
50+
}
51+
52+
var _ createResourceActuator = sharetypeActuator{}
53+
var _ deleteResourceActuator = sharetypeActuator{}
54+
55+
func (sharetypeActuator) GetResourceID(osResource *osResourceT) string {
56+
return osResource.ID
57+
}
58+
59+
func (actuator sharetypeActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) {
60+
resource, err := actuator.osClient.GetShareType(ctx, id)
61+
if err != nil {
62+
return nil, progress.WrapError(err)
63+
}
64+
return resource, nil
65+
}
66+
67+
func (actuator sharetypeActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) {
68+
resourceSpec := orcObject.Spec.Resource
69+
if resourceSpec == nil {
70+
return nil, false
71+
}
72+
73+
// TODO(scaffolding) If you need to filter resources on fields that the List() function
74+
// of gophercloud does not support, it's possible to perform client-side filtering.
75+
// Check osclients.ResourceFilter
76+
77+
listOpts := sharetypes.ListOpts{
78+
Name: getResourceName(orcObject),
79+
Description: ptr.Deref(resourceSpec.Description, ""),
80+
}
81+
82+
return actuator.osClient.ListShareTypes(ctx, listOpts), true
83+
}
84+
85+
func (actuator sharetypeActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
86+
// TODO(scaffolding) If you need to filter resources on fields that the List() function
87+
// of gophercloud does not support, it's possible to perform client-side filtering.
88+
// Check osclients.ResourceFilter
89+
90+
listOpts := sharetypes.ListOpts{
91+
Name: string(ptr.Deref(filter.Name, "")),
92+
Description: string(ptr.Deref(filter.Description, "")),
93+
// TODO(scaffolding): Add more import filters
94+
}
95+
96+
return actuator.osClient.ListShareTypes(ctx, listOpts), nil
97+
}
98+
99+
func (actuator sharetypeActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
100+
resource := obj.Spec.Resource
101+
102+
if resource == nil {
103+
// Should have been caught by API validation
104+
return nil, progress.WrapError(
105+
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
106+
}
107+
createOpts := sharetypes.CreateOpts{
108+
Name: getResourceName(obj),
109+
Description: ptr.Deref(resource.Description, ""),
110+
// TODO(scaffolding): Add more fields
111+
}
112+
113+
osResource, err := actuator.osClient.CreateShareType(ctx, createOpts)
114+
if err != nil {
115+
if !orcerrors.IsRetryable(err) {
116+
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
117+
}
118+
return nil, progress.WrapError(err)
119+
}
120+
121+
return osResource, nil
122+
}
123+
124+
func (actuator sharetypeActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
125+
return progress.WrapError(actuator.osClient.DeleteShareType(ctx, resource.ID))
126+
}
127+
128+
func (actuator sharetypeActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
129+
log := ctrl.LoggerFrom(ctx)
130+
resource := obj.Spec.Resource
131+
if resource == nil {
132+
// Should have been caught by API validation
133+
return progress.WrapError(
134+
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
135+
}
136+
137+
updateOpts := sharetypes.UpdateOpts{}
138+
139+
handleNameUpdate(&updateOpts, obj, osResource)
140+
handleDescriptionUpdate(&updateOpts, resource, osResource)
141+
142+
// TODO(scaffolding): add handler for all fields supporting mutability
143+
144+
needsUpdate, err := needsUpdate(updateOpts)
145+
if err != nil {
146+
return progress.WrapError(
147+
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
148+
}
149+
if !needsUpdate {
150+
log.V(logging.Debug).Info("No changes")
151+
return nil
152+
}
153+
154+
_, err = actuator.osClient.UpdateShareType(ctx, osResource.ID, updateOpts)
155+
156+
if err != nil {
157+
if !orcerrors.IsRetryable(err) {
158+
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)
159+
}
160+
return progress.WrapError(err)
161+
}
162+
163+
return progress.NeedsRefresh()
164+
}
165+
166+
func needsUpdate(updateOpts sharetypes.UpdateOpts) (bool, error) {
167+
updateOptsMap, err := updateOpts.ToShareTypeUpdateMap()
168+
if err != nil {
169+
return false, err
170+
}
171+
172+
updateMap, ok := updateOptsMap["share_type"].(map[string]any)
173+
if !ok {
174+
updateMap = make(map[string]any)
175+
}
176+
177+
return len(updateMap) > 0, nil
178+
}
179+
180+
func handleNameUpdate(updateOpts *sharetypes.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
181+
name := getResourceName(obj)
182+
if osResource.Name != name {
183+
updateOpts.Name = &name
184+
}
185+
}
186+
187+
func handleDescriptionUpdate(updateOpts *sharetypes.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
188+
description := ptr.Deref(resource.Description, "")
189+
if osResource.Description != description {
190+
updateOpts.Description = &description
191+
}
192+
}
193+
194+
func (actuator sharetypeActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
195+
return []resourceReconciler{
196+
actuator.updateResource,
197+
}, nil
198+
}
199+
200+
type sharetypeHelperFactory struct{}
201+
202+
var _ helperFactory = sharetypeHelperFactory{}
203+
204+
func newActuator(ctx context.Context, orcObject *orcv1alpha1.ShareType, controller interfaces.ResourceController) (sharetypeActuator, progress.ReconcileStatus) {
205+
log := ctrl.LoggerFrom(ctx)
206+
207+
// Ensure credential secrets exist and have our finalizer
208+
_, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true })
209+
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
210+
return sharetypeActuator{}, reconcileStatus
211+
}
212+
213+
clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject)
214+
if err != nil {
215+
return sharetypeActuator{}, progress.WrapError(err)
216+
}
217+
osClient, err := clientScope.NewShareTypeClient()
218+
if err != nil {
219+
return sharetypeActuator{}, progress.WrapError(err)
220+
}
221+
222+
return sharetypeActuator{
223+
osClient: osClient,
224+
k8sClient: controller.GetK8sClient(),
225+
}, nil
226+
}
227+
228+
func (sharetypeHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI {
229+
return sharetypeAdapter{obj}
230+
}
231+
232+
func (sharetypeHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) {
233+
return newActuator(ctx, orcObject, controller)
234+
}
235+
236+
func (sharetypeHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) {
237+
return newActuator(ctx, orcObject, controller)
238+
}

0 commit comments

Comments
 (0)