@@ -18,6 +18,7 @@ package applicationcredential
1818
1919import (
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"
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+
6881func (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
86109func (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
113144func (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
233277type applicationcredentialHelperFactory struct {}
0 commit comments