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"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v4/cmd/helm/require"
@ -54,6 +55,7 @@ type createOptions struct {
starter string // --starter
name string
starterDir string
noOverride bool
}
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
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 {
o.name = args[0]
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().BoolVarP(&o.noOverride, "no-override", "o", false, "if specified, does not override the starter's Chart.yaml")
return cmd
}
@ -97,6 +107,10 @@ func (o *createOptions) run(out io.Writer) error {
APIVersion: chart.APIVersionV2,
}
if o.noOverride {
cfile = nil
}
if o.starter != "" {
// Create from the 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) {
lstarter = o.starter
}
return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter)
return chartutil.CreateFromWithOverride(chartname, filepath.Dir(chartname), lstarter, cfile)
}
chartutil.Stderr = out

@ -25,6 +25,7 @@ import (
"strings"
"github.com/pkg/errors"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart"
@ -555,13 +556,24 @@ spec:
var Stderr io.Writer = os.Stderr
// 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 {
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)
if err != nil {
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
@ -582,12 +594,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
}
schart.Values = m
// SaveDir looks for the file values.yaml when saving rather than the values
// key in order to preserve the comments in the YAML. The name placeholder
// needs to be replaced on that file.
// SaveDir looks for the files values.yaml and Chart.yaml when saving rather than the values
// and metadata keys in order to preserve the comments in the YAML. The name placeholder
// needs to be replaced on these files.
for _, f := range schart.Raw {
if f.Name == ValuesfileName {
f.Data = transform(string(f.Data), schart.Name())
if slices.Contains([]string{ValuesfileName, ChartfileName}, f.Name) {
f.Data = transform(string(f.Data), name)
}
}
@ -733,3 +745,39 @@ func validateChartName(name string) error {
}
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"
"os"
"path/filepath"
"strings"
"testing"
"helm.sh/helm/v4/pkg/chart"
@ -72,6 +73,8 @@ func TestCreateFrom(t *testing.T) {
Name: "foo",
Version: "0.1.0",
}
chartname := "foo"
srcdir := "./testdata/frobnitz/charts/mariner"
if err := CreateFrom(cf, tdir, srcdir); err != nil {
@ -79,7 +82,7 @@ func TestCreateFrom(t *testing.T) {
}
dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name)
c := filepath.Join(tdir, chartname)
mychart, err := loader.LoadDir(c)
if err != nil {
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.
func TestCreate_Overwrite(t *testing.T) {
tdir := t.TempDir()

@ -26,6 +26,7 @@ import (
"time"
"github.com/pkg/errors"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart"
@ -51,15 +52,29 @@ func SaveDir(c *chart.Chart, dest string) error {
return err
}
// Save the chart file.
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
return err
// Check if Chart has raw metadata stored
hasRawMetadata := false
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 {
if f.Name == ValuesfileName {
vf := filepath.Join(outdir, ValuesfileName)
if slices.Contains([]string{ValuesfileName, ChartfileName}, f.Name) {
vf := filepath.Join(outdir, f.Name)
if err := writeFile(vf, f.Data); err != nil {
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