pull/11273/merge
Filip Krakowski 8 months ago committed by GitHub
commit a7d0e7c76d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,6 +21,7 @@ import (
"io" "io"
"path/filepath" "path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v4/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
@ -54,6 +55,7 @@ type createOptions struct {
starter string // --starter starter string // --starter
name string name string
starterDir string starterDir string
noOverride bool
} }
func newCreateCmd(out io.Writer) *cobra.Command { func newCreateCmd(out io.Writer) *cobra.Command {
@ -73,6 +75,13 @@ func newCreateCmd(out io.Writer) *cobra.Command {
// No more completions, so disable file completion // No more completions, so disable file completion
return noMoreArgsComp() return noMoreArgsComp()
}, },
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(o.starter) == 0 && o.noOverride {
return errors.New("the -o/--no-override flag can only be specified when using a starter")
}
return nil
},
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.starterDir = helmpath.DataPath("starters") o.starterDir = helmpath.DataPath("starters")
@ -81,6 +90,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
} }
cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold") cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold")
cmd.Flags().BoolVarP(&o.noOverride, "no-override", "o", false, "if specified, does not override the starter's Chart.yaml")
return cmd return cmd
} }
@ -97,6 +107,10 @@ func (o *createOptions) run(out io.Writer) error {
APIVersion: chart.APIVersionV2, APIVersion: chart.APIVersionV2,
} }
if o.noOverride {
cfile = nil
}
if o.starter != "" { if o.starter != "" {
// Create from the starter // Create from the starter
lstarter := filepath.Join(o.starterDir, o.starter) lstarter := filepath.Join(o.starterDir, o.starter)
@ -104,7 +118,7 @@ func (o *createOptions) run(out io.Writer) error {
if filepath.IsAbs(o.starter) { if filepath.IsAbs(o.starter) {
lstarter = o.starter lstarter = o.starter
} }
return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter) return chartutil.CreateFromWithOverride(chartname, filepath.Dir(chartname), lstarter, cfile)
} }
chartutil.Stderr = out chartutil.Stderr = out

