@@ -24,6 +24,7 @@ import (
24
24
"github.com/Azure/go-autorest/autorest"
25
25
"github.com/Azure/go-autorest/autorest/adal"
26
26
"github.com/Azure/go-autorest/autorest/azure"
27
+ "github.com/Azure/go-autorest/autorest/to"
27
28
configv1 "github.com/openshift/api/config/v1"
28
29
machinev1 "github.com/openshift/api/machine/v1beta1"
29
30
apierrors "github.com/openshift/machine-api-operator/pkg/controller/machine"
@@ -87,6 +88,18 @@ func NewMachineScope(params MachineScopeParams) (*MachineScope, error) {
87
88
return nil , fmt .Errorf ("failed to get cluster infrastructure: %w" , err )
88
89
}
89
90
91
+ infraTags := getInfraResourceTags (infra .Status .PlatformStatus )
92
+
93
+ ocpTags , err := getOCPTagList (infra .Status .InfrastructureName )
94
+ if err != nil {
95
+ return nil , fmt .Errorf ("failed to get OCP tag list: %w" , err )
96
+ }
97
+
98
+ tags , err := getTagList (ocpTags , infraTags , machineConfig .Tags )
99
+ if err != nil {
100
+ return nil , fmt .Errorf ("failed to get combined tag list: %w" , err )
101
+ }
102
+
90
103
cloudEnv , armEndpoint := GetCloudEnvironment (infra )
91
104
92
105
machineScope := & MachineScope {
@@ -107,6 +120,7 @@ func NewMachineScope(params MachineScopeParams) (*MachineScope, error) {
107
120
machineToBePatched : controllerclient .MergeFrom (params .Machine .DeepCopy ()),
108
121
cloudEnv : cloudEnv ,
109
122
armEndpoint : armEndpoint ,
123
+ Tags : tags ,
110
124
}
111
125
112
126
if err = updateFromSecret (params .CoreClient , machineScope ); err != nil {
@@ -141,6 +155,10 @@ type MachineScope struct {
141
155
origMachineStatus * machinev1.AzureMachineProviderStatus
142
156
143
157
machineToBePatched controllerclient.Patch
158
+
159
+ // Tags is a list of tags to apply to the resources created
160
+ // for the cluster
161
+ Tags map [string ]* string
144
162
}
145
163
146
164
// Name returns the machine name.
@@ -400,3 +418,99 @@ func getEnvironment(m *MachineScope) (*azure.Environment, error) {
400
418
401
419
return & env , nil
402
420
}
421
+
422
+ // getTagList is for getting the merged tag list present in
423
+ // AzureMachineProviderSpec and Infrastructure.Status. Both
424
+ // will have the same tags, unless AzureMachineProviderSpec.Tags
425
+ // has been modified to add/update/delete any tag. Merge will be
426
+ // necessary in delete case, to honour the user-defined tags
427
+ // in Infrastructure.Status and those to newly created resources.
428
+ func getTagList (ocpTags , infraStatusTags , machineSpecTags map [string ]string ) (map [string ]* string , error ) {
429
+ if machineSpecTags == nil && infraStatusTags == nil {
430
+ return * to .StringMapPtr (ocpTags ), nil
431
+ }
432
+
433
+ // Tag keys are case-insensitive. A tag with a key, regardless of
434
+ // the casing, is updated or retrieved. An Azure service might keep
435
+ // the casing as provided for the tag key. To allow user to choose
436
+ // the required variant of the key to add. return error when
437
+ // duplicate tag keys are found in machineSpecTags. Tags in
438
+ // infraStatusTags is vetted by installer.
439
+ if err := findDuplicateTagKeys (machineSpecTags ); err != nil {
440
+ return nil , fmt .Errorf ("Machine.Spec.ProviderSpec.Tags validation failed: %w" , err )
441
+ }
442
+
443
+ tags := make (map [string ]* string )
444
+ // copy user defined tags in Infrastructure.Status.
445
+ for k , v := range infraStatusTags {
446
+ tags [k ] = to .StringPtr (v )
447
+ }
448
+
449
+ // merge tags present in Infrastructure.Status with
450
+ // the tags configured in AzureMachineProviderSpec, with
451
+ // precedence given to those in AzureMachineProviderSpec
452
+ // for new or updated tags.
453
+ for k , v := range machineSpecTags {
454
+ tags [k ] = to .StringPtr (v )
455
+ }
456
+
457
+ // copy OCP tags, overwrite any OCP reserved tags found in
458
+ // the user defined tag list.
459
+ for k , v := range ocpTags {
460
+ tags [k ] = to .StringPtr (v )
461
+ }
462
+
463
+ // Automation, Content Delivery Network, DNS Azure resources can have a
464
+ // maximum of 15 tags. OpenShift is reserved with 5 tags for internal use,
465
+ // allowing 10 tags for user configuration. But this operator does not create
466
+ // any of the above resources and supports configuring additional tags, hence
467
+ // increasing the user tags max limit to 45.
468
+ if len (ocpTags ) > 5 || (len (tags )- len (ocpTags )) > 45 {
469
+ return nil , fmt .Errorf ("ocp can define upto 5 tags and user can define upto 45 tags," +
470
+ "infrstructure.status.resourceTags and Machine.Spec.ProviderSpec.Tags put together, current tag count: %d" , len (tags ))
471
+ }
472
+
473
+ return tags , nil
474
+ }
475
+
476
+ func getInfraResourceTags (platformStatus * configv1.PlatformStatus ) (tags map [string ]string ) {
477
+ if platformStatus != nil && platformStatus .Azure != nil && platformStatus .Azure .ResourceTags != nil {
478
+ tags = make (map [string ]string , len (platformStatus .Azure .ResourceTags ))
479
+ for _ , tag := range platformStatus .Azure .ResourceTags {
480
+ tags [tag .Key ] = tag .Value
481
+ }
482
+ }
483
+ return
484
+ }
485
+
486
+ func getOCPTagList (clusterID string ) (map [string ]string , error ) {
487
+ if clusterID == "" {
488
+ return nil , fmt .Errorf ("cluster ID required for generating OCP tag list" )
489
+ }
490
+ return map [string ]string {
491
+ fmt .Sprintf ("kubernetes.io_cluster.%v" , clusterID ): "owned" ,
492
+ }, nil
493
+ }
494
+
495
+ func findDuplicateTagKeys (tagSet map [string ]string ) error {
496
+ if len (tagSet ) == 0 {
497
+ return nil
498
+ }
499
+
500
+ dupKeys := make (map [string ]int )
501
+ for k := range tagSet {
502
+ dupKeys [strings .ToTitle (k )]++
503
+ }
504
+
505
+ var errMsg []string
506
+ for key , count := range dupKeys {
507
+ if count > 1 {
508
+ errMsg = append (errMsg , fmt .Sprintf ("\" %s\" matches %d keys" , key , count ))
509
+ }
510
+ }
511
+ if len (errMsg ) > 0 {
512
+ return fmt .Errorf ("found duplicate tag keys: %v" , strings .Join (errMsg , ", " ))
513
+ }
514
+
515
+ return nil
516
+ }
0 commit comments