@@ -24,9 +24,13 @@ import (
24
24
"github.com/go-logr/logr"
25
25
26
26
"github.com/IBM/go-sdk-core/v5/core"
27
+ "github.com/IBM/platform-services-go-sdk/globaltaggingv1"
27
28
"github.com/IBM/platform-services-go-sdk/resourcecontrollerv2"
29
+ "github.com/IBM/platform-services-go-sdk/resourcemanagerv2"
30
+ "github.com/IBM/vpc-go-sdk/vpcv1"
28
31
29
32
"k8s.io/klog/v2/textlogger"
33
+ "k8s.io/utils/ptr"
30
34
31
35
"sigs.k8s.io/controller-runtime/pkg/client"
32
36
@@ -36,6 +40,7 @@ import (
36
40
infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2"
37
41
"sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/authenticator"
38
42
"sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/cos"
43
+ "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/globaltagging"
39
44
"sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcecontroller"
40
45
"sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcemanager"
41
46
"sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/vpc"
@@ -65,6 +70,7 @@ type VPCClusterScope struct {
65
70
patchHelper * patch.Helper
66
71
67
72
COSClient cos.Cos
73
+ GlobalTaggingClient globaltagging.GlobalTagging
68
74
ResourceControllerClient resourcecontroller.ResourceController
69
75
ResourceManagerClient resourcemanager.ResourceManager
70
76
VPCClient vpc.Vpc
@@ -117,27 +123,50 @@ func NewVPCClusterScope(params VPCClusterScopeParams) (*VPCClusterScope, error)
117
123
}
118
124
119
125
// Create Global Tagging client.
120
- // TODO(cjschaef): need service support.
126
+ gtOptions := globaltagging.ServiceOptions {
127
+ GlobalTaggingV1Options : & globaltaggingv1.GlobalTaggingV1Options {
128
+ Authenticator : auth ,
129
+ },
130
+ }
131
+ // Override the global tagging endpoint if provided.
132
+ if gtEndpoint := endpoints .FetchEndpoints (string (endpoints .GlobalTagging ), params .ServiceEndpoint ); gtEndpoint != "" {
133
+ gtOptions .URL = gtEndpoint
134
+ params .Logger .V (3 ).Info ("Overriding the default global tagging endpoint" , "GlobaTaggingEndpoint" , gtEndpoint )
135
+ }
136
+ globalTaggingClient , err := globaltagging .NewService (gtOptions )
137
+ if err != nil {
138
+ return nil , fmt .Errorf ("failed to create global tagging client: %w" , err )
139
+ }
121
140
122
141
// Create Resource Controller client.
123
142
rcOptions := resourcecontroller.ServiceOptions {
124
143
ResourceControllerV2Options : & resourcecontrollerv2.ResourceControllerV2Options {
125
144
Authenticator : auth ,
126
145
},
127
146
}
128
- // Fetch the resource controller endpoint.
129
- rcEndpoint := endpoints .FetchEndpoints (string (endpoints .RC ), params .ServiceEndpoint )
130
- if rcEndpoint != "" {
147
+ // Override the resource controller endpoint if provided.
148
+ if rcEndpoint := endpoints .FetchEndpoints (string (endpoints .RC ), params .ServiceEndpoint ); rcEndpoint != "" {
131
149
rcOptions .URL = rcEndpoint
132
150
params .Logger .V (3 ).Info ("Overriding the default resource controller endpoint" , "ResourceControllerEndpoint" , rcEndpoint )
133
151
}
134
152
resourceControllerClient , err := resourcecontroller .NewService (rcOptions )
135
153
if err != nil {
136
- return nil , fmt .Errorf ("error failed to create resource controller client: %w" , err )
154
+ return nil , fmt .Errorf ("failed to create resource controller client: %w" , err )
137
155
}
138
156
139
157
// Create Resource Manager client.
140
- // TODO(cjschaef): Need to extend ResourceManager service and endpoint support to add properly.
158
+ rmOptions := & resourcemanagerv2.ResourceManagerV2Options {
159
+ Authenticator : auth ,
160
+ }
161
+ // Override the ResourceManager endpoint if provided.
162
+ if rmEndpoint := endpoints .FetchEndpoints (string (endpoints .RM ), params .ServiceEndpoint ); rmEndpoint != "" {
163
+ rmOptions .URL = rmEndpoint
164
+ params .Logger .V (3 ).Info ("Overriding the default resource manager endpoint" , "ResourceManagerEndpoint" , rmEndpoint )
165
+ }
166
+ resourceManagerClient , err := resourcemanager .NewService (rmOptions )
167
+ if err != nil {
168
+ return nil , fmt .Errorf ("failed to create resource manager client: %w" , err )
169
+ }
141
170
142
171
clusterScope := & VPCClusterScope {
143
172
Logger : params .Logger ,
@@ -146,7 +175,9 @@ func NewVPCClusterScope(params VPCClusterScopeParams) (*VPCClusterScope, error)
146
175
Cluster : params .Cluster ,
147
176
IBMVPCCluster : params .IBMVPCCluster ,
148
177
ServiceEndpoint : params .ServiceEndpoint ,
178
+ GlobalTaggingClient : globalTaggingClient ,
149
179
ResourceControllerClient : resourceControllerClient ,
180
+ ResourceManagerClient : resourceManagerClient ,
150
181
VPCClient : vpcClient ,
151
182
}
152
183
return clusterScope , nil
@@ -166,3 +197,281 @@ func (s *VPCClusterScope) Close() error {
166
197
func (s * VPCClusterScope ) Name () string {
167
198
return s .Cluster .Name
168
199
}
200
+
201
+ // NetworkSpec returns the VPCClusterScope's Network spec.
202
+ func (s * VPCClusterScope ) NetworkSpec () * infrav1beta2.VPCNetworkSpec {
203
+ return s .IBMVPCCluster .Spec .Network
204
+ }
205
+
206
+ // NetworkStatus returns the VPCClusterScope's Network status.
207
+ func (s * VPCClusterScope ) NetworkStatus () * infrav1beta2.VPCNetworkStatus {
208
+ return s .IBMVPCCluster .Status .Network
209
+ }
210
+
211
+ // CheckTagExists checks whether a user tag already exists.
212
+ func (s * VPCClusterScope ) CheckTagExists (tagName string ) (bool , error ) {
213
+ exists , err := s .GlobalTaggingClient .GetTagByName (tagName )
214
+ if err != nil {
215
+ return false , fmt .Errorf ("failed checking for tag: %w" , err )
216
+ }
217
+ return exists != nil , nil
218
+ }
219
+
220
+ // GetNetworkResourceGroupID returns the Resource Group ID for the Network Resources if it is present. Otherwise, it defaults to the cluster's Resource Group ID.
221
+ func (s * VPCClusterScope ) GetNetworkResourceGroupID () (string , error ) {
222
+ // Check if the ID is available from Status first.
223
+ if s .NetworkStatus () != nil && s .NetworkStatus ().ResourceGroup != nil && s .NetworkStatus ().ResourceGroup .ID != "" {
224
+ return s .NetworkStatus ().ResourceGroup .ID , nil
225
+ }
226
+
227
+ // If there is no Network Resource Group defined, use the cluster's Resource Group.
228
+ if s .NetworkSpec () == nil || s .NetworkSpec ().ResourceGroup == nil {
229
+ return s .GetResourceGroupID ()
230
+ }
231
+
232
+ // Otherwise, Collect the Network's Resource Group Id.
233
+ // Retrieve the Resource Group based on the name.
234
+ resourceGroup , err := s .ResourceManagerClient .GetResourceGroupByName (* s .NetworkSpec ().ResourceGroup )
235
+ if err != nil {
236
+ return "" , fmt .Errorf ("failed to retrieve network resource group id by name: %w" , err )
237
+ } else if resourceGroup == nil || resourceGroup .ID == nil {
238
+ return "" , fmt .Errorf ("error retrieving network resource group by name: %s" , * s .NetworkSpec ().ResourceGroup )
239
+ }
240
+
241
+ // Populate the Network Status' Resource Group to shortcut future lookups.
242
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeResourceGroup , & infrav1beta2.ResourceStatus {
243
+ ID : * resourceGroup .ID ,
244
+ Name : s .NetworkSpec ().ResourceGroup ,
245
+ Ready : true ,
246
+ })
247
+
248
+ return * resourceGroup .ID , nil
249
+ }
250
+
251
+ // GetResourceGroupID returns the Resource Group ID for the cluster.
252
+ func (s * VPCClusterScope ) GetResourceGroupID () (string , error ) {
253
+ // Check if the Resource Group ID is available from Status first.
254
+ if s .IBMVPCCluster .Status .ResourceGroup != nil && s .IBMVPCCluster .Status .ResourceGroup .ID != "" {
255
+ return s .IBMVPCCluster .Status .ResourceGroup .ID , nil
256
+ }
257
+
258
+ // If the Resource Group is not defined in Spec, we generate the name based on the cluster name.
259
+ resourceGroupName := s .IBMVPCCluster .Spec .ResourceGroup
260
+ if resourceGroupName == "" {
261
+ resourceGroupName = s .IBMVPCCluster .Name
262
+ }
263
+
264
+ // Retrieve the Resource Group based on the name.
265
+ resourceGroup , err := s .ResourceManagerClient .GetResourceGroupByName (resourceGroupName )
266
+ if err != nil {
267
+ return "" , fmt .Errorf ("failed to retrieve resource group by name: %w" , err )
268
+ } else if resourceGroup == nil || resourceGroup .ID == nil {
269
+ return "" , fmt .Errorf ("failed to find resource group by name: %s" , resourceGroupName )
270
+ }
271
+
272
+ // Populate the Stauts Resource Group to shortcut future lookups.
273
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeResourceGroup , & infrav1beta2.ResourceStatus {
274
+ ID : * resourceGroup .ID ,
275
+ Name : ptr .To (resourceGroupName ),
276
+ Ready : true ,
277
+ })
278
+
279
+ return * resourceGroup .ID , nil
280
+ }
281
+
282
+ // GetServiceName returns the name of a given service type from Spec or generates a name for it.
283
+ func (s * VPCClusterScope ) GetServiceName (resourceType infrav1beta2.ResourceType ) * string {
284
+ switch resourceType {
285
+ case infrav1beta2 .ResourceTypeVPC :
286
+ // Generate a name based off cluster name if no VPC defined in Spec, or no VPC name nor ID.
287
+ if s .NetworkSpec ().VPC == nil || (s .NetworkSpec ().VPC .Name == nil && s .NetworkSpec ().VPC .ID == nil ) {
288
+ return ptr .To (fmt .Sprintf ("%s-vpc" , s .Name ()))
289
+ }
290
+ if s .NetworkSpec ().VPC .Name != nil {
291
+ return s .NetworkSpec ().VPC .Name
292
+ }
293
+ default :
294
+ s .V (3 ).Info ("unsupported resource type" , "resourceType" , resourceType )
295
+ }
296
+ return nil
297
+ }
298
+
299
+ // GetVPCID returns the VPC id, if available.
300
+ func (s * VPCClusterScope ) GetVPCID () (* string , error ) {
301
+ // Check if the VPC ID is available from Status first.
302
+ if s .NetworkStatus () != nil && s .NetworkStatus ().VPC != nil {
303
+ return ptr .To (s .NetworkStatus ().VPC .ID ), nil
304
+ }
305
+
306
+ if s .NetworkSpec () != nil && s .NetworkSpec ().VPC != nil {
307
+ if s .NetworkSpec ().VPC .ID != nil {
308
+ return s .NetworkSpec ().VPC .ID , nil
309
+ } else if s .NetworkSpec ().VPC .Name != nil {
310
+ vpcDetails , err := s .VPCClient .GetVPCByName (* s .NetworkSpec ().VPC .Name )
311
+ if err != nil {
312
+ return nil , fmt .Errorf ("failed vpc id lookup: %w" , err )
313
+ }
314
+
315
+ // Check if the VPC was found and has an ID
316
+ if vpcDetails != nil && vpcDetails .ID != nil {
317
+ // Set VPC ID in Status to shortcut future lookups
318
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeVPC , & infrav1beta2.ResourceStatus {
319
+ ID : * vpcDetails .ID ,
320
+ Name : s .NetworkSpec ().VPC .Name ,
321
+ Ready : true ,
322
+ })
323
+ }
324
+ }
325
+ }
326
+ return nil , nil
327
+ }
328
+
329
+ // SetResourceStatus sets the status for the provided ResourceType.
330
+ func (s * VPCClusterScope ) SetResourceStatus (resourceType infrav1beta2.ResourceType , resource * infrav1beta2.ResourceStatus ) {
331
+ // Ignore attempts to set status without resource.
332
+ if resource == nil {
333
+ return
334
+ }
335
+ s .V (3 ).Info ("Setting status" , "resourceType" , resourceType , "resource" , resource )
336
+ switch resourceType {
337
+ case infrav1beta2 .ResourceTypeResourceGroup :
338
+ if s .IBMVPCCluster .Status .ResourceGroup == nil {
339
+ s .IBMVPCCluster .Status .ResourceGroup = resource
340
+ return
341
+ }
342
+ s .IBMVPCCluster .Status .ResourceGroup .Set (* resource )
343
+ case infrav1beta2 .ResourceTypeVPC :
344
+ if s .NetworkStatus () == nil {
345
+ s .IBMVPCCluster .Status .Network = & infrav1beta2.VPCNetworkStatus {
346
+ VPC : resource ,
347
+ }
348
+ return
349
+ } else if s .NetworkStatus ().VPC == nil {
350
+ s .IBMVPCCluster .Status .Network .VPC = resource
351
+ }
352
+ s .NetworkStatus ().VPC .Set (* resource )
353
+ default :
354
+ s .V (3 ).Info ("unsupported resource type" , "resourceType" , resourceType )
355
+ }
356
+ }
357
+
358
+ // TagResource will attach a user Tag to a resource.
359
+ func (s * VPCClusterScope ) TagResource (tagName string , resourceCRN string ) error {
360
+ // Verify the Tag we wish to use exists, otherwise create it.
361
+ exists , err := s .CheckTagExists (tagName )
362
+ if err != nil {
363
+ return fmt .Errorf ("failure checking if tag exists: %w" , err )
364
+ }
365
+
366
+ // Create tag if it doesn't exist.
367
+ if ! exists {
368
+ createOptions := & globaltaggingv1.CreateTagOptions {}
369
+ createOptions .SetTagNames ([]string {tagName })
370
+ if _ , _ , err := s .GlobalTaggingClient .CreateTag (createOptions ); err != nil {
371
+ return fmt .Errorf ("failure creating tag: %w" , err )
372
+ }
373
+ }
374
+
375
+ // Finally, tag resource.
376
+ tagOptions := & globaltaggingv1.AttachTagOptions {}
377
+ tagOptions .SetResources ([]globaltaggingv1.Resource {
378
+ {
379
+ ResourceID : ptr .To (resourceCRN ),
380
+ },
381
+ })
382
+ tagOptions .SetTagName (tagName )
383
+ tagOptions .SetTagType (globaltaggingv1 .AttachTagOptionsTagTypeUserConst )
384
+
385
+ if _ , _ , err = s .GlobalTaggingClient .AttachTag (tagOptions ); err != nil {
386
+ return fmt .Errorf ("failure tagging resource: %w" , err )
387
+ }
388
+
389
+ return nil
390
+ }
391
+
392
+ // ReconcileVPC reconciles the cluster's VPC.
393
+ func (s * VPCClusterScope ) ReconcileVPC () (bool , error ) {
394
+ // If VPC id is set, that indicates the VPC already exists.
395
+ vpcID , err := s .GetVPCID ()
396
+ if err != nil {
397
+ return false , fmt .Errorf ("failed to retrieve vpc id: %w" , err )
398
+ }
399
+ if vpcID != nil {
400
+ s .V (3 ).Info ("VPC id is set" , "id" , vpcID )
401
+ vpcDetails , _ , err := s .VPCClient .GetVPC (& vpcv1.GetVPCOptions {
402
+ ID : vpcID ,
403
+ })
404
+ if err != nil {
405
+ return false , fmt .Errorf ("failed to retrieve vpc by id: %w" , err )
406
+ } else if vpcDetails == nil {
407
+ return false , fmt .Errorf ("failed to retrieve vpc with id: %s" , * vpcID )
408
+ }
409
+ s .V (3 ).Info ("Found VPC with provided id" , "id" , vpcID )
410
+
411
+ requeue := true
412
+ if vpcDetails .Status != nil && * vpcDetails .Status == string (vpcv1 .VPCStatusAvailableConst ) {
413
+ requeue = false
414
+ }
415
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeVPC , & infrav1beta2.ResourceStatus {
416
+ ID : * vpcID ,
417
+ Name : vpcDetails .Name ,
418
+ // Ready status will be invert of the need to requeue.
419
+ Ready : ! requeue ,
420
+ })
421
+
422
+ // After updating the Status of VPC, return with requeue or return as reconcile complete.
423
+ return requeue , nil
424
+ }
425
+
426
+ // If no VPC id was found, we need to create a new VPC.
427
+ s .V (3 ).Info ("Creating a VPC" )
428
+ vpcDetails , err := s .createVPC ()
429
+ if err != nil {
430
+ return false , fmt .Errorf ("failed to create vpc: %w" , err )
431
+ }
432
+
433
+ s .V (3 ).Info ("Successfully created VPC" )
434
+ var vpcName * string
435
+ if vpcDetails != nil {
436
+ vpcName = vpcDetails .Name
437
+ }
438
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeVPC , & infrav1beta2.ResourceStatus {
439
+ ID : * vpcDetails .ID ,
440
+ Name : vpcName ,
441
+ Ready : false ,
442
+ })
443
+ return true , nil
444
+ }
445
+
446
+ func (s * VPCClusterScope ) createVPC () (* vpcv1.VPC , error ) {
447
+ // We use the cluster's Resource Group ID, as we expect to create all resources in that Resource Group.
448
+ resourceGroupID , err := s .GetResourceGroupID ()
449
+ if err != nil {
450
+ return nil , fmt .Errorf ("failed retreiving resource group id during vpc creation: %w" , err )
451
+ } else if resourceGroupID == "" {
452
+ return nil , fmt .Errorf ("resource group id is empty cannot create vpc" )
453
+ }
454
+ vpcName := s .GetServiceName (infrav1beta2 .ResourceTypeVPC )
455
+ if s .NetworkSpec () != nil && s .NetworkSpec ().VPC != nil && s .NetworkSpec ().VPC .Name != nil {
456
+ vpcName = s .NetworkSpec ().VPC .Name
457
+ }
458
+
459
+ // TODO(cjschaef): Look at adding support to specify prefix management
460
+ addressPrefixManagement := "auto"
461
+ vpcOptions := & vpcv1.CreateVPCOptions {
462
+ AddressPrefixManagement : & addressPrefixManagement ,
463
+ Name : vpcName ,
464
+ ResourceGroup : & vpcv1.ResourceGroupIdentity {ID : & resourceGroupID },
465
+ }
466
+ vpcDetails , _ , err := s .VPCClient .CreateVPC (vpcOptions )
467
+ if err != nil {
468
+ return nil , fmt .Errorf ("error creating vpc: %w" , err )
469
+ } else if vpcDetails == nil {
470
+ return nil , fmt .Errorf ("no vpc details after creation" )
471
+ }
472
+ if err = s .TagResource (s .IBMVPCCluster .Name , * vpcDetails .CRN ); err != nil {
473
+ return nil , fmt .Errorf ("error tagging vpc: %w" , err )
474
+ }
475
+
476
+ return vpcDetails , nil
477
+ }
0 commit comments