Skip to content

Commit

Permalink
fix resource group tags updation
Browse files Browse the repository at this point in the history
Signed-off-by: Karuppiah Natarajan <[email protected]>
  • Loading branch information
karuppiah7890 committed Sep 23, 2021
1 parent 0ea1c6a commit 5494ae3
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 4 deletions.
6 changes: 6 additions & 0 deletions api/v1alpha4/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ 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"

// 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 @@ -172,6 +172,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
36 changes: 36 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 @@ -747,3 +748,38 @@ 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
}
36 changes: 36 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 @@ -603,3 +604,38 @@ 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
}
59 changes: 57 additions & 2 deletions azure/services/groups/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package groups
import (
"context"

"github.com/Azure/go-autorest/autorest/to"
"github.com/go-logr/logr"
"github.com/pkg/errors"

infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/async"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/tags"
"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
)
Expand All @@ -36,6 +38,7 @@ const serviceName = "group"
type Service struct {
Scope GroupScope
client
tagsSvc azure.Reconciler
}

// GroupScope defines the scope interface for a group service.
Expand All @@ -44,14 +47,41 @@ type GroupScope interface {
azure.Authorizer
azure.AsyncStatusUpdater
GroupSpec() azure.ResourceSpecGetter
AdditionalTags() infrav1.Tags
ClusterName() string
AnnotationJSON(string) (map[string]interface{}, error)
UpdateAnnotationJSON(string, map[string]interface{}) error
}

// GroupTagScope defines the scope for a group service with additional information about tags.
type GroupTagScope struct {
GroupScope
}

func (s *GroupTagScope) TagsSpecs() []azure.TagsSpec {
return []azure.TagsSpec{
{
Scope: azure.ResourceGroupID(s.SubscriptionID(), s.GroupSpec().ResourceName()),
Tags: infrav1.Build(infrav1.BuildParams{
ClusterName: s.ClusterName(),
Lifecycle: infrav1.ResourceLifecycleOwned,
Name: to.StringPtr(s.GroupSpec().ResourceName()),
Role: to.StringPtr(infrav1.CommonRole),
Additional: s.AdditionalTags(),
}),
Annotation: infrav1.RGTagsLastAppliedAnnotation,
},
}
}

// New creates a new service.
func New(scope GroupScope) *Service {
groupTagScope := &GroupTagScope{scope}

return &Service{
Scope: scope,
client: newClient(scope),
Scope: scope,
client: newClient(scope),
tagsSvc: tags.New(groupTagScope),
}
}

Expand All @@ -67,9 +97,34 @@ func (s *Service) Reconcile(ctx context.Context) error {

err := async.CreateResource(ctx, s.Scope, s.client, groupSpec, serviceName)
s.Scope.UpdatePutStatus(infrav1.ResourceGroupReadyCondition, serviceName, err)

if err != nil {
return err
}

err = s.ReconcileTags(ctx)

return err
}

func (s *Service) ReconcileTags(ctx context.Context) error {
// check that the resource group is not BYO.
managed, err := s.IsGroupManaged(ctx)
if err != nil {
return errors.Wrap(err, "could not get resource group management state")
}
if !managed {
s.Scope.V(2).Info("Should not update resource group tags in unmanaged mode")
return azure.ErrNotOwned
}

if err := s.tagsSvc.Reconcile(ctx); err != nil {
return errors.Wrap(err, "unable to update tags")
}

return nil
}

// Delete deletes the resource group if it is managed by capz.
func (s *Service) Delete(ctx context.Context) error {
ctx, span := tele.Tracer().Start(ctx, "groups.Service.Delete")
Expand Down
1 change: 1 addition & 0 deletions azure/services/groups/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ 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{
Expand Down
4 changes: 2 additions & 2 deletions azure/services/tags/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
// TagScope defines the scope interface for a tags service.
type TagScope interface {
logr.Logger
azure.ClusterDescriber
azure.Authorizer
TagsSpecs() []azure.TagsSpec
AnnotationJSON(string) (map[string]interface{}, error)
UpdateAnnotationJSON(string, map[string]interface{}) error
Expand Down Expand Up @@ -94,7 +94,7 @@ func (s *Service) Reconcile(ctx context.Context) error {
return nil
}

// Delete is a no-op as the tags get deleted as part of VM deletion.
// Delete is a no-op as the tags get deleted as part of resource deletion.
func (s *Service) Delete(ctx context.Context) error {
_, span := tele.Tracer().Start(ctx, "tags.Service.Delete")
defer span.End()
Expand Down

0 comments on commit 5494ae3

Please sign in to comment.