Skip to content

Commit ea3ea8f

Browse files
committed
ApplicationCredential Actuator and Status
On-behalf-of: SAP nils.gondermann@sap.com
1 parent 75bdc69 commit ea3ea8f

4 files changed

Lines changed: 124 additions & 229 deletions

File tree

internal/controllers/applicationcredential/actuator.go

Lines changed: 68 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
3030
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
3131
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
32-
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
3332
"github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
3433
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
3534
)
@@ -70,30 +69,42 @@ func (actuator applicationcredentialActuator) ListOSResourcesForAdoption(ctx con
7069
return nil, false
7170
}
7271

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
72+
var filters []osclients.ResourceFilter[osResourceT]
73+
74+
// Add client-side filters
75+
if resourceSpec.Description != nil {
76+
filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool {
77+
return f.Description == *resourceSpec.Description
78+
})
79+
}
7680

7781
listOpts := applicationcredentials.ListOpts{
78-
Name: getResourceName(orcObject),
79-
Description: ptr.Deref(resourceSpec.Description, ""),
82+
Name: getResourceName(orcObject),
8083
}
8184

82-
return actuator.osClient.ListApplicationCredentials(ctx, listOpts), true
85+
return actuator.listOSResources(ctx, resourceSpec.UserID, filters, listOpts), true
8386
}
8487

8588
func (actuator applicationcredentialActuator) 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+
var filters []osclients.ResourceFilter[osResourceT]
90+
91+
// Add client-side filters
92+
if filter.Description != nil {
93+
filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool {
94+
return f.Description == *filter.Description
95+
})
96+
}
8997

9098
listOpts := applicationcredentials.ListOpts{
91-
Name: string(ptr.Deref(filter.Name, "")),
92-
Description: string(ptr.Deref(filter.Description, "")),
93-
// TODO(scaffolding): Add more import filters
99+
Name: string(ptr.Deref(filter.Name, "")),
94100
}
95101

96-
return actuator.osClient.ListApplicationCredentials(ctx, listOpts), nil
102+
return actuator.listOSResources(ctx, filter.UserID, filters, listOpts), nil
103+
}
104+
105+
func (actuator applicationcredentialActuator) listOSResources(ctx context.Context, userID string, filters []osclients.ResourceFilter[osResourceT], listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] {
106+
applicationCredentials := actuator.osClient.ListApplicationCredentials(ctx, userID, listOpts)
107+
return osclients.Filter(applicationCredentials, filters...)
97108
}
98109

99110
func (actuator applicationcredentialActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) {
@@ -104,100 +115,71 @@ func (actuator applicationcredentialActuator) CreateResource(ctx context.Context
104115
return nil, progress.WrapError(
105116
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
106117
}
107-
createOpts := applicationcredentials.CreateOpts{
108-
Name: getResourceName(obj),
109-
Description: ptr.Deref(resource.Description, ""),
110-
// TODO(scaffolding): Add more fields
111-
}
112-
113-
osResource, err := actuator.osClient.CreateApplicationCredential(ctx, createOpts)
114-
if err != nil {
115-
// We should require the spec to be updated before retrying a create which returned a conflict
116-
if !orcerrors.IsRetryable(err) {
117-
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
118-
}
119-
return nil, progress.WrapError(err)
120-
}
121118

122-
return osResource, nil
123-
}
119+
roleList := make([]applicationcredentials.Role, len(resource.Roles))
120+
for i := range resource.Roles {
121+
roleSpec := &resource.Roles[i]
122+
role := &roleList[i]
124123

125-
func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
126-
return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, resource.ID))
127-
}
124+
if roleSpec.ID != nil {
125+
role.ID = *roleSpec.ID
126+
}
128127

