From 862ca9b0e6c2eee62b8d11bc1cd710bd8a1e3b75 Mon Sep 17 00:00:00 2001 From: Levi Blackstone Date: Mon, 11 Feb 2019 15:25:30 -0700 Subject: [PATCH 1/2] Allow awaiters to be skipped by setting an annotation - Generalize annotation handling - Create "pulumi.com/skipAwait" annotation - Skip await logic for any resource with the skipAwait annotation set to true --- pkg/annotations/base.go | 45 +++++++++ pkg/annotations/naming.go | 66 ++++++++++++ pkg/{provider => annotations}/naming_test.go | 36 ++++--- pkg/annotations/overrides.go | 23 +++++ pkg/annotations/overrides_test.go | 51 ++++++++++ pkg/await/await.go | 100 +++++++++++-------- pkg/provider/naming.go | 67 ------------- pkg/provider/provider.go | 12 ++- 8 files changed, 274 insertions(+), 126 deletions(-) create mode 100644 pkg/annotations/base.go create mode 100644 pkg/annotations/naming.go rename pkg/{provider => annotations}/naming_test.go (56%) create mode 100644 pkg/annotations/overrides.go create mode 100644 pkg/annotations/overrides_test.go delete mode 100644 pkg/provider/naming.go diff --git a/pkg/annotations/base.go b/pkg/annotations/base.go new file mode 100644 index 0000000000..071d19e735 --- /dev/null +++ b/pkg/annotations/base.go @@ -0,0 +1,45 @@ +// Copyright 2016-2019, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package annotations + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const AnnotationTrue = "true" +const AnnotationFalse = "false" +const AnnotationPrefix = "pulumi.com/" +const AnnotationInternalPrefix = AnnotationPrefix + "internal/" +const AnnotationInternalAutonamed = AnnotationInternalPrefix + "autonamed" +const AnnotationSkipAwait = AnnotationPrefix + "skipAwait" + +func SetAnnotation(obj *unstructured.Unstructured, key, value string) { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[key] = value + obj.SetAnnotations(annotations) +} + +func SetAnnotationTrue(obj *unstructured.Unstructured, key string) { + SetAnnotation(obj, key, AnnotationTrue) +} + +func IsAnnotationTrue(obj *unstructured.Unstructured, key string) bool { + annotations := obj.GetAnnotations() + value := annotations[key] + return value == AnnotationTrue +} diff --git a/pkg/annotations/naming.go b/pkg/annotations/naming.go new file mode 100644 index 0000000000..fe687b80c7 --- /dev/null +++ b/pkg/annotations/naming.go @@ -0,0 +1,66 @@ +// Copyright 2016-2019, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package annotations + +import ( + "fmt" + "math/rand" + "time" + + "github.com/pulumi/pulumi/pkg/tokens" + "github.com/pulumi/pulumi/pkg/util/contract" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +var dns1123Alphabet = []rune("abcdefghijklmnopqrstuvwxyz0123456789") + +// AssignNameIfAutonamable generates a name for an object. Uses DNS-1123-compliant characters. +// All auto-named resources get the annotation `pulumi.com/autonamed` for tooling purposes. +func AssignNameIfAutonamable(obj *unstructured.Unstructured, base tokens.QName) { + contract.Assert(base != "") + if obj.GetName() == "" { + obj.SetName(fmt.Sprintf("%s-%s", base, randString(8))) + SetAnnotationTrue(obj, AnnotationInternalAutonamed) + } +} + +// AdoptOldNameIfUnnamed checks if `newObj` has a name, and if not, "adopts" the name of `oldObj` +// instead. If `oldObj` was autonamed, then we mark `newObj` as autonamed, too. +func AdoptOldNameIfUnnamed(newObj, oldObj *unstructured.Unstructured) { + contract.Assert(oldObj.GetName() != "") + if newObj.GetName() == "" { + newObj.SetName(oldObj.GetName()) + if IsAutonamed(oldObj) { + SetAnnotationTrue(newObj, AnnotationInternalAutonamed) + } + } +} + +func IsAutonamed(obj *unstructured.Unstructured) bool { + return IsAnnotationTrue(obj, AnnotationInternalAutonamed) +} + +func randString(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = dns1123Alphabet[rand.Intn(len(dns1123Alphabet))] + } + return string(b) +} + +// Seed RNG to get different random names at each suffix. +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/pkg/provider/naming_test.go b/pkg/annotations/naming_test.go similarity index 56% rename from pkg/provider/naming_test.go rename to pkg/annotations/naming_test.go index 930bc298ba..5f5b8ed962 100644 --- a/pkg/provider/naming_test.go +++ b/pkg/annotations/naming_test.go @@ -1,6 +1,18 @@ -// Copyright 2016-2018, Pulumi Corporation. All rights reserved. +// Copyright 2016-2019, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -package provider +package annotations import ( "strings" @@ -14,16 +26,16 @@ import ( func TestAssignNameIfAutonamable(t *testing.T) { // o1 has no name, so autonaming succeeds. o1 := &unstructured.Unstructured{} - assignNameIfAutonamable(o1, "foo") - assert.True(t, isAutonamed(o1)) + AssignNameIfAutonamable(o1, "foo") + assert.True(t, IsAutonamed(o1)) assert.True(t, strings.HasPrefix(o1.GetName(), "foo-")) // o2 has a name, so autonaming fails. o2 := &unstructured.Unstructured{ Object: map[string]interface{}{"metadata": map[string]interface{}{"name": "bar"}}, } - assignNameIfAutonamable(o2, "foo") - assert.False(t, isAutonamed(o2)) + AssignNameIfAutonamable(o2, "foo") + assert.False(t, IsAutonamed(o2)) assert.Equal(t, "bar", o2.GetName()) } @@ -35,23 +47,23 @@ func TestAdoptName(t *testing.T) { "name": "old1", // NOTE: annotations needs to be a `map[string]interface{}` rather than `map[string]string` // or the k8s utility functions fail. - "annotations": map[string]interface{}{annotationInternalAutonamed: "true"}, + "annotations": map[string]interface{}{AnnotationInternalAutonamed: "true"}, }}, } new1 := &unstructured.Unstructured{ Object: map[string]interface{}{"metadata": map[string]interface{}{"name": "new1"}}, } - adoptOldNameIfUnnamed(new1, old1) + AdoptOldNameIfUnnamed(new1, old1) assert.Equal(t, "old1", old1.GetName()) - assert.True(t, isAutonamed(old1)) + assert.True(t, IsAutonamed(old1)) assert.Equal(t, "new1", new1.GetName()) - assert.False(t, isAutonamed(new1)) + assert.False(t, IsAutonamed(new1)) // new2 is unnamed and therefore DOES adopt old1's name. new2 := &unstructured.Unstructured{ Object: map[string]interface{}{}, } - adoptOldNameIfUnnamed(new2, old1) + AdoptOldNameIfUnnamed(new2, old1) assert.Equal(t, "old1", new2.GetName()) - assert.True(t, isAutonamed(new2)) + assert.True(t, IsAutonamed(new2)) } diff --git a/pkg/annotations/overrides.go b/pkg/annotations/overrides.go new file mode 100644 index 0000000000..f852aa1bc3 --- /dev/null +++ b/pkg/annotations/overrides.go @@ -0,0 +1,23 @@ +// Copyright 2016-2019, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package annotations + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func SkipAwaitLogic(obj *unstructured.Unstructured) bool { + return IsAnnotationTrue(obj, AnnotationSkipAwait) +} diff --git a/pkg/annotations/overrides_test.go b/pkg/annotations/overrides_test.go new file mode 100644 index 0000000000..df3f9323fb --- /dev/null +++ b/pkg/annotations/overrides_test.go @@ -0,0 +1,51 @@ +// Copyright 2016-2019, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package annotations + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestSkipAwaitLogic(t *testing.T) { + resource := &unstructured.Unstructured{} + + annotatedResourceTrue := &unstructured.Unstructured{} + annotatedResourceTrue.SetAnnotations(map[string]string{AnnotationSkipAwait: AnnotationTrue}) + + annotatedResourceFalse := &unstructured.Unstructured{} + annotatedResourceFalse.SetAnnotations(map[string]string{AnnotationSkipAwait: AnnotationFalse}) + + type args struct { + obj *unstructured.Unstructured + } + tests := []struct { + name string + args args + want bool + }{ + {name: "Skip annotation unset", args: args{resource}, want: false}, + {name: "Skip annotation set true", args: args{annotatedResourceTrue}, want: true}, + {name: "Skip annotation set false", args: args{annotatedResourceFalse}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SkipAwaitLogic(tt.args.obj); got != tt.want { + t.Errorf("SkipAwaitLogic() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/await/await.go b/pkg/await/await.go index 5c4488845b..599992c4cc 100644 --- a/pkg/await/await.go +++ b/pkg/await/await.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/golang/glog" + "github.com/pulumi/pulumi-kubernetes/pkg/annotations" "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi-kubernetes/pkg/retry" @@ -131,19 +132,22 @@ func Creation(c CreateConfig) (*unstructured.Unstructured, error) { // logic is blank, simply do nothing instead of logging. id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if awaiter.awaitCreation != nil { - conf := createAwaitConfig{ - host: c.Host, - ctx: c.Context, - urn: c.URN, - clientSet: c.ClientSet, - // TODO(lblackstone): maybe pass create output into input here? - currentInputs: c.Inputs, - currentOutputs: outputs, - } - waitErr := awaiter.awaitCreation(conf) - if waitErr != nil { - return nil, waitErr + if annotations.SkipAwaitLogic(c.Inputs) { + glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) + } else { + if awaiter.awaitCreation != nil { + conf := createAwaitConfig{ + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + currentInputs: c.Inputs, + currentOutputs: outputs, + } + waitErr := awaiter.awaitCreation(conf) + if waitErr != nil { + return nil, waitErr + } } } } else { @@ -173,18 +177,22 @@ func Read(c ReadConfig) (*unstructured.Unstructured, error) { id := fmt.Sprintf("%s/%s", outputs.GetAPIVersion(), outputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if awaiter.awaitRead != nil { - conf := createAwaitConfig{ - host: c.Host, - ctx: c.Context, - urn: c.URN, - clientSet: c.ClientSet, - currentInputs: c.Inputs, - currentOutputs: outputs, - } - waitErr := awaiter.awaitRead(conf) - if waitErr != nil { - return nil, waitErr + if annotations.SkipAwaitLogic(c.Inputs) { + glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) + } else { + if awaiter.awaitRead != nil { + conf := createAwaitConfig{ + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + currentInputs: c.Inputs, + currentOutputs: outputs, + } + waitErr := awaiter.awaitRead(conf) + if waitErr != nil { + return nil, waitErr + } } } } @@ -284,22 +292,26 @@ func Update(c UpdateConfig) (*unstructured.Unstructured, error) { // is blank, simply do nothing instead of logging. id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if awaiter.awaitUpdate != nil { - conf := updateAwaitConfig{ - createAwaitConfig: createAwaitConfig{ - host: c.Host, - ctx: c.Context, - urn: c.URN, - clientSet: c.ClientSet, - currentInputs: c.Inputs, - currentOutputs: currentOutputs, - }, - lastInputs: c.Previous, - lastOutputs: liveOldObj, - } - waitErr := awaiter.awaitUpdate(conf) - if waitErr != nil { - return nil, waitErr + if annotations.SkipAwaitLogic(c.Inputs) { + glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) + } else { + if awaiter.awaitUpdate != nil { + conf := updateAwaitConfig{ + createAwaitConfig: createAwaitConfig{ + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + currentInputs: c.Inputs, + currentOutputs: currentOutputs, + }, + lastInputs: c.Previous, + lastOutputs: liveOldObj, + } + waitErr := awaiter.awaitUpdate(conf) + if waitErr != nil { + return nil, waitErr + } } } } else { @@ -391,7 +403,11 @@ func Deletion(c DeleteConfig) error { var waitErr error id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists && awaiter.awaitDeletion != nil { - waitErr = awaiter.awaitDeletion(c.Context, client, c.Name) + if annotations.SkipAwaitLogic(c.Inputs) { + glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) + } else { + waitErr = awaiter.awaitDeletion(c.Context, client, c.Name) + } } else { for { select { diff --git a/pkg/provider/naming.go b/pkg/provider/naming.go deleted file mode 100644 index 0ca02d716c..0000000000 --- a/pkg/provider/naming.go +++ /dev/null @@ -1,67 +0,0 @@ -package provider - -import ( - "fmt" - "math/rand" - "time" - - "github.com/pulumi/pulumi/pkg/tokens" - "github.com/pulumi/pulumi/pkg/util/contract" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -const trueAnnotation = "true" -const annotationInternalPrefix = "pulumi.com/" -const annotationInternalAutonamed = "pulumi.com/autonamed" - -var dns1123Alphabet = []rune("abcdefghijklmnopqrstuvwxyz0123456789") - -// assignName generates a name for an object. Uses DNS-1123-compliant characters. All auto-named -// resources get the annotation `pulumi.com/autonamed` for tooling purposes. -func assignNameIfAutonamable(obj *unstructured.Unstructured, base tokens.QName) { - contract.Assert(base != "") - if obj.GetName() == "" { - obj.SetName(fmt.Sprintf("%s-%s", base, randString(8))) - setAutonameAnnotation(obj) - } -} - -// adoptOldNameIfUnnamed checks if `newObj` has a name, and if not, "adopts" the name of `oldObj` -// instead. If `oldObj` was autonamed, then we mark `newObj` as autonamed, too. -func adoptOldNameIfUnnamed(newObj, oldObj *unstructured.Unstructured) { - contract.Assert(oldObj.GetName() != "") - if newObj.GetName() == "" { - newObj.SetName(oldObj.GetName()) - if isAutonamed(oldObj) { - setAutonameAnnotation(newObj) - } - } -} - -func setAutonameAnnotation(obj *unstructured.Unstructured) { - annotations := obj.GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - annotations[annotationInternalAutonamed] = trueAnnotation - obj.SetAnnotations(annotations) -} - -func isAutonamed(obj *unstructured.Unstructured) bool { - annotations := obj.GetAnnotations() - autonamed := annotations[annotationInternalAutonamed] - return autonamed == trueAnnotation -} - -func randString(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = dns1123Alphabet[rand.Intn(len(dns1123Alphabet))] - } - return string(b) -} - -// Seed RNG to get different random names at each suffix. -func init() { - rand.Seed(time.Now().UTC().UnixNano()) -} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index cab47ec1e8..e72e9fc7cd 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -24,6 +24,7 @@ import ( "github.com/golang/glog" pbempty "github.com/golang/protobuf/ptypes/empty" "github.com/golang/protobuf/ptypes/struct" + "github.com/pulumi/pulumi-kubernetes/pkg/annotations" "github.com/pulumi/pulumi-kubernetes/pkg/await" "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" @@ -229,9 +230,10 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( // If annotations with the prefix `pulumi.com/` exist, report that as error. for k := range newInputs.GetAnnotations() { - if strings.HasPrefix(k, annotationInternalPrefix) { + if strings.HasPrefix(k, annotations.AnnotationInternalPrefix) { failures = append(failures, &pulumirpc.CheckFailure{ - Reason: fmt.Sprintf("annotation %q uses illegal prefix `pulumi.com/internal`", k), + Reason: fmt.Sprintf("annotation %q uses illegal prefix %q", k, + annotations.AnnotationInternalPrefix), }) } } @@ -246,9 +248,9 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( // NOTE: If old inputs exist, they have a name, either provided by the user or filled in with a // previous run of `Check`. contract.Assert(oldInputs.GetName() != "") - adoptOldNameIfUnnamed(newInputs, oldInputs) + annotations.AdoptOldNameIfUnnamed(newInputs, oldInputs) } else { - assignNameIfAutonamable(newInputs, urn.Name()) + annotations.AssignNameIfAutonamable(newInputs, urn.Name()) } gvk, err := k.gvkFromURN(urn) @@ -372,7 +374,7 @@ func (k *kubeProvider) Diff( len(replaces) > 0 && // 2. Object is NOT autonamed (i.e., user manually named it, and therefore we can't // auto-generate the name). - !isAutonamed(newInputs) && + !annotations.IsAutonamed(newInputs) && // 3. The new, user-specified name is the same as the old name. newInputs.GetName() == oldInputs.GetName() && // 4. The resource is being deployed to the same namespace (i.e., we aren't creating the From c6da5e245329d5a94af5eb794e70ba3e57a67845 Mon Sep 17 00:00:00 2001 From: Levi Blackstone Date: Mon, 11 Feb 2019 17:07:26 -0700 Subject: [PATCH 2/2] Use a valid annotation (only one / allowed in name) - Rename annotations package to metadata - Check against a known list of internal annotations rather than a prefix --- pkg/await/await.go | 10 +++--- .../base.go => metadata/annotations.go} | 32 +++++++++++++++---- pkg/{annotations => metadata}/naming.go | 8 ++--- pkg/{annotations => metadata}/naming_test.go | 4 +-- pkg/{annotations => metadata}/overrides.go | 2 +- .../overrides_test.go | 2 +- pkg/provider/provider.go | 15 ++++----- 7 files changed, 45 insertions(+), 28 deletions(-) rename pkg/{annotations/base.go => metadata/annotations.go} (68%) rename pkg/{annotations => metadata}/naming.go (91%) rename pkg/{annotations => metadata}/naming_test.go (95%) rename pkg/{annotations => metadata}/overrides.go (97%) rename pkg/{annotations => metadata}/overrides_test.go (98%) diff --git a/pkg/await/await.go b/pkg/await/await.go index 599992c4cc..a4b0db77b9 100644 --- a/pkg/await/await.go +++ b/pkg/await/await.go @@ -19,7 +19,7 @@ import ( "fmt" "github.com/golang/glog" - "github.com/pulumi/pulumi-kubernetes/pkg/annotations" + "github.com/pulumi/pulumi-kubernetes/pkg/metadata" "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi-kubernetes/pkg/retry" @@ -132,7 +132,7 @@ func Creation(c CreateConfig) (*unstructured.Unstructured, error) { // logic is blank, simply do nothing instead of logging. id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if annotations.SkipAwaitLogic(c.Inputs) { + if metadata.SkipAwaitLogic(c.Inputs) { glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) } else { if awaiter.awaitCreation != nil { @@ -177,7 +177,7 @@ func Read(c ReadConfig) (*unstructured.Unstructured, error) { id := fmt.Sprintf("%s/%s", outputs.GetAPIVersion(), outputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if annotations.SkipAwaitLogic(c.Inputs) { + if metadata.SkipAwaitLogic(c.Inputs) { glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) } else { if awaiter.awaitRead != nil { @@ -292,7 +292,7 @@ func Update(c UpdateConfig) (*unstructured.Unstructured, error) { // is blank, simply do nothing instead of logging. id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { - if annotations.SkipAwaitLogic(c.Inputs) { + if metadata.SkipAwaitLogic(c.Inputs) { glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) } else { if awaiter.awaitUpdate != nil { @@ -403,7 +403,7 @@ func Deletion(c DeleteConfig) error { var waitErr error id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists && awaiter.awaitDeletion != nil { - if annotations.SkipAwaitLogic(c.Inputs) { + if metadata.SkipAwaitLogic(c.Inputs) { glog.V(1).Infof("Skipping await logic for %v", c.Inputs.GetName()) } else { waitErr = awaiter.awaitDeletion(c.Context, client, c.Name) diff --git a/pkg/annotations/base.go b/pkg/metadata/annotations.go similarity index 68% rename from pkg/annotations/base.go rename to pkg/metadata/annotations.go index 071d19e735..3a3a27c036 100644 --- a/pkg/annotations/base.go +++ b/pkg/metadata/annotations.go @@ -12,18 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -package annotations +package metadata import ( + "strings" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -const AnnotationTrue = "true" -const AnnotationFalse = "false" -const AnnotationPrefix = "pulumi.com/" -const AnnotationInternalPrefix = AnnotationPrefix + "internal/" -const AnnotationInternalAutonamed = AnnotationInternalPrefix + "autonamed" -const AnnotationSkipAwait = AnnotationPrefix + "skipAwait" +const ( + AnnotationTrue = "true" + AnnotationFalse = "false" + + AnnotationPrefix = "pulumi.com/" + + AnnotationAutonamed = AnnotationPrefix + "autonamed" + AnnotationSkipAwait = AnnotationPrefix + "skipAwait" +) + +// Annotations for internal Pulumi use only. +var internalAnnotationPrefixes = []string{AnnotationAutonamed} + +func IsInternalAnnotation(key string) bool { + for _, annotationPrefix := range internalAnnotationPrefixes { + if strings.HasPrefix(key, annotationPrefix) { + return true + } + } + + return false +} func SetAnnotation(obj *unstructured.Unstructured, key, value string) { annotations := obj.GetAnnotations() diff --git a/pkg/annotations/naming.go b/pkg/metadata/naming.go similarity index 91% rename from pkg/annotations/naming.go rename to pkg/metadata/naming.go index fe687b80c7..f345a135da 100644 --- a/pkg/annotations/naming.go +++ b/pkg/metadata/naming.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package annotations +package metadata import ( "fmt" @@ -32,7 +32,7 @@ func AssignNameIfAutonamable(obj *unstructured.Unstructured, base tokens.QName) contract.Assert(base != "") if obj.GetName() == "" { obj.SetName(fmt.Sprintf("%s-%s", base, randString(8))) - SetAnnotationTrue(obj, AnnotationInternalAutonamed) + SetAnnotationTrue(obj, AnnotationAutonamed) } } @@ -43,13 +43,13 @@ func AdoptOldNameIfUnnamed(newObj, oldObj *unstructured.Unstructured) { if newObj.GetName() == "" { newObj.SetName(oldObj.GetName()) if IsAutonamed(oldObj) { - SetAnnotationTrue(newObj, AnnotationInternalAutonamed) + SetAnnotationTrue(newObj, AnnotationAutonamed) } } } func IsAutonamed(obj *unstructured.Unstructured) bool { - return IsAnnotationTrue(obj, AnnotationInternalAutonamed) + return IsAnnotationTrue(obj, AnnotationAutonamed) } func randString(n int) string { diff --git a/pkg/annotations/naming_test.go b/pkg/metadata/naming_test.go similarity index 95% rename from pkg/annotations/naming_test.go rename to pkg/metadata/naming_test.go index 5f5b8ed962..ef9d3c9d4c 100644 --- a/pkg/annotations/naming_test.go +++ b/pkg/metadata/naming_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package annotations +package metadata import ( "strings" @@ -47,7 +47,7 @@ func TestAdoptName(t *testing.T) { "name": "old1", // NOTE: annotations needs to be a `map[string]interface{}` rather than `map[string]string` // or the k8s utility functions fail. - "annotations": map[string]interface{}{AnnotationInternalAutonamed: "true"}, + "annotations": map[string]interface{}{AnnotationAutonamed: "true"}, }}, } new1 := &unstructured.Unstructured{ diff --git a/pkg/annotations/overrides.go b/pkg/metadata/overrides.go similarity index 97% rename from pkg/annotations/overrides.go rename to pkg/metadata/overrides.go index f852aa1bc3..33a6f13ebf 100644 --- a/pkg/annotations/overrides.go +++ b/pkg/metadata/overrides.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package annotations +package metadata import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/annotations/overrides_test.go b/pkg/metadata/overrides_test.go similarity index 98% rename from pkg/annotations/overrides_test.go rename to pkg/metadata/overrides_test.go index df3f9323fb..77e6ce40cb 100644 --- a/pkg/annotations/overrides_test.go +++ b/pkg/metadata/overrides_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package annotations +package metadata import ( "testing" diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index e72e9fc7cd..dc52a0f587 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -24,7 +24,7 @@ import ( "github.com/golang/glog" pbempty "github.com/golang/protobuf/ptypes/empty" "github.com/golang/protobuf/ptypes/struct" - "github.com/pulumi/pulumi-kubernetes/pkg/annotations" + "github.com/pulumi/pulumi-kubernetes/pkg/metadata" "github.com/pulumi/pulumi-kubernetes/pkg/await" "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" @@ -228,12 +228,11 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( var failures []*pulumirpc.CheckFailure - // If annotations with the prefix `pulumi.com/` exist, report that as error. + // If annotations with a reserved internal prefix exist, report that as error. for k := range newInputs.GetAnnotations() { - if strings.HasPrefix(k, annotations.AnnotationInternalPrefix) { + if metadata.IsInternalAnnotation(k) { failures = append(failures, &pulumirpc.CheckFailure{ - Reason: fmt.Sprintf("annotation %q uses illegal prefix %q", k, - annotations.AnnotationInternalPrefix), + Reason: fmt.Sprintf("invalid use of reserved internal annotation %q", k), }) } } @@ -248,9 +247,9 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( // NOTE: If old inputs exist, they have a name, either provided by the user or filled in with a // previous run of `Check`. contract.Assert(oldInputs.GetName() != "") - annotations.AdoptOldNameIfUnnamed(newInputs, oldInputs) + metadata.AdoptOldNameIfUnnamed(newInputs, oldInputs) } else { - annotations.AssignNameIfAutonamable(newInputs, urn.Name()) + metadata.AssignNameIfAutonamable(newInputs, urn.Name()) } gvk, err := k.gvkFromURN(urn) @@ -374,7 +373,7 @@ func (k *kubeProvider) Diff( len(replaces) > 0 && // 2. Object is NOT autonamed (i.e., user manually named it, and therefore we can't // auto-generate the name). - !annotations.IsAutonamed(newInputs) && + !metadata.IsAutonamed(newInputs) && // 3. The new, user-specified name is the same as the old name. newInputs.GetName() == oldInputs.GetName() && // 4. The resource is being deployed to the same namespace (i.e., we aren't creating the