Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate inputs from live state for imports #1846

Merged
merged 6 commits into from Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@

- 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
29 changes: 28 additions & 1 deletion provider/pkg/provider/provider.go
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
pulumischema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"io/ioutil"
"net/http"
"net/url"
Expand Down Expand Up @@ -1841,15 +1842,16 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
return nil, err
}

noOldInputs := false
oldInputs, oldLive := parseCheckpointObject(oldState)

if oldInputs.GroupVersionKind().Empty() {
if oldLive.GroupVersionKind().Empty() {
gvk, err := k.gvkFromURN(urn)
if err != nil {
return nil, err
}
oldInputs.SetGroupVersionKind(gvk)
noOldInputs = true
} else {
oldInputs.SetGroupVersionKind(oldLive.GroupVersionKind())
}
Expand Down Expand Up @@ -1947,6 +1949,30 @@ 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 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{}
if err := json.Unmarshal(k.pulumiSchema, &pkgSpec); err != nil {
return nil, err
}
res := pkgSpec.Resources[urn.Type().String()]
for k := range res.InputProperties {
if liveVal, ok := liveObj.Object[k]; ok {
if err = unstructured.SetNestedField(liveInputs.Object, liveVal, k); err != nil {
return nil, fmt.Errorf("failure setting field %q for %q: %w", k, urn, err)
}
}
}

// Cleanup some obviously non-input-ty fields.
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "creationTimestamp")
unstructured.RemoveNestedField(liveInputs.Object, "metadata", "generation")
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")
}

// TODO(lblackstone): not sure why this is needed
id := fqObjName(liveObj)
Expand Down Expand Up @@ -2759,6 +2785,7 @@ func parseLiveInputs(live, oldInputs *unstructured.Unstructured) *unstructured.U
inputs := &unstructured.Unstructured{Object: map[string]interface{}{}}
inputs.SetGroupVersionKind(live.GroupVersionKind())
metadata.AdoptOldAutonameIfUnnamed(inputs, live)

return inputs
}

Expand Down
22 changes: 22 additions & 0 deletions tests/sdk/go/go_test.go
Expand Up @@ -121,6 +121,28 @@ func TestGo(t *testing.T) {
Config: map[string]string{
"namespace": namespace,
},
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotEmpty(t, stack.Outputs["svc_ip"])
},
NoParallel: true,
})
integration.ProgramTest(t, &options)
})

t.Run("Import Deployment Created by Helm", func(t *testing.T) {
baseDir := filepath.Join(cwd, "helm-import-deployment", "step1")
namespace := getRandomNamespace("importdepl")
require.NoError(t, createRelease("mynginx", namespace, baseDir, true))
defer func() {
contract.IgnoreError(deleteRelease("mynginx", namespace))
}()
options := baseOptions.With(integration.ProgramTestOptions{
Dir: baseDir,
Config: map[string]string{
"namespace": namespace,
},
NoParallel: true,
Verbose: true,
})
integration.ProgramTest(t, &options)
})
Expand Down
3 changes: 3 additions & 0 deletions tests/sdk/go/helm-import-deployment/step1/Pulumi.yaml
@@ -0,0 +1,3 @@
name: go_helm_import_deployment
description: Test import support for Kubernetes resources deployed by a Helm chart externally.
runtime: go
140 changes: 140 additions & 0 deletions tests/sdk/go/helm-import-deployment/step1/main.go
@@ -0,0 +1,140 @@
package main

import (
"fmt"

appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)

