Skip to content

Commit 44beb3c

Browse files
committed
ApplicationCredential Actuator and Status
On-behalf-of: SAP nils.gondermann@sap.com
1 parent e1c7b0d commit 44beb3c

5 files changed

Lines changed: 270 additions & 246 deletions

File tree

internal/controllers/applicationcredential/actuator.go

Lines changed: 144 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package applicationcredential
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"iter"
2223

2324
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials"
@@ -29,7 +30,6 @@ import (
2930
orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
3031
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
3132
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
32-
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
3333
"github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
3434
"github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
3535
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
@@ -41,7 +41,6 @@ type (
4141

4242
createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT]
4343
deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT]
44-
resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT]
4544
helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT]
4645
)
4746

@@ -65,33 +64,54 @@ func (actuator applicationcredentialActuator) GetOSResourceByID(ctx context.Cont
6564
return resource, nil
6665
}
6766

67+
func (actuator applicationcredentialActuator) ResolveUserIDDependency(ctx context.Context, orcObject orcObjectPT) (*string, progress.ReconcileStatus) {
68+
user, rs := userDependency.GetDependency(
69+
ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.User) bool {
70+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
71+
},
72+
)
73+
74+
if user == nil {
75+
return nil, rs
76+
}
77+
78+
return user.Status.ID, rs
79+
}
80+
6881
func (actuator applicationcredentialActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) {
6982
resourceSpec := orcObject.Spec.Resource
7083
if resourceSpec == nil {
7184
return nil, false
7285
}
7386

74-
// TODO(scaffolding) If you need to filter resources on fields that the List() function
75-
// of gophercloud does not support, it's possible to perform client-side filtering.
76-
// Check osclients.ResourceFilter
87+
user, _ := dependency.FetchDependency(
88+
ctx, actuator.k8sClient, orcObject.Namespace,
89+
&resourceSpec.UserRef, "User",
90+
func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil },
91+
)
92+
93+
var filters []osclients.ResourceFilter[osResourceT]
94+
95+
// Add client-side filters
96+
if resourceSpec.Description != nil {
97+
filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool {
98+
return f.Description == *resourceSpec.Description
99+
})
100+
}
77101

78102
listOpts := applicationcredentials.ListOpts{
79-
Name: getResourceName(orcObject),
80-
Description: ptr.Deref(resourceSpec.Description, ""),
103+
Name: getResourceName(orcObject),
81104
}
82105

83-
return actuator.osClient.ListApplicationCredentials(ctx, listOpts), true
106+
return actuator.listOSResources(ctx, ptr.Deref(user.Status.ID, ""), filters, listOpts), true
84107
}
85108

86109
func (actuator applicationcredentialActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
87-
// TODO(scaffolding) If you need to filter resources on fields that the List() function
88-
// of gophercloud does not support, it's possible to perform client-side filtering.
89-
// Check osclients.ResourceFilter
90110
var reconcileStatus progress.ReconcileStatus
91111

92112
user, rs := dependency.FetchDependency(
93113
ctx, actuator.k8sClient, obj.Namespace,
94-
filter.UserRef, "User",
114+
&filter.UserRef, "User",
95115
func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil },
96116
)
97117
reconcileStatus = reconcileStatus.WithReconcileStatus(rs)
@@ -100,14 +120,25 @@ func (actuator applicationcredentialActuator) ListOSResourcesForImport(ctx conte
100120
return nil, reconcileStatus
101121
}
102122

123+
var filters []osclients.ResourceFilter[osResourceT]
124+
125+
// Add client-side filters
126+
if filter.Description != nil {
127+
filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool {
128+
return f.Description == *filter.Description
129+
})
130+
}
131+
103132
listOpts := applicationcredentials.ListOpts{
104-
Name: string(ptr.Deref(filter.Name, "")),
105-
Description: string(ptr.Deref(filter.Description, "")),
106-
UserID: ptr.Deref(user.Status.ID, ""),
107-
// TODO(scaffolding): Add more import filters
133+
Name: string(ptr.Deref(filter.Name, "")),
108134
}
109135

