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

Helm Release detect changes to local chart #2568

Merged
merged 9 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions provider/pkg/provider/helm_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package provider

import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -132,6 +134,8 @@ type Release struct {
// Manifest map[string]interface{} `json:"manifest,omitempty"`
// Names of resources created by the release grouped by "kind/version".
ResourceNames map[string][]string `json:"resourceNames,omitempty"`
// The checksum of the rendered template (to detect changes)
Checksum string `json:"checksum,omitempty"`
EronWright marked this conversation as resolved.
Show resolved Hide resolved
// Status of the deployed release.
Status *ReleaseStatus `json:"status,omitempty"`
}
Expand Down Expand Up @@ -353,19 +357,23 @@ func (r *helmReleaseProvider) Check(ctx context.Context, req *pulumirpc.CheckReq
return nil, err
}

resourceNames, err := r.computeResourceNames(new, r.clientSet)
resourceInfo, err := r.computeResourceInfo(new, r.clientSet)
if err != nil && errors.Is(err, fs.ErrNotExist) {
// Likely because the chart is not readily available (e.g. import of chart where no repo info is stored).
// Declare bankruptcy in being able to determine the underlying resources and hope for the best
// further down the operations.
resourceNames = nil
resourceInfo = nil
} else if err != nil {
return nil, err
}

if len(new.ResourceNames) == 0 {
new.ResourceNames = resourceNames
if resourceInfo != nil {
EronWright marked this conversation as resolved.
Show resolved Hide resolved
new.Checksum = resourceInfo.checksum
if len(new.ResourceNames) == 0 {
new.ResourceNames = resourceInfo.resourceNames
}
}

logger.V(9).Infof("New: %+v", new)
news = resource.NewPropertyMap(new)
}
Expand Down Expand Up @@ -674,6 +682,15 @@ func (r *helmReleaseProvider) Diff(ctx context.Context, req *pulumirpc.DiffReque

// Extract old inputs from the `__inputs` field of the old state.
oldInputs, _ := parseCheckpointRelease(olds)

// if the checkpointed inputs have no checksum, then assume no change since checksums were introduced,
// to avoid making a spurious release.
checksum, ok := oldInputs["checksum"]
if !ok || (checksum.IsString() && checksum.StringValue() == "") {
oldInputs["checksum"] = news["checksum"]
}
EronWright marked this conversation as resolved.
Show resolved Hide resolved

// Calculate the diff between the old and new inputs
diff := oldInputs.Diff(news)
if diff == nil {
logger.V(9).Infof("No diff found for %q", req.GetUrn())
Expand Down Expand Up @@ -1089,8 +1106,14 @@ func (r *helmReleaseProvider) Delete(ctx context.Context, req *pulumirpc.DeleteR
return &pbempty.Empty{}, nil
}

func (r *helmReleaseProvider) computeResourceNames(rel *Release, clientSet *clients.DynamicClientSet) (map[string][]string, error) {
logger.V(9).Infof("Looking up resource names for release: %q: %#v", rel.Name, rel)
type resourceInfo struct {
resourceNames map[string][]string
checksum string
}

func (r *helmReleaseProvider) computeResourceInfo(rel *Release, clientSet *clients.DynamicClientSet) (*resourceInfo, error) {
logger.V(9).Infof("Computing resource info for release: %q: %#v", rel.Name, rel)
result := &resourceInfo{}
helmChartOpts := r.chartOptsFromRelease(rel)

logger.V(9).Infof("About to template: %+v", helmChartOpts)
Expand All @@ -1099,12 +1122,22 @@ func (r *helmReleaseProvider) computeResourceNames(rel *Release, clientSet *clie
return nil, err
}

// compute the resource names
_, resourceNames, err := convertYAMLManifestToJSON(templ)
if err != nil {
return nil, err
}
result.resourceNames = resourceNames

// compute the checksum of the rendered output, to be able to detect changes
h := sha256.New()
EronWright marked this conversation as resolved.
Show resolved Hide resolved
_, err = h.Write([]byte(templ))
if err != nil {
return nil, err
}
result.checksum = hex.EncodeToString(h.Sum(nil))

return resourceNames, nil
return result, nil
}

func (r *helmReleaseProvider) chartOptsFromRelease(rel *Release) HelmChartOpts {
Expand Down
21 changes: 21 additions & 0 deletions tests/sdk/go/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,31 @@ func TestGo(t *testing.T) {
})

t.Run("Helm Release Local", func(t *testing.T) {
validateReplicas := func(t *testing.T, stack integration.RuntimeValidationStackInfo, expected float64) {
actual, ok := stack.Outputs["replicas"].(float64)
if !ok {
t.Fatalf("expected a replicas output")
}
assert.Equal(t, expected, actual, "expected replicas to be %d", expected)
}

options := baseOptions.With(integration.ProgramTestOptions{
Dir: filepath.Join(cwd, "helm-release-local", "step1"),
Quick: true,
ExpectRefreshChanges: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
validateReplicas(t, stack, 1)
},
EditDirs: []integration.EditDir{
{
Dir: filepath.Join("helm-release-local", "step2"),
Additive: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
validateReplicas(t, stack, 2)
},
ExpectFailure: false,
},
},
})
integration.ProgramTest(t, &options)
})
Expand Down
2 changes: 1 addition & 1 deletion tests/sdk/go/helm-release-local/step1/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name: go_helm_release
description: Test Kubernetes Helm Release resource with remote Chart.
description: Test Kubernetes Helm Release resource with local Chart.
runtime: go
15 changes: 15 additions & 0 deletions tests/sdk/go/helm-release-local/step1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"

appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
Expand All @@ -24,6 +25,20 @@ func main() {
if err != nil {
return err
}

replicas := pulumi.All(rel.Status.Namespace(), rel.Status.Name()).
ApplyT(func(r any) (any, error) {
arr := r.([]any)
namespace := arr[0].(*string)
name := arr[1].(*string)
svc, err := appsv1.GetDeployment(ctx, "deployment", pulumi.ID(fmt.Sprintf("%s/%s-nginx", *namespace, *name)), nil)
if err != nil {
return "", nil
}
return svc.Spec.Replicas(), nil
})
ctx.Export("replicas", replicas)

svc := pulumi.All(rel.Status.Namespace(), rel.Status.Name()).
ApplyT(func(r any) (any, error) {
arr := r.([]any)
Expand Down
Loading
Loading