Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion cmd/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) {
subcmd = "template"

filter = func(s []byte) []byte {
return s
return stripOCIPullProgress(s)
}
}

Expand Down Expand Up @@ -494,6 +494,36 @@ func extractManifestFromHelmUpgradeDryRunOutput(s []byte, noHooks bool) []byte {
return r
}

// ociPullProgressRE matches Helm's OCI chart pull progress lines that Helm writes
// to stdout before the rendered manifests when the chart, or one of its subcharts,
// is pulled from an OCI registry.
//
// The lines reported in the wild are "Pulled: ..." and "Digest: ...";
// "Pulling: ..." is matched defensively too.
//
// These lines are emitted at the start of a line and are not valid Kubernetes
// manifests, so they are safe to strip. Top-level manifest keys are
// apiVersion/kind/metadata/spec and never "Pulled", "Digest" or "Pulling";
// any homonymous keys inside a manifest are indented and therefore not matched.
//
// See https://github.com/databus23/helm-diff/issues/1040
var ociPullProgressRE = regexp.MustCompile(`(?m)^(?:Pulled|Digest|Pulling):[^\n]*\n?`)

// stripOCIPullProgress removes Helm's OCI chart pull progress output that leaks
// into the rendered manifest buffer when the chart (or a subchart) is pulled
// from an OCI registry.
//
// Without this, the progress lines (e.g. "Pulled: ...", "Digest: ...") are
// parsed as a YAML document lacking a Kind and break the downstream three-way
// merge / kubeclient.Build():
//
// unable to decode "": Object 'Kind' is missing in '{"Digest":"...","Pulled":"..."}'
//
// See https://github.com/databus23/helm-diff/issues/1040
func stripOCIPullProgress(s []byte) []byte {
return ociPullProgressRE.ReplaceAll(s, []byte(""))
}

// serverSideFlags returns the --server-side flag(s) to forward to helm.
//
// The flag is Helm v4 only:
Expand Down
72 changes: 72 additions & 0 deletions cmd/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,78 @@ import (
"github.com/google/go-cmp/cmp"
)

func TestStripOCIPullProgress(t *testing.T) {
manifest := `---
# Source: karpenter/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: karpenter
namespace: karpenter
data:
Pulled: preserved
Digest: preserved
---
# Source: karpenter/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: karpenter
namespace: karpenter
`

cases := []struct {
name string
in string
want string
}{
{
name: "strips OCI pull progress prepended to manifests",
in: "Pulled: public.ecr.aws/karpenter/karpenter:1.9.0\n" +
"Digest: sha256:8e3952caafd208cb888fbf97467cd04a4a024a3fba64c84af73039040cc6a371\n" +
manifest,
want: manifest,
},
{
name: "strips Pulling/Pulled/Digest progress lines",
in: "Pulling: public.ecr.aws/karpenter/karpenter:1.9.0\n" +
"Pulled: public.ecr.aws/karpenter/karpenter:1.9.0\n" +
"Digest: sha256:abc123\n" +
manifest,
want: manifest,
},
{
name: "does not modify output without OCI progress",
in: manifest,
want: manifest,
},
{
name: "preserves indented Pulled/Digest keys inside manifests",
in: manifest,
want: manifest,
},
{
name: "strips OCI progress even with no trailing manifests",
in: "Pulled: registry.example.com/chart:1.0.0\nDigest: sha256:deadbeef\n",
want: "",
},
{
name: "returns empty for empty input",
in: "",
want: "",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := stripOCIPullProgress([]byte(tc.in))
if d := cmp.Diff(tc.want, string(got)); d != "" {
t.Errorf("unexpected diff: %s", d)
}
})
}
}

type dryRunFlagsConfig struct {
isHelmV4 bool
supportsDryRunLookup bool
Expand Down
3 changes: 2 additions & 1 deletion cmd/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,5 +250,6 @@ func (l *local) renderChart(chartPath string) ([]byte, error) {
helmBin = "helm"
}
cmd := exec.Command(helmBin, args...)
return outputWithRichError(cmd)
out, err := outputWithRichError(cmd)
return stripOCIPullProgress(out), err
}