110-
return actuator.osClient.ListApplicationCredentials(ctx, listOpts), reconcileStatus
136+
return actuator.listOSResources(ctx, ptr.Deref(user.Status.ID, ""), filters, listOpts), nil
137+
}
138+
139+
func (actuator applicationcredentialActuator) listOSResources(ctx context.Context, userID string, filters []osclients.ResourceFilter[osResourceT], listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] {
140+
applicationCredentials := actuator.osClient.ListApplicationCredentials(ctx, userID, listOpts)
141+
return osclients.Filter(applicationCredentials, filters...)
111142
}
112143

113144
func (actuator applicationcredentialActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
@@ -118,116 +149,129 @@ func (actuator applicationcredentialActuator) CreateResource(ctx context.Context
118149
return nil, progress.WrapError(
119150
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
120151
}
152+
121153
var reconcileStatus progress.ReconcileStatus
122154

123-
var userID string
124-
user, userDepRS := userDependency.GetDependency(
125-
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool {
126-
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
127-
},
128-
)
129-
reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS)
130-
if user != nil {
131-
userID = ptr.Deref(user.Status.ID, "")
132-
}
133-
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
134-
return nil, reconcileStatus
135-
}
136-
createOpts := applicationcredentials.CreateOpts{
137-
Name: getResourceName(obj),
138-
Description: ptr.Deref(resource.Description, ""),
139-
UserID: userID,
140-
// TODO(scaffolding): Add more fields
141-
}
155+
user, userDepRS := userDependency.GetDependency(
156+
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool {
157+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
158+
},
159+
)
142160

143-
osResource, err := actuator.osClient.CreateApplicationCredential(ctx, createOpts)
144-
if err != nil {
145-
// We should require the spec to be updated before retrying a create which returned a conflict
146-
if !orcerrors.IsRetryable(err) {
147-
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
161+
rolesMap, roleDepRs := roleDependency.GetDependencies(
162+
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool {
163+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
164+
},
165+
)
166+
167+
serviceMap, serviceDepRS := serviceDependency.GetDependencies(
168+
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool {
169+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
170+
},
171+
)
172+
173+
secret, secretReconcileStatus := dependency.FetchDependency(
174+
ctx, actuator.k8sClient, obj.Namespace,
175+
&resource.SecretRef, "Secret",
176+
func(*corev1.Secret) bool { return true }, // Secrets don't have availability status
177+
)
178+
179+
var secretData []byte
180+
if secretReconcileStatus == nil {
181+
var ok bool
182+
secretData, ok = secret.Data["value"]
183+
if !ok {
184+
reconcileStatus = reconcileStatus.WithReconcileStatus(
185+
progress.NewReconcileStatus().WithProgressMessage("Application credential secret does not contain \"value\" key"))
148186
}
149-
return nil, progress.WrapError(err)
150187
}
151188

152-
return osResource, nil
153-
}
154-
155-
func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
156-
return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, resource.ID))
157-
}
189+
reconcileStatus = reconcileStatus.
190+
WithReconcileStatus(userDepRS).
191+
WithReconcileStatus(roleDepRs).
192+
WithReconcileStatus(serviceDepRS).
193+
WithReconcileStatus(secretReconcileStatus)
158194

