diff --git a/pkg/helpers/helpers_labels_test.go b/pkg/helpers/helpers_labels_test.go new file mode 100644 index 00000000000..2eb634348a5 --- /dev/null +++ b/pkg/helpers/helpers_labels_test.go @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package helpers + +import ( + "log" + "testing" + + "github.com/Azure/go-autorest/autorest/to" +) + +func TestLabelsToTags(t *testing.T) { + checks := []struct { + Name string + In map[string]string + Out map[string]*string + }{ + { + Name: "normal labels", + In: map[string]string{ + "age": "null", + "type": "null", + }, + Out: map[string]*string{ + "age": to.StringPtr("null"), + "type": to.StringPtr("null"), + }, + }, + { + Name: "backslash labels", + In: map[string]string{ + "age/date": "null", + "type\\kind": "null", + "fun/\\zone": "null", + }, + Out: map[string]*string{ + "age.date": to.StringPtr("null"), + "type.kind": to.StringPtr("null"), + "fun..zone": to.StringPtr("null"), + }, + }, + { + Name: "greater than less than labels", + In: map[string]string{ + "age>date": "null", + "typezone": "null", + }, + Out: map[string]*string{ + "age.date": to.StringPtr("null"), + "type.kind": to.StringPtr("null"), + "fun..zone": to.StringPtr("null"), + }, + }, + { + Name: "percent labels", + In: map[string]string{ + "age%date": "null", + "%": "null", + }, + Out: map[string]*string{ + "age.date": to.StringPtr("null"), + ".": to.StringPtr("null"), + }, + }, + } + for _, check := range checks { + log.Println("Checking", check.Name) + translated := LabelsToTags(check.In) + for k, v := range translated { + value := v + if check.Out[k] == nil { + t.Errorf("Expected 'null'\nGot nil\n%q", k) + } + if check.Out[k] != nil && *value != *check.Out[k] { + t.Errorf("Expected %s\nGot %s", *check.Out[k], *value) + } + } + } +} diff --git a/pkg/helpers/labels.go b/pkg/helpers/labels.go new file mode 100644 index 00000000000..7647a895242 --- /dev/null +++ b/pkg/helpers/labels.go @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package helpers + +import ( + "strings" +) + +// LabelsToTags converts labels from a kube resource to the data structure expected by Azure for tags +// this function will translate characters that are not allows by Azure to "."s +func LabelsToTags(in map[string]string) map[string]*string { + out := map[string]*string{} + for k, v := range in { + newK := k + value := v + if strings.ContainsAny(k, "<>%/?\\") { + newK = ReplaceAny(k, []string{"<", ">", "%", "/", "\\\\", "?"}) + } + out[newK] = &value + } + + return out +} diff --git a/pkg/helpers/stringhelper.go b/pkg/helpers/stringhelper.go index 5613f45cb8f..92e8e10105e 100644 --- a/pkg/helpers/stringhelper.go +++ b/pkg/helpers/stringhelper.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "math/rand" + "regexp" "strings" "time" "unicode" @@ -146,3 +147,10 @@ func Hash256(i interface{}) string { h.Write(inBytes) return fmt.Sprintf("%x", h.Sum(nil)) } + +// ReplaceAny replaces any instance of the strings passes in the chars slice +// replacing a backslash is problematic so it will require 4 eg []string{"\\\\"} +func ReplaceAny(s string, chars []string) string { + reg := regexp.MustCompile(fmt.Sprintf(`(%s)`, strings.Join(chars, "|"))) + return reg.ReplaceAllString(s, ".") +} diff --git a/pkg/resourcemanager/azuresql/azuresqldb/azuresqldb_reconcile.go b/pkg/resourcemanager/azuresql/azuresqldb/azuresqldb_reconcile.go index fae66455458..70276ecb232 100644 --- a/pkg/resourcemanager/azuresql/azuresqldb/azuresqldb_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqldb/azuresqldb_reconcile.go @@ -42,11 +42,7 @@ func (db *AzureSqlDbManager) Ensure(ctx context.Context, obj runtime.Object, opt dbEdition := instance.Spec.Edition // convert kube labels to expected tag format - labels := map[string]*string{} - for k, v := range instance.GetLabels() { - value := v - labels[k] = &value - } + labels := helpers.LabelsToTags(instance.GetLabels()) azureSQLDatabaseProperties := azuresqlshared.SQLDatabaseProperties{ DatabaseName: dbName, diff --git a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go index 0cfd41c2c66..f4416ddd55d 100644 --- a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go @@ -38,6 +38,9 @@ func (s *AzureSqlServerManager) Ensure(ctx context.Context, obj runtime.Object, return false, err } + // convert kube labels to expected tag format + tags := helpers.LabelsToTags(instance.GetLabels()) + // Check to see if secret already exists for admin username/password // create or update the secret key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} @@ -136,7 +139,7 @@ func (s *AzureSqlServerManager) Ensure(ctx context.Context, obj runtime.Object, // create the sql server instance.Status.Provisioning = true - if _, err := s.CreateOrUpdateSQLServer(ctx, instance.Spec.ResourceGroup, instance.Spec.Location, instance.Name, labels, azureSQLServerProperties, false); err != nil { + if _, err := s.CreateOrUpdateSQLServer(ctx, instance.Spec.ResourceGroup, instance.Spec.Location, instance.Name, tags, azureSQLServerProperties, false); err != nil { instance.Status.Message = err.Error()