Skip to content

Commit

Permalink
Fix resource group not getting updated if tags are added
Browse files Browse the repository at this point in the history
Signed-off-by: Karuppiah Natarajan <[email protected]>
  • Loading branch information
karuppiah7890 committed Oct 22, 2021
1 parent 9250e50 commit 6e9e775
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 199 deletions.
9 changes: 9 additions & 0 deletions api/v1beta1/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ const (
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
// for annotation formatting rules.
VMTagsLastAppliedAnnotation = "sigs.k8s.io/cluster-api-provider-azure-last-applied-tags-vm"

// TODO(karuppiah7890): Check if this is the right place to define RGTagsLastAppliedAnnotation constant.
// And if it needs to be defined elsewhere too, like v1alpha3, v1alpha4 packages.

// RGTagsLastAppliedAnnotation is the key for the Azure Cluster object annotation
// which tracks the AdditionalTags for Resource Group which is part in the Azure Cluster.
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
// for annotation formatting rules.
RGTagsLastAppliedAnnotation = "sigs.k8s.io/cluster-api-provider-azure-last-applied-tags-rg"
)

// SpecVersionHashTagKey is the key for the spec version hash used to enable quick spec difference comparison.
Expand Down
5 changes: 5 additions & 0 deletions azure/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ func WithIndex(name string, n int) string {
return fmt.Sprintf("%s-%d", name, n)
}

// ResourceGroupID returns the azure resource ID for a given resource group.
func ResourceGroupID(subscriptionID, resourceGroup string) string {
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, resourceGroup)
}

// VMID returns the azure resource ID for a given VM.
func VMID(subscriptionID, resourceGroup, vmName string) string {
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", subscriptionID, resourceGroup, vmName)
Expand Down
47 changes: 47 additions & 0 deletions azure/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package scope

import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"strconv"
Expand Down Expand Up @@ -796,3 +797,49 @@ func (s *ClusterScope) UpdatePatchStatus(condition clusterv1.ConditionType, serv
conditions.MarkFalse(s.AzureCluster, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error())
}
}

// AnnotationJSON returns a map[string]interface from a JSON annotation.
func (s *ClusterScope) AnnotationJSON(annotation string) (map[string]interface{}, error) {
out := map[string]interface{}{}
jsonAnnotation := s.AzureCluster.GetAnnotations()[annotation]
if len(jsonAnnotation) == 0 {
return out, nil
}
err := json.Unmarshal([]byte(jsonAnnotation), &out)
if err != nil {
return out, err
}
return out, nil
}

// UpdateAnnotationJSON updates the `annotation` with
// `content`. `content` in this case should be a `map[string]interface{}`
// suitable for turning into JSON. This `content` map will be marshalled into a
// JSON string before being set as the given `annotation`.
func (s *ClusterScope) UpdateAnnotationJSON(annotation string, content map[string]interface{}) error {
b, err := json.Marshal(content)
if err != nil {
return err
}
s.SetAnnotation(annotation, string(b))
return nil
}

// SetAnnotation sets a key value annotation on the AzureCluster.
func (s *ClusterScope) SetAnnotation(key, value string) {
if s.AzureCluster.Annotations == nil {
s.AzureCluster.Annotations = map[string]string{}
}
s.AzureCluster.Annotations[key] = value
}