159-
func (actuator applicationcredentialActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
160-
log := ctrl.LoggerFrom(ctx)
161-
resource := obj.Spec.Resource
162-
if resource == nil {
163-
// Should have been caught by API validation
164-
return progress.WrapError(
165-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
195+
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
196+
return nil, reconcileStatus
166197
}
167198

168-
updateOpts := applicationcredentials.UpdateOpts{}
199+
roleList := make([]applicationcredentials.Role, len(resource.RoleRefs))
200+
for i := range resource.RoleRefs {
201+
roleName := string(resource.RoleRefs[i])
202+
role, ok := rolesMap[roleName]
203+
if !ok {
204+
// Programming error
205+
return nil, progress.WrapError(fmt.Errorf("role %s was not returned by GetDependencies", roleName))
206+
}
207+
roleList[i].ID = *role.Status.ID
208+
}
169209

170-
handleNameUpdate(&updateOpts, obj, osResource)
171-
handleDescriptionUpdate(&updateOpts, resource, osResource)
210+
accessRuleList := make([]applicationcredentials.AccessRule, len(resource.AccessRules))
211+
for i := range resource.AccessRules {
212+
accessRuleSpec := &resource.AccessRules[i]
213+
accessRule := &accessRuleList[i]
214+
215+
if accessRuleSpec.ServiceRef != nil {
216+
serviceName := string(*accessRuleSpec.ServiceRef)
217+
service, ok := serviceMap[serviceName]
218+
if !ok {
219+
// Programming error
220+
return nil, progress.WrapError(fmt.Errorf("service %s was not returned by GetDependencies", serviceName))
221+
}
222+
accessRule.Service = service.Status.Resource.Type
223+
}
172224

173-
// TODO(scaffolding): add handler for all fields supporting mutability
225+
if accessRuleSpec.Path != nil {
226+
accessRule.Path = *accessRuleSpec.Path
227+
}
174228

175-
needsUpdate, err := needsUpdate(updateOpts)
176-
if err != nil {
177-
return progress.WrapError(
178-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
179-
}
180-
if !needsUpdate {
181-
log.V(logging.Debug).Info("No changes")
182-
return nil
229+
if accessRuleSpec.Method != nil {
230+
accessRule.Method = string(*accessRuleSpec.Method)
231+
}
183232
}
184233

185-
_, err = actuator.osClient.UpdateApplicationCredential(ctx, osResource.ID, updateOpts)
234+
createOpts := applicationcredentials.CreateOpts{
235+
Name: getResourceName(obj),
236+
Description: ptr.Deref(resource.Description, ""),
237+
Unrestricted: ptr.Deref(resource.Unrestricted, false),
238+
Secret: string(secretData),
239+
Roles: roleList,
240+
AccessRules: accessRuleList,
241+
}
186242

187-
// We should require the spec to be updated before retrying an update which returned a conflict
188-
if orcerrors.IsConflict(err) {
189-
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)
243+
if resource.ExpiresAt != nil {
244+
createOpts.ExpiresAt = &resource.ExpiresAt.Time
190245
}
191246

247+
osResource, err := actuator.osClient.CreateApplicationCredential(ctx, ptr.Deref(user.Status.ID, ""), createOpts)
192248
if err != nil {
193-
return progress.WrapError(err)
249+
// We should require the spec to be updated before retrying a create which returned a conflict
250+
if !orcerrors.IsRetryable(err) {
251+
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
252+
}
253+
return nil, progress.WrapError(err)
194254
}
195255

196-
return progress.NeedsRefresh()
256+
return osResource, nil
197257
}
198258

199-
func needsUpdate(updateOpts applicationcredentials.UpdateOpts) (bool, error) {
200-
updateOptsMap, err := updateOpts.ToApplicationCredentialUpdateMap()
201-
if err != nil {
202-
return false, err
203-
}
204-
205-
updateMap, ok := updateOptsMap["application_credentials"].(map[string]any)
206-
if !ok {
207-
updateMap = make(map[string]any)
208-
}
259+
func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, orcObject orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
260+
var reconcileStatus progress.ReconcileStatus
209261

210-
return len(updateMap) > 0, nil
211-
}
262+
user, userDepRS := userDependency.GetDependency(
263+
ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.User) bool {
264+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
265+
},
266+
)
212267

213-
func handleNameUpdate(updateOpts *applicationcredentials.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
214-
name := getResourceName(obj)
215-
if osResource.Name != name {
216-
updateOpts.Name = &name
217-
}
218-
}
268+
reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS)
219269

220-
func handleDescriptionUpdate(updateOpts *applicationcredentials.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
221-
description := ptr.Deref(resource.Description, "")
222-
if osResource.Description != description {
223-
updateOpts.Description = &description
270+
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
271+
return reconcileStatus
224272
}
225-
}
226273

227-
func (actuator applicationcredentialActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
228-
return []resourceReconciler{
229-
actuator.updateResource,
230-
}, nil
274+
return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, ptr.Deref(user.Status.ID, ""), resource.ID))
231275
}
232276

233277
type applicationcredentialHelperFactory struct{}

0 commit comments

Comments
 (0)