129-
func (actuator applicationcredentialActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
130-
log := ctrl.LoggerFrom(ctx)
131-
resource := obj.Spec.Resource
132-
if resource == nil {
133-
// Should have been caught by API validation
134-
return progress.WrapError(
135-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
128+
if roleSpec.Name != nil {
129+
role.Name = string(*roleSpec.Name)
130+
}
136131
}
137132

138-
updateOpts := applicationcredentials.UpdateOpts{}
133+
accessRuleList := make([]applicationcredentials.AccessRule, len(resource.AccessRules))
134+
for i := range resource.AccessRules {
135+
accessRuleSpec := &resource.AccessRules[i]
136+
accessRule := &accessRuleList[i]
139137

140-
handleNameUpdate(&updateOpts, obj, osResource)
141-
handleDescriptionUpdate(&updateOpts, resource, osResource)
138+
if accessRuleSpec.Path != nil {
139+
accessRule.Path = *accessRuleSpec.Path
140+
}
142141

143-
// TODO(scaffolding): add handler for all fields supporting mutability
142+
if accessRuleSpec.Service != nil {
143+
accessRule.Service = *accessRuleSpec.Service
144+
}
144145

145-
needsUpdate, err := needsUpdate(updateOpts)
146-
if err != nil {
147-
return progress.WrapError(
148-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err))
149-
}
150-
if !needsUpdate {
151-
log.V(logging.Debug).Info("No changes")
152-
return nil
146+
if accessRuleSpec.Method != nil {
147+
accessRule.Method = string(*accessRuleSpec.Method)
148+
}
153149
}
154150

155-
_, err = actuator.osClient.UpdateApplicationCredential(ctx, osResource.ID, updateOpts)
156-
157-
// We should require the spec to be updated before retrying an update which returned a conflict
158-
if orcerrors.IsConflict(err) {
159-
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)
151+
createOpts := applicationcredentials.CreateOpts{
152+
Name: getResourceName(obj),
153+
Description: ptr.Deref(resource.Description, ""),
154+
Unrestricted: ptr.Deref(resource.Unrestricted, false),
155+
Secret: ptr.Deref(resource.Secret, ""),
156+
Roles: roleList,
157+
AccessRules: accessRuleList,
160158
}
161159

162-
if err != nil {
163-
return progress.WrapError(err)
160+
if resource.ExpiresAt != nil {
161+
createOpts.ExpiresAt = &resource.ExpiresAt.Time
164162
}
165163

166-
return progress.NeedsRefresh()
167-
}
168-
169-
func needsUpdate(updateOpts applicationcredentials.UpdateOpts) (bool, error) {
170-
updateOptsMap, err := updateOpts.ToApplicationCredentialUpdateMap()
164+
osResource, err := actuator.osClient.CreateApplicationCredential(ctx, resource.UserID, createOpts)
171165
if err != nil {
172-
return false, err
173-
}
174-
175-
updateMap, ok := updateOptsMap["application_credentials"].(map[string]any)
176-
if !ok {
177-
updateMap = make(map[string]any)
166+
// We should require the spec to be updated before retrying a create which returned a conflict
167+
if !orcerrors.IsRetryable(err) {
168+
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err)
169+
}
170+
return nil, progress.WrapError(err)
178171
}
179172

180-
return len(updateMap) > 0, nil
181-
}
182-
183-
func handleNameUpdate(updateOpts *applicationcredentials.UpdateOpts, obj orcObjectPT, osResource *osResourceT) {
184-
name := getResourceName(obj)
185-
if osResource.Name != name {
186-
updateOpts.Name = &name
187-
}
173+
return osResource, nil
188174
}
189175

190-
func handleDescriptionUpdate(updateOpts *applicationcredentials.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) {
191-
description := ptr.Deref(resource.Description, "")
192-
if osResource.Description != description {
193-
updateOpts.Description = &description
194-
}
176+
func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, orcObject orcObjectPT, resource *osResourceT) progress.ReconcileStatus {
177+
return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, orcObject.Spec.Resource.UserID, resource.ID))
195178
}
196179

197180
func (actuator applicationcredentialActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
198-
return []resourceReconciler{
199-
actuator.updateResource,
200-
}, nil
181+
// ApplicationCredentials are immutable - no update reconcilers needed
182+
return []resourceReconciler{}, nil
201183
}
202184

203185
type applicationcredentialHelperFactory struct{}

internal/controllers/applicationcredential/actuator_test.go

Lines changed: 0 additions & 119 deletions
This file was deleted.

internal/controllers/applicationcredential/status.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,55 @@ func (applicationcredentialStatusWriter) ResourceAvailableStatus(orcObject *orcv
5050

5151
func (applicationcredentialStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) {
5252
resourceStatus := orcapplyconfigv1alpha1.ApplicationCredentialResourceStatus().
53-
WithName(osResource.Name)
53+
WithName(osResource.Name).
54+
WithUnrestricted(osResource.Unrestricted).
55+
WithProjectID(osResource.ProjectID)
5456

55-
// TODO(scaffolding): add all of the fields supported in the ApplicationCredentialResourceStatus struct
56-
// If a zero-value isn't expected in the response, place it behind a conditional
57+
if osResource.Secret != "" {
58+
resourceStatus.WithSecret(osResource.Secret)
59+
}
60+
61+
if !osResource.ExpiresAt.IsZero() {
62+
resourceStatus.WithExpiresAt(metav1.NewTime(osResource.ExpiresAt))
63+
}
64+
65+
if osResource.ID != "" {
66+
resourceStatus.WithID(osResource.ID)
67+
}
5768

5869
if osResource.Description != "" {
5970
resourceStatus.WithDescription(osResource.Description)
6071
}
6172

73+
for i := range osResource.Roles {
74+
roleStatus := orcapplyconfigv1alpha1.ApplicationCredentialAccessRoleStatus().
75+
WithID(osResource.Roles[i].ID).
76+
WithName(osResource.Roles[i].Name)
77+
78+
if osResource.Roles[i].DomainID != "" {
79+
roleStatus.WithDomainID(osResource.Roles[i].DomainID)
80+
}
81+
82+
resourceStatus.WithRoles(roleStatus)
83+
}
84+
85+
for i := range osResource.AccessRules {
86+
accessRuleStatus := orcapplyconfigv1alpha1.ApplicationCredentialAccessRuleStatus().
87+
WithID(osResource.AccessRules[i].ID).
88+
WithPath(osResource.AccessRules[i].Path).
89+
WithMethod(osResource.AccessRules[i].Method).
90+
WithService(osResource.AccessRules[i].Service)
91+
92+
resourceStatus.WithAccessRules(accessRuleStatus)
93+
}
94+
95+
links := make(map[string]string, len(osResource.Links))
96+
97+
for k, v := range osResource.Links {
98+
links[k] = v.(string)
99+
}
100+
101+
resourceStatus.WithLinks(links)
102+
62103
statusApply.WithResource(resourceStatus)
63104
}

0 commit comments

Comments
 (0)