Skip to content

Commit

Permalink
Elide last applied configuration annotation when SSA is supported (pu…
Browse files Browse the repository at this point in the history
…lumi#1863)

* Elide last applied configuration annotation when SSA is supported

* Changelog

* Avoid mutating old inputs in Read unless importing
  • Loading branch information
Vivek Lakshmanan committed Jan 8, 2022
1 parent feebd29 commit 11e356e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 36 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
## HEAD (Unreleased)

- Change await log type to cloud-ready-check lib (https://github.com/pulumi/pulumi-kubernetes/pull/1855)
- Populate inputs from live state for imports (https://github.com/pulumi/pulumi-kubernetes/pull/1846)
- Elide last-applied-configuration annotation when server-side support is enabled (https://github.com/pulumi/pulumi-kubernetes/pull/1863)

## 3.12.2 (January 5, 2022)

- Relax ingress await restrictions (https://github.com/pulumi/pulumi-kubernetes/pull/1832)
- Exclude nil entries from values (https://github.com/pulumi/pulumi-kubernetes/pull/1845)
- Populate inputs from live state for imports (https://github.com/pulumi/pulumi-kubernetes/pull/1846)

## 3.12.1 (December 9, 2021)

Expand Down
78 changes: 43 additions & 35 deletions provider/pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ func (k *kubeProvider) Create(
return &pulumirpc.CreateResponse{Id: "", Properties: req.GetProperties()}, nil
}

annotatedInputs, err := withLastAppliedConfig(newInputs)
annotatedInputs, err := k.withLastAppliedConfig(newInputs)
if err != nil {
return nil, pkgerrors.Wrapf(
err, "Failed to create resource %s/%s because of an error generating the %s value in "+
Expand Down Expand Up @@ -1842,7 +1842,14 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
return nil, err
}

noOldInputs := false
namespace, name := parseFqName(req.GetId())
if name == "" {
return nil, fmt.Errorf(
"failed to read resource because of a failure to parse resource name from request ID: %s",
req.GetId())
}

freshImport := false
oldInputs, oldLive := parseCheckpointObject(oldState)
if oldInputs.GroupVersionKind().Empty() {
if oldLive.GroupVersionKind().Empty() {
Expand All @@ -1851,23 +1858,18 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
return nil, err
}
oldInputs.SetGroupVersionKind(gvk)
noOldInputs = true
freshImport = true
} else {
oldInputs.SetGroupVersionKind(oldLive.GroupVersionKind())
}
}

namespace, name := parseFqName(req.GetId())
if name == "" {
return nil, fmt.Errorf(
"failed to read resource because of a failure to parse resource name from request ID: %s",
req.GetId())
}
if oldInputs.GetName() == "" {
oldInputs.SetName(name)
}
if oldInputs.GetNamespace() == "" {
oldInputs.SetNamespace(namespace)
if oldInputs.GetName() == "" {
oldInputs.SetName(name)
}

if oldInputs.GetNamespace() == "" {
oldInputs.SetNamespace(namespace)
}
}

initialAPIVersion, err := initialAPIVersion(oldState, oldInputs)
Expand Down Expand Up @@ -1949,7 +1951,8 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p

// Attempt to parse the inputs for this object. If parsing was unsuccessful, retain the old inputs.
liveInputs := parseLiveInputs(liveObj, oldInputs)
if noOldInputs {

if freshImport {
// If no previous inputs were known, this is a fresh import. In which case we want to populate
// the inputs from the live state for the resource by referring to the input properties for the resource.
pkgSpec := pulumischema.PackageSpec{}
Expand All @@ -1971,7 +1974,7 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "managedFields")
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "resourceVersion")
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "uid")
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "annotations", lastAppliedConfigKey)
}

// TODO(lblackstone): not sure why this is needed
Expand Down Expand Up @@ -2126,7 +2129,7 @@ func (k *kubeProvider) Update(
// Ignore old state; we'll get it from Kubernetes later.
oldInputs, _ := parseCheckpointObject(oldState)

annotatedInputs, err := withLastAppliedConfig(newInputs)
annotatedInputs, err := k.withLastAppliedConfig(newInputs)
if err != nil {
return nil, pkgerrors.Wrapf(
err, "Failed to update resource %s/%s because of an error generating the %s value in "+
Expand Down Expand Up @@ -2561,6 +2564,28 @@ func (k *kubeProvider) tryServerSidePatch(oldInputs, newInputs *unstructured.Uns
return ssPatch, ssPatchBase, true
}

func (k *kubeProvider) withLastAppliedConfig(config *unstructured.Unstructured) (*unstructured.Unstructured, error) {
if k.supportsDryRun(config.GroupVersionKind()) {
// Skip last-applied-config annotation if the resource supports server-side apply.
return config, nil
}

// Serialize the inputs and add the last-applied-configuration annotation.
marshaled, err := config.MarshalJSON()
if err != nil {
return nil, err
}

// Deep copy the config before returning.
config = config.DeepCopy()

annotations := getAnnotations(config)

annotations[lastAppliedConfigKey] = string(marshaled)
config.SetAnnotations(annotations)
return config, nil
}

func mapReplStripSecrets(v resource.PropertyValue) (interface{}, bool) {
if v.IsSecret() {
return v.SecretValue().Element.MapRepl(nil, mapReplStripSecrets), true
Expand Down Expand Up @@ -2618,23 +2643,6 @@ func initialAPIVersion(state resource.PropertyMap, oldConfig *unstructured.Unstr
return oldConfig.GetAPIVersion(), nil
}

func withLastAppliedConfig(config *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// Serialize the inputs and add the last-applied-configuration annotation.
marshaled, err := config.MarshalJSON()
if err != nil {
return nil, err
}

// Deep copy the config before returning.
config = config.DeepCopy()

annotations := getAnnotations(config)

annotations[lastAppliedConfigKey] = string(marshaled)
config.SetAnnotations(annotations)
return config, nil
}

func checkpointObject(inputs, live *unstructured.Unstructured, fromInputs resource.PropertyMap, initialAPIVersion string) resource.PropertyMap {
object := resource.NewPropertyMapFromMap(live.Object)
inputsPM := resource.NewPropertyMapFromMap(inputs.Object)
Expand Down
9 changes: 9 additions & 0 deletions tests/sdk/nodejs/nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,15 @@ func TestDryRun(t *testing.T) {
Additive: true,
},
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
for _, res := range stackInfo.Deployment.Resources {
if res.Type == "kubernetes:apps/v1:Deployment" {
annotations, _ := openapi.Pluck(res.Outputs, "metadata", "annotations")
assert.NotEmpty(t, annotations)
assert.NotContains(t, annotations, "kubectl.kubernetes.io/last-applied-configuration")
}
}
},
})
integration.ProgramTest(t, &test)
}
Expand Down

0 comments on commit 11e356e

Please sign in to comment.