@ -25,6 +25,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
@ -555,13 +556,24 @@ spec:
var Stderr io.Writer = os.Stderr var Stderr io.Writer = os.Stderr
// CreateFrom creates a new chart, but scaffolds it from the src chart. // CreateFrom creates a new chart, but scaffolds it from the src chart.
// Deprecated: Use CreateFromWithOverride
// TODO Helm 4: Fold CreateFromWithOverride back into CreateFrom
func CreateFrom(chartfile *chart.Metadata, dest, src string) error { func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
return CreateFromWithOverride(chartfile.Name, dest, src, chartfile)
}
// CreateFromWithOverride creates a new chart, but scaffolds it from the src chart and
// provides the option to override the chart's metadata (Chart.yaml).
func CreateFromWithOverride(name, dest, src string, override *chart.Metadata) error {
schart, err := loader.Load(src) schart, err := loader.Load(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not load %s", src) return errors.Wrapf(err, "could not load %s", src)
} }
schart.Metadata = chartfile // Override the chart's metadata if the user requested it
if err := setMetadata(schart, name, override); err != nil {
return errors.Wrap(err, "setting metadata")
}
var updatedTemplates []*chart.File var updatedTemplates []*chart.File
@ -582,12 +594,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
} }
schart.Values = m schart.Values = m
// SaveDir looks for the file values.yaml when saving rather than the values // SaveDir looks for the files values.yaml and Chart.yaml when saving rather than the values
// key in order to preserve the comments in the YAML. The name placeholder // and metadata keys in order to preserve the comments in the YAML. The name placeholder
// needs to be replaced on that file. // needs to be replaced on these files.
for _, f := range schart.Raw { for _, f := range schart.Raw {
if f.Name == ValuesfileName { if slices.Contains([]string{ValuesfileName, ChartfileName}, f.Name) {
f.Data = transform(string(f.Data), schart.Name()) f.Data = transform(string(f.Data), name)
} }
} }
@ -733,3 +745,39 @@ func validateChartName(name string) error {
} }
return nil return nil
} }
func setMetadata(schart *chart.Chart, name string, override *chart.Metadata) error {
// Override the source chart's metadata and raw chart file content
// if the user provided a chart.Metadata struct
if override != nil {
schart.Metadata = override
for _, f := range schart.Raw {
if f.Name == ChartfileName {
metadataBytes, err := yaml.Marshal(schart.Metadata)
if err != nil {
return errors.Wrap(err, "marshalling metadata override")
}
f.Data = metadataBytes
break
}
}
return nil
}
// Unmarshal Chart.yaml from source directory while replacing
// all <CHARTNAME> placeholders if no override was requested
for _, f := range schart.Raw {
if f.Name == ChartfileName {
var m chart.Metadata
if err := yaml.Unmarshal(transform(string(f.Data), name), &m); err != nil {
return errors.Wrap(err, "transforming charts file")
}
schart.Metadata = &m
break
}
}
return nil
}

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
@ -72,6 +73,8 @@ func TestCreateFrom(t *testing.T) {
Name: "foo", Name: "foo",
Version: "0.1.0", Version: "0.1.0",
} }
chartname := "foo"
srcdir := "./testdata/frobnitz/charts/mariner" srcdir := "./testdata/frobnitz/charts/mariner"
if err := CreateFrom(cf, tdir, srcdir); err != nil { if err := CreateFrom(cf, tdir, srcdir); err != nil {
@ -79,7 +82,7 @@ func TestCreateFrom(t *testing.T) {
} }
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name) c := filepath.Join(tdir, chartname)
mychart, err := loader.LoadDir(c) mychart, err := loader.LoadDir(c)
if err != nil { if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", c, err) t.Fatalf("Failed to load newly created chart %q: %s", c, err)
@ -109,6 +112,58 @@ func TestCreateFrom(t *testing.T) {
} }
} }
func TestCreateFromWithOverride(t *testing.T) {
tdir := t.TempDir()
chartname := "foo"
srcdir := "./testdata/frobnitz/charts/custom"
srcMetadata := filepath.Join(srcdir, ChartfileName)
rawMetadata, err := ioutil.ReadFile(srcMetadata)
if err != nil {
t.Errorf("Unable to read file %s: %s", srcMetadata, err)
}
if err := CreateFromWithOverride(chartname, tdir, srcdir, nil); err != nil {
t.Fatal(err)
}
dir := filepath.Join(tdir, chartname)
mychart, err := loader.LoadDir(dir)
if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", dir, err)
}
if mychart.Name() != "foo" {
t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
}
expectedMetadata := strings.ReplaceAll(string(rawMetadata), "<CHARTNAME>", chartname)
for _, f := range []string{
ChartfileName,
ValuesfileName,
filepath.Join(TemplatesDir, "deployment.yaml"),
} {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err)
}
// Check each file to make sure <CHARTNAME> has been replaced
b, err := ioutil.ReadFile(filepath.Join(dir, f))
if err != nil {
t.Errorf("Unable to read file %s: %s", f, err)
}
if bytes.Contains(b, []byte("<CHARTNAME>")) {
t.Errorf("File %s contains <CHARTNAME>", f)
}
if f == ChartfileName {
if string(b) != expectedMetadata {
t.Errorf("%s does not contain expected content", ChartfileName)
}
}
}
}
// TestCreate_Overwrite is a regression test for making sure that files are overwritten. // TestCreate_Overwrite is a regression test for making sure that files are overwritten.
func TestCreate_Overwrite(t *testing.T) { func TestCreate_Overwrite(t *testing.T) {
tdir := t.TempDir() tdir := t.TempDir()

@ -26,6 +26,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
@ -51,15 +52,29 @@ func SaveDir(c *chart.Chart, dest string) error {
return err return err
} }
// Save the chart file. // Check if Chart has raw metadata stored
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil { hasRawMetadata := false
return err for _, f := range c.Raw {
if f.Name == ChartfileName {
hasRawMetadata = true
break
}
} }
// Save values.yaml // This ensures that the Chart always has raw metadata stored, since some tests
// call SaveDir without adding raw metadata to the Chart.
if !hasRawMetadata {
b, err := yaml.Marshal(c.Metadata)
if err != nil {
return errors.Wrap(err, "reading charts file")
}
c.Raw = append(c.Raw, &chart.File{Name: ChartfileName, Data: b})
}
// Save values.yaml and Chart.yaml
for _, f := range c.Raw { for _, f := range c.Raw {
if f.Name == ValuesfileName { if slices.Contains([]string{ValuesfileName, ChartfileName}, f.Name) {
vf := filepath.Join(outdir, ValuesfileName) vf := filepath.Join(outdir, f.Name)
if err := writeFile(vf, f.Data); err != nil { if err := writeFile(vf, f.Data); err != nil {
return err return err
} }

@ -0,0 +1,29 @@
apiVersion: v2
name: <CHARTNAME>
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
dependencies:
- name: albatross
repository: https://example.com/mariner/charts
version: "0.1.0"

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP

@ -0,0 +1,22 @@
# Default values for <CHARTNAME>.
# This is a YAML-formatted file. https://github.com/toml-lang/toml
# Declare name/value pairs to be passed into your templates.
# name: "value"
<CHARTNAME>:
test: true
replicaCount: 1
image:
repository: ""
pullPolicy: IfNotPresent
tag: latest
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 80
Loading…
Cancel
Save