func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
conf := config.New(ctx, "")
namespace := conf.Require("namespace")
_, err := appsv1.NewDeployment(
ctx,
"mynginx",
&appsv1.DeploymentArgs{
ApiVersion: pulumi.String("apps/v1"),
Kind: pulumi.String("Deployment"),
Metadata: &metav1.ObjectMetaArgs{
Annotations: pulumi.StringMap{
"meta.helm.sh/release-name": pulumi.String("mynginx"),
"meta.helm.sh/release-namespace": pulumi.String(namespace),
},
Labels: pulumi.StringMap{
"app.kubernetes.io/instance": pulumi.String("mynginx"),
"app.kubernetes.io/managed-by": pulumi.String("Helm"),
"app.kubernetes.io/name": pulumi.String("nginx"),
"helm.sh/chart": pulumi.String("nginx-6.0.5"),
},
Name: pulumi.String("mynginx"),
Namespace: pulumi.String(namespace),
},
Spec: &appsv1.DeploymentSpecArgs{
ProgressDeadlineSeconds: pulumi.Int(600),
Replicas: pulumi.Int(1),
RevisionHistoryLimit: pulumi.Int(10),
Selector: &metav1.LabelSelectorArgs{
MatchLabels: pulumi.StringMap{
"app.kubernetes.io/instance": pulumi.String("mynginx"),
"app.kubernetes.io/name": pulumi.String("nginx"),
},
},
Strategy: &appsv1.DeploymentStrategyArgs{
RollingUpdate: &appsv1.RollingUpdateDeploymentArgs{
MaxSurge: pulumi.String("25%"),
MaxUnavailable: pulumi.String("25%"),
},
Type: pulumi.String("RollingUpdate"),
},
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: pulumi.StringMap{
"app.kubernetes.io/instance": pulumi.String("mynginx"),
"app.kubernetes.io/managed-by": pulumi.String("Helm"),
"app.kubernetes.io/name": pulumi.String("nginx"),
"helm.sh/chart": pulumi.String("nginx-6.0.5"),
},
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
&corev1.ContainerArgs{
Image: pulumi.String("docker.io/bitnami/nginx:1.19.1-debian-10-r23"),
ImagePullPolicy: pulumi.String("IfNotPresent"),
LivenessProbe: &corev1.ProbeArgs{
FailureThreshold: pulumi.Int(6),
InitialDelaySeconds: pulumi.Int(30),
PeriodSeconds: pulumi.Int(10),
SuccessThreshold: pulumi.Int(1),
TcpSocket: &corev1.TCPSocketActionArgs{
Port: pulumi.String("http"),
},
TimeoutSeconds: pulumi.Int(5),
},
Name: pulumi.String("nginx"),
Ports: corev1.ContainerPortArray{
&corev1.ContainerPortArgs{
ContainerPort: pulumi.Int(8080),
Name: pulumi.String("http"),
Protocol: pulumi.String("TCP"),
},
},
ReadinessProbe: &corev1.ProbeArgs{
FailureThreshold: pulumi.Int(3),
InitialDelaySeconds: pulumi.Int(5),
PeriodSeconds: pulumi.Int(5),
SuccessThreshold: pulumi.Int(1),
TcpSocket: &corev1.TCPSocketActionArgs{
Port: pulumi.String("http"),
},
TimeoutSeconds: pulumi.Int(3),
},
Resources: corev1.ResourceRequirementsArgs{},
TerminationMessagePath: pulumi.String("/dev/termination-log"),
TerminationMessagePolicy: pulumi.String("File"),
VolumeMounts: corev1.VolumeMountArray{
&corev1.VolumeMountArgs{
MountPath: pulumi.String("/opt/bitnami/nginx/conf/server_blocks"),
Name: pulumi.String("nginx-server-block-paths"),
},
},
},
},
DnsPolicy: pulumi.String("ClusterFirst"),
RestartPolicy: pulumi.String("Always"),
SchedulerName: pulumi.String("default-scheduler"),
SecurityContext: corev1.PodSecurityContextArgs{},
TerminationGracePeriodSeconds: pulumi.Int(30),
Volumes: corev1.VolumeArray{
&corev1.VolumeArgs{
ConfigMap: &corev1.ConfigMapVolumeSourceArgs{
DefaultMode: pulumi.Int(420),
Items: corev1.KeyToPathArray{
&corev1.KeyToPathArgs{
Key: pulumi.String("server-blocks-paths.conf"),
Path: pulumi.String("server-blocks-paths.conf"),
},
},
Name: pulumi.String("mynginx-server-block"),
},
Name: pulumi.String("nginx-server-block-paths"),
},
},
},
},
},
},
pulumi.IgnoreChanges([]string{
`metadata.annotations["deployment.kubernetes.io/revision"]`,
`metadata.selfLink`}),
pulumi.Import(pulumi.ID(fmt.Sprintf("%s/mynginx", namespace))))
if err != nil {
return err
}
return nil
})
}
21 changes: 21 additions & 0 deletions tests/sdk/go/helm-import-deployment/step1/nginx/.helmignore
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
21 changes: 21 additions & 0 deletions tests/sdk/go/helm-import-deployment/step1/nginx/Chart.yaml
@@ -0,0 +1,21 @@
annotations:
category: Infrastructure
apiVersion: v1
appVersion: 1.19.1
description: Chart for the nginx server
engine: gotpl
home: http://www.nginx.org
icon: https://bitnami.com/assets/stacks/nginx/img/nginx-stack-220x234.png
keywords:
- nginx
- http
- web
- www
- reverse proxy
maintainers:
- email: containers@bitnami.com
name: Bitnami
name: nginx
sources:
- https://github.com/bitnami/bitnami-docker-nginx
version: 6.0.5