// TagsSpecs returns the tag specs for the AzureCluster.
func (s *ClusterScope) TagsSpecs() []azure.TagsSpec {
return []azure.TagsSpec{
{
Scope: azure.ResourceGroupID(s.SubscriptionID(), s.ResourceGroup()),
Tags: s.AdditionalTags(),
Annotation: infrav1.RGTagsLastAppliedAnnotation,
},
}
}
47 changes: 47 additions & 0 deletions azure/scope/managedcontrolplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package scope
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"strings"
Expand Down Expand Up @@ -622,3 +623,49 @@ func (s *ManagedControlPlaneScope) UpdatePutStatus(condition clusterv1.Condition
func (s *ManagedControlPlaneScope) UpdatePatchStatus(condition clusterv1.ConditionType, service string, err error) {
// TODO: add condition to AzureManagedControlPlane status
}

// AnnotationJSON returns a map[string]interface from a JSON annotation.
func (s *ManagedControlPlaneScope) AnnotationJSON(annotation string) (map[string]interface{}, error) {
out := map[string]interface{}{}
jsonAnnotation := s.ControlPlane.GetAnnotations()[annotation]
if len(jsonAnnotation) == 0 {
return out, nil
}
err := json.Unmarshal([]byte(jsonAnnotation), &out)
if err != nil {
return out, err
}
return out, nil
}

// UpdateAnnotationJSON updates the `annotation` with
// `content`. `content` in this case should be a `map[string]interface{}`
// suitable for turning into JSON. This `content` map will be marshalled into a
// JSON string before being set as the given `annotation`.
func (s *ManagedControlPlaneScope) UpdateAnnotationJSON(annotation string, content map[string]interface{}) error {
b, err := json.Marshal(content)
if err != nil {
return err
}
s.SetAnnotation(annotation, string(b))
return nil
}

// SetAnnotation sets a key value annotation on the ControlPlane.
func (s *ManagedControlPlaneScope) SetAnnotation(key, value string) {
if s.ControlPlane.Annotations == nil {
s.ControlPlane.Annotations = map[string]string{}
}
s.ControlPlane.Annotations[key] = value
}

// TagsSpecs returns the tag specs for the ManagedControlPlane.
func (s *ManagedControlPlaneScope) TagsSpecs() []azure.TagsSpec {
return []azure.TagsSpec{
{
Scope: azure.ResourceGroupID(s.SubscriptionID(), s.ResourceGroup()),
Tags: s.AdditionalTags(),
Annotation: infrav1.RGTagsLastAppliedAnnotation,
},
}
}
4 changes: 3 additions & 1 deletion azure/services/groups/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,18 @@ func (s *GroupSpec) OwnerResourceName() string {
func (s *GroupSpec) Parameters(existing interface{}) (interface{}, error) {
if existing != nil {
// rg already exists, nothing to update.
// Note that rg tags are updated separately using tags service.
return nil, nil
}
return resources.Group{
Location: to.StringPtr(s.Location),
// We create only CAPZ default tags. User defined additional tags
// are created and updated using tags service.
Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{
ClusterName: s.ClusterName,
Lifecycle: infrav1.ResourceLifecycleOwned,
Name: to.StringPtr(s.Name),
Role: to.StringPtr(infrav1.CommonRole),
Additional: s.AdditionalTags,
})),
}, nil
}
11 changes: 6 additions & 5 deletions azure/services/tags/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// client wraps go-sdk.
type client interface {
GetAtScope(context.Context, string) (resources.TagsResource, error)
CreateOrUpdateAtScope(context.Context, string, resources.TagsResource) (resources.TagsResource, error)
UpdateAtScope(context.Context, string, resources.TagsPatchResource) (resources.TagsResource, error)
}

// azureClient contains the Azure go-sdk Client.
Expand Down Expand Up @@ -60,10 +60,11 @@ func (ac *azureClient) GetAtScope(ctx context.Context, scope string) (resources.
return ac.tags.GetAtScope(ctx, scope)
}

// CreateOrUpdateAtScope allows adding or replacing the entire set of tags on the specified resource or subscription.
func (ac *azureClient) CreateOrUpdateAtScope(ctx context.Context, scope string, parameters resources.TagsResource) (resources.TagsResource, error) {
ctx, _, done := tele.StartSpanWithLogger(ctx, "tags.AzureClient.CreateOrUpdateAtScope")
// UpdateAtScope this operation allows replacing, merging or selectively deleting tags on the specified resource or
// subscription.
func (ac *azureClient) UpdateAtScope(ctx context.Context, scope string, parameters resources.TagsPatchResource) (resources.TagsResource, error) {
ctx, _, done := tele.StartSpanWithLogger(ctx, "tags.AzureClient.UpdateAtScope")
defer done()

return ac.tags.CreateOrUpdateAtScope(ctx, scope, parameters)
return ac.tags.UpdateAtScope(ctx, scope, parameters)
}
24 changes: 12 additions & 12 deletions azure/services/tags/mock_tags/client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 0 additions & 85 deletions azure/services/tags/mock_tags/tags_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6e9e775

Please sign in to comment.