Merge branch 'dev-v3' of github.com:helm/helm into HEAD

pull/5597/head
Josh Dolitsky 7 years ago
commit a138699686

25
Gopkg.lock generated

@ -929,6 +929,30 @@
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0" version = "v1.3.0"
[[projects]]
branch = "master"
digest = "1:f4e5276a3b356f4692107047fd2890f2fe534f4feeb6b1fd2f6dfbd87f1ccf54"
name = "github.com/xeipuuv/gojsonpointer"
packages = ["."]
pruneopts = "UT"
revision = "4e3ac2762d5f479393488629ee9370b50873b3a6"
[[projects]]
branch = "master"
digest = "1:dc6a6c28ca45d38cfce9f7cb61681ee38c5b99ec1425339bfc1e1a7ba769c807"
name = "github.com/xeipuuv/gojsonreference"
packages = ["."]
pruneopts = "UT"
revision = "bd5ef7bd5415a7ac448318e64f11a24cd21e594b"
[[projects]]
digest = "1:1c898ea6c30c16e8d55fdb6fe44c4bee5f9b7d68aa260cfdfc3024491dcc7bea"
name = "github.com/xeipuuv/gojsonschema"
packages = ["."]
pruneopts = "UT"
revision = "f971f3cd73b2899de6923801c147f075263e0c50"
version = "v1.1.0"
[[projects]] [[projects]]
digest = "1:340553b2fdaab7d53e63fd40f8ed82203bdd3274253055bdb80a46828482ef81" digest = "1:340553b2fdaab7d53e63fd40f8ed82203bdd3274253055bdb80a46828482ef81"
name = "github.com/xenolf/lego" name = "github.com/xenolf/lego"
@ -1777,6 +1801,7 @@
"github.com/spf13/pflag", "github.com/spf13/pflag",
"github.com/stretchr/testify/assert", "github.com/stretchr/testify/assert",
"github.com/stretchr/testify/suite", "github.com/stretchr/testify/suite",
"github.com/xeipuuv/gojsonschema",
"golang.org/x/crypto/bcrypt", "golang.org/x/crypto/bcrypt",
"golang.org/x/crypto/openpgp", "golang.org/x/crypto/openpgp",
"golang.org/x/crypto/openpgp/clearsign", "golang.org/x/crypto/openpgp/clearsign",

@ -102,3 +102,6 @@
go-tests = true go-tests = true
unused-packages = true unused-packages = true
[[constraint]]
name = "github.com/xeipuuv/gojsonschema"
version = "1.1.0"

@ -80,8 +80,8 @@ func (o *createOptions) run(out io.Writer) error {
Description: "A Helm chart for Kubernetes", Description: "A Helm chart for Kubernetes",
Type: "application", Type: "application",
Version: "0.1.0", Version: "0.1.0",
AppVersion: "1.0", AppVersion: "0.1.0",
APIVersion: chart.APIVersionv1, APIVersion: chart.APIVersionV1,
} }
if o.starter != "" { if o.starter != "" {
@ -90,6 +90,6 @@ func (o *createOptions) run(out io.Writer) error {
return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter) return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter)
} }
_, err := chartutil.Create(cfile, filepath.Dir(o.name)) _, err := chartutil.Create(chartname, filepath.Dir(o.name))
return err return err
} }

@ -55,7 +55,7 @@ func TestCreateCmd(t *testing.T) {
if c.Name() != cname { if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name()) t.Errorf("Expected %q name, got %q", cname, c.Name())
} }
if c.Metadata.APIVersion != chart.APIVersionv1 { if c.Metadata.APIVersion != chart.APIVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
} }
} }
@ -73,7 +73,7 @@ func TestCreateStarterCmd(t *testing.T) {
// Create a starter. // Create a starter.
starterchart := hh.Starters() starterchart := hh.Starters()
os.Mkdir(starterchart, 0755) os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create(&chart.Metadata{Name: "starterchart"}, starterchart); err != nil { if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err) t.Fatalf("Could not create chart: %s", err)
} else { } else {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
@ -106,7 +106,7 @@ func TestCreateStarterCmd(t *testing.T) {
if c.Name() != cname { if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name()) t.Errorf("Expected %q name, got %q", cname, c.Name())
} }
if c.Metadata.APIVersion != chart.APIVersionv1 { if c.Metadata.APIVersion != chart.APIVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
} }

@ -46,8 +46,9 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
chartname := "depup" chartname := "depup"
md := createTestingMetadata(chartname, srv.URL()) ch := createTestingMetadata(chartname, srv.URL())
if _, err := chartutil.Create(md, hh.String()); err != nil { md := ch.Metadata
if err := chartutil.SaveDir(ch, hh.String()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -203,13 +204,16 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// createTestingMetadata creates a basic chart that depends on reqtest-0.1.0 // createTestingMetadata creates a basic chart that depends on reqtest-0.1.0
// //
// The baseURL can be used to point to a particular repository server. // The baseURL can be used to point to a particular repository server.
func createTestingMetadata(name, baseURL string) *chart.Metadata { func createTestingMetadata(name, baseURL string) *chart.Chart {
return &chart.Metadata{ return &chart.Chart{
Name: name, Metadata: &chart.Metadata{
Version: "1.2.3", APIVersion: chart.APIVersionV1,
Dependencies: []*chart.Dependency{ Name: name,
{Name: "reqtest", Version: "0.1.0", Repository: baseURL}, Version: "1.2.3",
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL}, Dependencies: []*chart.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: baseURL},
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL},
},
}, },
} }
} }
@ -219,6 +223,5 @@ func createTestingMetadata(name, baseURL string) *chart.Metadata {
// The baseURL can be used to point to a particular repository server. // The baseURL can be used to point to a particular repository server.
func createTestingChart(dest, name, baseURL string) error { func createTestingChart(dest, name, baseURL string) error {
cfile := createTestingMetadata(name, baseURL) cfile := createTestingMetadata(name, baseURL)
_, err := chartutil.Create(cfile, dest) return chartutil.SaveDir(cfile, dest)
return err
} }

@ -35,11 +35,11 @@ configures the maximum length of the revision list returned.
The historical release set is printed as a formatted table, e.g: The historical release set is printed as a formatted table, e.g:
$ helm history angry-bird --max=4 $ helm history angry-bird --max=4
REVISION UPDATED STATUS CHART DESCRIPTION REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Initial install 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install
2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Upgraded successfully 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully
3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Rolled back to 2 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2
4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 Upgraded successfully 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully
` `
func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {

@ -113,7 +113,7 @@ func TestInstall(t *testing.T) {
}, },
// Install, chart with bad dependencies in Chart.yaml in /charts // Install, chart with bad dependencies in Chart.yaml in /charts
{ {
name: "install chart with bad dependencies in Chart.yaml", name: "install chart with bad dependencies in Chart.yaml",
cmd: "install badreq testdata/testcharts/chart-bad-requirements", cmd: "install badreq testdata/testcharts/chart-bad-requirements",
wantError: true, wantError: true,
}, },
@ -136,6 +136,53 @@ func TestInstall(t *testing.T) {
wantError: true, wantError: true,
golden: "output/install-chart-bad-type.txt", golden: "output/install-chart-bad-type.txt",
}, },
// Install, values from yaml, schematized
{
name: "install with schema file",
cmd: "install schema testdata/testcharts/chart-with-schema",
golden: "output/schema.txt",
},
// Install, values from yaml, schematized with errors
{
name: "install with schema file, with errors",
cmd: "install schema testdata/testcharts/chart-with-schema-negative",
wantError: true,
golden: "output/schema-negative.txt",
},
// Install, values from yaml, extra values from yaml, schematized with errors
{
name: "install with schema file, extra values from yaml, with errors",
cmd: "install schema testdata/testcharts/chart-with-schema -f testdata/testcharts/chart-with-schema/extra-values.yaml",
wantError: true,
golden: "output/schema-negative.txt",
},
// Install, values from yaml, extra values from cli, schematized with errors
{
name: "install with schema file, extra values from cli, with errors",
cmd: "install schema testdata/testcharts/chart-with-schema --set age=-5",
wantError: true,
golden: "output/schema-negative-cli.txt",
},
// Install with subchart, values from yaml, schematized with errors
{
name: "install with schema file and schematized subchart, with errors",
cmd: "install schema testdata/testcharts/chart-with-schema-and-subchart",
wantError: true,
golden: "output/subchart-schema-negative.txt",
},
// Install with subchart, values from yaml, extra values from cli, schematized with errors
{
name: "install with schema file and schematized subchart, extra values from cli",
cmd: "install schema testdata/testcharts/chart-with-schema-and-subchart --set lastname=doe --set subchart-with-schema.age=25",
golden: "output/subchart-schema-cli.txt",
},
// Install with subchart, values from yaml, extra values from cli, schematized with errors
{
name: "install with schema file and schematized subchart, extra values from cli, with errors",
cmd: "install schema testdata/testcharts/chart-with-schema-and-subchart --set lastname=doe --set subchart-with-schema.age=-25",
wantError: true,
golden: "output/subchart-schema-cli-negative.txt",
},
} }
runTestActionCmd(t, tests) runTestActionCmd(t, tests)

@ -17,68 +17,29 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/release"
) )
const releaseTestDesc = ` const releaseTestHelp = `
The test command runs the tests for a release. The test command consists of multiple subcommands around running tests on a release.
Example usage:
$ helm test run [RELEASE]
The argument this command takes is the name of a deployed release.
The tests to be run are defined in the chart that was installed.
` `
func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewReleaseTesting(cfg)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test [RELEASE]", Use: "test",
Short: "test a release", Short: "test a release or cleanup test artifacts",
Long: releaseTestDesc, Long: releaseTestHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, errc := client.Run(args[0])
testErr := &testErr{}
for {
select {
case err := <-errc:
if err == nil && testErr.failed > 0 {
return testErr.Error()
}
return err
case res, ok := <-c:
if !ok {
break
}
if res.Status == release.TestRunFailure {
testErr.failed++
}
fmt.Fprintf(out, res.Msg+"\n")
}
}
},
} }
cmd.AddCommand(
f := cmd.Flags() newReleaseTestRunCmd(cfg, out),
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") )
f.BoolVar(&client.Cleanup, "cleanup", false, "delete test pods upon completion")
return cmd return cmd
} }
type testErr struct {
failed int
}
func (err *testErr) Error() error {
return errors.Errorf("%v test(s) failed", err.failed)
}

@ -0,0 +1,83 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/release"
)
const releaseTestRunHelp = `
The test command runs the tests for a release.
The argument this command takes is the name of a deployed release.
The tests to be run are defined in the chart that was installed.
`
func newReleaseTestRunCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewReleaseTesting(cfg)
cmd := &cobra.Command{
Use: "run [RELEASE]",
Short: "run tests for a release",
Long: releaseTestRunHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, errc := client.Run(args[0])
testErr := &testErr{}
for {
select {
case err := <-errc:
if err != nil && testErr.failed > 0 {
return testErr.Error()
}
return err
case res, ok := <-c:
if !ok {
break
}
if res.Status == release.TestRunFailure {
testErr.failed++
}
fmt.Fprintf(out, res.Msg+"\n")
}
}
},
}
f := cmd.Flags()
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Cleanup, "cleanup", false, "delete test pods upon completion")
return cmd
}
type testErr struct {
failed int
}
func (err *testErr) Error() error {
return errors.Errorf("%v test(s) failed", err.failed)
}

@ -1,96 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
"time"
"helm.sh/helm/pkg/release"
)
func TestReleaseTesting(t *testing.T) {
timestamp := time.Unix(1452902400, 0).UTC()
tests := []cmdTestCase{{
name: "successful test",
cmd: "status test-success",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-success",
TestSuiteResults: []*release.TestRun{
{
Name: "test-success",
Status: release.TestRunSuccess,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-success.txt",
}, {
name: "test failure",
cmd: "status test-failure",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-failure",
TestSuiteResults: []*release.TestRun{
{
Name: "test-failure",
Status: release.TestRunFailure,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-failure.txt",
}, {
name: "test unknown",
cmd: "status test-unknown",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-unknown",
TestSuiteResults: []*release.TestRun{
{
Name: "test-unknown",
Status: release.TestRunUnknown,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-unknown.txt",
}, {
name: "test running",
cmd: "status test-running",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-running",
TestSuiteResults: []*release.TestRun{
{
Name: "test-running",
Status: release.TestRunRunning,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-running.txt",
}, {
name: "test with no tests",
cmd: "test no-tests",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "no-tests"})},
golden: "output/test-no-tests.txt",
}}
runTestCmd(t, tests)
}

@ -1,3 +1,3 @@
REVISION UPDATED STATUS CHART DESCRIPTION REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock 3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock
4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 Release mock 4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 1.0 Release mock

@ -1 +1 @@
[{"revision":3,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"superseded","chart":"foo-0.1.0-beta.1","description":"Release mock"},{"revision":4,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"deployed","chart":"foo-0.1.0-beta.1","description":"Release mock"}] [{"revision":3,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"superseded","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Release mock"},{"revision":4,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"deployed","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Release mock"}]

@ -1,5 +1,5 @@
REVISION UPDATED STATUS CHART DESCRIPTION REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock 1 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock
2 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock 2 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock
3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock 3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock
4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 Release mock 4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 1.0 Release mock

@ -1,9 +1,11 @@
- chart: foo-0.1.0-beta.1 - app_version: "1.0"
chart: foo-0.1.0-beta.1
description: Release mock description: Release mock
revision: 3 revision: 3
status: superseded status: superseded
updated: 1977-09-02 22:04:05 +0000 UTC updated: 1977-09-02 22:04:05 +0000 UTC
- chart: foo-0.1.0-beta.1 - app_version: "1.0"
chart: foo-0.1.0-beta.1
description: Release mock description: Release mock
revision: 4 revision: 4
status: deployed status: deployed

@ -0,0 +1,4 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
empty:
- age: Must be greater than or equal to 0/1

@ -0,0 +1,5 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
empty:
- (root): employmentInfo is required
- age: Must be greater than or equal to 0/1

@ -0,0 +1,5 @@
NAME: schema
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -0,0 +1,4 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
subchart-with-schema:
- age: Must be greater than or equal to 0/1

@ -0,0 +1,5 @@
NAME: schema
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -0,0 +1,6 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
chart-without-schema:
- (root): lastname is required
subchart-with-schema:
- (root): age is required

@ -0,0 +1,2 @@
release "aeneas" uninstalled
release "aeneas2" uninstalled

@ -1 +1 @@
Error: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 5: did not find expected '-' indicator Error: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=crazy-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=zany-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=crazy-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -4,8 +4,3 @@ LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,3 +1,4 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm home: https://helm.sh/helm
name: alpine name: alpine

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: chart-missing-deps name: chart-missing-deps
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: reqsubchart name: reqsubchart
version: 0.1.0 version: 0.1.0

@ -1,7 +1,8 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm home: https://helm.sh/helm
name: chart-bad-type name: chart-bad-type
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
version: 0.1.0 version: 0.1.0
type: foobar type: foobar

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: chart-missing-deps name: chart-missing-deps
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: reqsubchart name: reqsubchart
version: 0.1.0 version: 0.1.0

@ -0,0 +1,6 @@
apiVersion: v1
name: chart-without-schema
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: 0.1.0

@ -0,0 +1,6 @@
apiVersion: v1
name: subchart-with-schema
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: 0.1.0

@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Values",
"type": "object",
"properties": {
"age": {
"description": "Age",
"minimum": 0,
"type": "integer"
}
},
"required": [
"age"
]
}

@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Values",
"type": "object",
"properties": {
"firstname": {
"description": "First name",
"type": "string"
},
"lastname": {
"type": "string"
}
},
"required": [
"firstname",
"lastname"
]
}

@ -0,0 +1,7 @@
apiVersion: v1
description: Empty testing chart
home: https://k8s.io/helm
name: empty
sources:
- https://github.com/kubernetes/helm
version: 0.1.0

@ -0,0 +1,67 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"addresses": {
"description": "List of addresses",
"items": {
"properties": {
"city": {
"type": "string"
},
"number": {
"type": "number"
},
"street": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"age": {
"description": "Age",
"minimum": 0,
"type": "integer"
},
"employmentInfo": {
"properties": {
"salary": {
"minimum": 0,
"type": "number"
},
"title": {
"type": "string"
}
},
"required": [
"salary"
],
"type": "object"
},
"firstname": {
"description": "First name",
"type": "string"
},
"lastname": {
"type": "string"
},
"likesCoffee": {
"type": "boolean"
},
"phoneNumbers": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"firstname",
"lastname",
"addresses",
"employmentInfo"
],
"title": "Values",
"type": "object"
}

@ -0,0 +1,14 @@
firstname: John
lastname: Doe
age: -5
likesCoffee: true
addresses:
- city: Springfield
street: Main
number: 12345
- city: New York
street: Broadway
number: 67890
phoneNumbers:
- "(888) 888-8888"
- "(555) 555-5555"

@ -0,0 +1,7 @@
apiVersion: v1
description: Empty testing chart
home: https://k8s.io/helm
name: empty
sources:
- https://github.com/kubernetes/helm
version: 0.1.0

@ -0,0 +1,2 @@
age: -5
employmentInfo: null

@ -0,0 +1 @@
# This file is intentionally blank

@ -0,0 +1,67 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"addresses": {
"description": "List of addresses",
"items": {
"properties": {
"city": {
"type": "string"
},
"number": {
"type": "number"
},
"street": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"age": {
"description": "Age",
"minimum": 0,
"type": "integer"
},
"employmentInfo": {
"properties": {
"salary": {
"minimum": 0,
"type": "number"
},
"title": {
"type": "string"
}
},
"required": [
"salary"
],
"type": "object"
},
"firstname": {
"description": "First name",
"type": "string"
},
"lastname": {
"type": "string"
},
"likesCoffee": {
"type": "boolean"
},
"phoneNumbers": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"firstname",
"lastname",
"addresses",
"employmentInfo"
],
"title": "Values",
"type": "object"
}

@ -0,0 +1,17 @@
firstname: John
lastname: Doe
age: 25
likesCoffee: true
employmentInfo:
title: Software Developer
salary: 100000
addresses:
- city: Springfield
street: Main
number: 12345
- city: New York
street: Broadway
number: 67890
phoneNumbers:
- "(888) 888-8888"
- "(555) 555-5555"

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: decompressedchart name: decompressedchart
version: 0.1.0 version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Empty testing chart description: Empty testing chart
home: https://helm.sh/helm home: https://helm.sh/helm
name: empty name: empty
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
version: 0.1.0 version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm home: https://helm.sh/helm
name: alpine name: alpine
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
version: 0.1.0 version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm home: https://helm.sh/helm
name: novals name: novals
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
version: 0.2.0 version: 0.2.0

Binary file not shown.

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: reqtest name: reqtest
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: reqsubchart name: reqsubchart
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: reqsubchart2 name: reqsubchart2
version: 0.2.0 version: 0.2.0

Binary file not shown.

@ -1,20 +1,21 @@
-----BEGIN PGP SIGNED MESSAGE----- -----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512 Hash: SHA512
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: signtest name: signtest
version: 0.1.0 version: 0.1.0
... ...
files: files:
signtest-0.1.0.tgz: sha256:dee72947753628425b82814516bdaa37aef49f25e8820dd2a6e15a33a007823b signtest-0.1.0.tgz: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55
-----BEGIN PGP SIGNATURE----- -----BEGIN PGP SIGNATURE-----
wsBcBAEBCgAQBQJXomNHCRCEO7+YH8GHYgAALywIAG1Me852Fpn1GYu8Q1GCcw4g wsBcBAEBCgAQBQJcoosfCRCEO7+YH8GHYgAA220IALAs8T8NPgkcLvHu+5109cAN
l2k7vOFchdDwDhdSVbkh4YyvTaIO3iE2Jtk1rxw+RIJiUr0eLO/rnIJuxZS8WKki BOCNPSZDNsqLZW/2Dc9cKoBG7Jen4Qad+i5l9351kqn3D9Gm6eRfAWcjfggRobV/
DR1LI9J1VD4dxN3uDETtWDWq7ScoPsRY5mJvYZXC8whrWEt/H2kfqmoA9LloRPWp 9daZ19h0nl4O1muQNAkjvdgZt8MOP3+PB3I3/Tu2QCYjI579SLUmuXlcZR5BCFPR
flOE0iktA4UciZOblTj6nAk3iDyjh/4HYL4a6tT0LjjKI7OTw4YyHfjHad1ywVCz PJy+e3QpV2PcdeU2KZLG4tjtlrq+3QC9ZHHEJLs+BVN9d46Dwo6CxJdHJrrrAkTw
9dMUc1rPgTnl+fnRiSPSrlZIWKOt1mcQ4fVrU3nwtRUwTId2k8FtygL0G6M+Y6t0 M8MhA92vbiTTPRSCZI9x5qDAwJYhoq0oxLflpuL2tIlo3qVoCsaTSURwMESEHO32
S6yaU7qfk9uTxkdkUF7Bf1X3ukxfe+cNBC32vf4m8LY4NkcYfSqK2fGtQsnVr6s= XwYG7BaVDMELWhAorBAGBGBwWFbJ1677qQ2gd9CN0COiVhekWlFRcnn60800r84=
=NyOM =k9Y9
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: signtest name: signtest
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm home: https://helm.sh/helm
name: alpine name: alpine

@ -27,8 +27,10 @@ import (
) )
const uninstallDesc = ` const uninstallDesc = `
This command takes a release name, and then uninstalls the release from Kubernetes. This command takes a release name and uninstalls the release.
It removes all of the resources associated with the last release of the chart.
It removes all of the resources associated with the last release of the chart
as well as the release history, freeing it up for future use.
Use the '--dry-run' flag to see which releases will be uninstalled without actually Use the '--dry-run' flag to see which releases will be uninstalled without actually
uninstalling them. uninstalling them.
@ -41,13 +43,13 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Use: "uninstall RELEASE_NAME [...]", Use: "uninstall RELEASE_NAME [...]",
Aliases: []string{"del", "delete", "un"}, Aliases: []string{"del", "delete", "un"},
SuggestFor: []string{"remove", "rm"}, SuggestFor: []string{"remove", "rm"},
Short: "given a release name, uninstall the release from Kubernetes", Short: "uninstall a release",
Long: uninstallDesc, Long: uninstallDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
res, err := client.Run(args[0]) res, err := client.Run(args[i])
if err != nil { if err != nil {
return err return err
} }
@ -64,7 +66,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&client.Purge, "purge", false, "remove the release from the store and make its name free for later use") f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
return cmd return cmd

@ -30,6 +30,15 @@ func TestUninstall(t *testing.T) {
golden: "output/uninstall.txt", golden: "output/uninstall.txt",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{
name: "multiple uninstall",
cmd: "uninstall aeneas aeneas2",
golden: "output/uninstall-multiple.txt",
rels: []*release.Release{
release.Mock(&release.MockReleaseOptions{Name: "aeneas"}),
release.Mock(&release.MockReleaseOptions{Name: "aeneas2"}),
},
},
{ {
name: "uninstall with timeout", name: "uninstall with timeout",
cmd: "uninstall aeneas --timeout 120", cmd: "uninstall aeneas --timeout 120",
@ -43,9 +52,9 @@ func TestUninstall(t *testing.T) {
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {
name: "purge", name: "keep history",
cmd: "uninstall aeneas --purge", cmd: "uninstall aeneas --keep-history",
golden: "output/uninstall-purge.txt", golden: "output/uninstall-keep-history.txt",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {

@ -18,6 +18,7 @@ package main
import ( import (
"fmt" "fmt"
"path/filepath"
"testing" "testing"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
@ -28,13 +29,16 @@ import (
func TestUpgradeCmd(t *testing.T) { func TestUpgradeCmd(t *testing.T) {
tmpChart := testTempDir(t) tmpChart := testTempDir(t)
cfile := &chart.Metadata{ cfile := &chart.Chart{
Name: "testUpgradeChart", Metadata: &chart.Metadata{
Description: "A Helm chart for Kubernetes", APIVersion: chart.APIVersionV1,
Version: "0.1.0", Name: "testUpgradeChart",
Description: "A Helm chart for Kubernetes",
Version: "0.1.0",
},
} }
chartPath, err := chartutil.Create(cfile, tmpChart) chartPath := filepath.Join(tmpChart, cfile.Metadata.Name)
if err != nil { if err := chartutil.SaveDir(cfile, tmpChart); err != nil {
t.Fatalf("Error creating chart for upgrade: %v", err) t.Fatalf("Error creating chart for upgrade: %v", err)
} }
ch, err := loader.Load(chartPath) ch, err := loader.Load(chartPath)
@ -47,14 +51,9 @@ func TestUpgradeCmd(t *testing.T) {
}) })
// update chart version // update chart version
cfile = &chart.Metadata{ cfile.Metadata.Version = "0.1.2"
Name: "testUpgradeChart",
Description: "A Helm chart for Kubernetes",
Version: "0.1.2",
}
chartPath, err = chartutil.Create(cfile, tmpChart) if err := chartutil.SaveDir(cfile, tmpChart); err != nil {
if err != nil {
t.Fatalf("Error creating chart: %v", err) t.Fatalf("Error creating chart: %v", err)
} }
ch, err = loader.Load(chartPath) ch, err = loader.Load(chartPath)
@ -63,14 +62,9 @@ func TestUpgradeCmd(t *testing.T) {
} }
// update chart version again // update chart version again
cfile = &chart.Metadata{ cfile.Metadata.Version = "0.1.3"
Name: "testUpgradeChart",
Description: "A Helm chart for Kubernetes",
Version: "0.1.3",
}
chartPath, err = chartutil.Create(cfile, tmpChart) if err := chartutil.SaveDir(cfile, tmpChart); err != nil {
if err != nil {
t.Fatalf("Error creating chart: %v", err) t.Fatalf("Error creating chart: %v", err)
} }
var ch2 *chart.Chart var ch2 *chart.Chart

@ -26,6 +26,7 @@ wordpress/
LICENSE # OPTIONAL: A plain text file containing the license for the chart LICENSE # OPTIONAL: A plain text file containing the license for the chart
README.md # OPTIONAL: A human-readable README file README.md # OPTIONAL: A human-readable README file
values.yaml # The default configuration values for this chart values.yaml # The default configuration values for this chart
values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file
charts/ # A directory containing any charts upon which this chart depends. charts/ # A directory containing any charts upon which this chart depends.
templates/ # A directory of templates that, when combined with values, templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files. # will generate valid Kubernetes manifest files.
@ -763,14 +764,98 @@ parent chart.
Also, global variables of parent charts take precedence over the global variables from subcharts. Also, global variables of parent charts take precedence over the global variables from subcharts.
### Schema Files
Sometimes, a chart maintainer might want to define a structure on their values.
This can be done by defining a schema in the `values.schema.json` file. A
schema is represented as a [JSON Schema](https://json-schema.org/).
It might look something like this:
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"image": {
"description": "Container Image",
"properties": {
"repo": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"type": "object"
},
"name": {
"description": "Service name",
"type": "string"
},
"port": {
"description": "Port",
"minimum": 0,
"type": "integer"
},
"protocol": {
"type": "string"
}
},
"required": [
"protocol",
"port"
],
"title": "Values",
"type": "object"
}
```
This schema will be applied to the values to validate it. Validation occurs
when any of the following commands are invoked:
* `helm install`
* `helm upgrade`
* `helm lint`
* `helm template`
An example of a
`values.yaml` file that meets the requirements of this schema might look
something like this:
```yaml
name: frontend
protocol: https
port: 443
```
Note that the schema is applied to the final `.Values` object, and not just to
the `values.yaml` file. This means that the following `yaml` file is valid,
given that the chart is installed with the appropriate `--set` option shown
below.
```yaml
name: frontend
protocol: https
```
````
helm install --set port=443
````
Furthermore, the final `.Values` object is checked against *all* subchart
schemas. This means that restrictions on a subchart can't be circumvented by a
parent chart. This also works backwards - if a subchart has a requirement that
is not met in the subchart's `values.yaml` file, the parent chart *must*
satisfy those restrictions in order to be valid.
### References ### References
When it comes to writing templates and values files, there are several When it comes to writing templates, values, and schema files, there are several
standard references that will help you out. standard references that will help you out.
- [Go templates](https://godoc.org/text/template) - [Go templates](https://godoc.org/text/template)
- [Extra template functions](https://godoc.org/github.com/Masterminds/sprig) - [Extra template functions](https://godoc.org/github.com/Masterminds/sprig)
- [The YAML format](http://yaml.org/spec/) - [The YAML format](http://yaml.org/spec/)
- [JSON Schema](https://json-schema.org/)
## Using Helm to Manage Charts ## Using Helm to Manage Charts

@ -19,7 +19,7 @@ $ make bootstrap build
``` ```
NOTE: This will fail if not running from the path `$GOPATH/src/helm.sh/helm`. The NOTE: This will fail if not running from the path `$GOPATH/src/helm.sh/helm`. The
directory `k8s.io` should not be a symlink or `build` will not find the relevant directory `helm.sh` should not be a symlink or `build` will not find the relevant
packages. packages.
This will build both Helm and the Helm library. `make bootstrap` will attempt to This will build both Helm and the Helm library. `make bootstrap` will attempt to
@ -94,7 +94,7 @@ home of the current development candidate. Releases are tagged.
We accept changes to the code via GitHub Pull Requests (PRs). One We accept changes to the code via GitHub Pull Requests (PRs). One
workflow for doing this is as follows: workflow for doing this is as follows:
1. Go to your `$GOPATH/src/k8s.io` directory and `git clone` the 1. Go to your `$GOPATH/src` directory, then `mkdir helm.sh; cd helm.sh` and `git clone` the
`github.com/helm/helm` repository. `github.com/helm/helm` repository.
2. Fork that repository into your GitHub account 2. Fork that repository into your GitHub account
3. Add your repository as a remote for `$GOPATH/src/helm.sh/helm` 3. Add your repository as a remote for `$GOPATH/src/helm.sh/helm`

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine name: alpine
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: nginx name: nginx
description: A basic NGINX HTTP server description: A basic NGINX HTTP server
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine name: alpine
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
version: 0.1.0 version: 0.1.0

@ -15,6 +15,16 @@ Here's an exhaustive list of all the major changes introduced in Helm 3.
In Helm 3, Helm switched the Go import path over from `k8s.io/helm` to `helm.sh/helm`. If you intend In Helm 3, Helm switched the Go import path over from `k8s.io/helm` to `helm.sh/helm`. If you intend
to upgrade to the Helm 3 Go client libraries, make sure to change your import paths. to upgrade to the Helm 3 Go client libraries, make sure to change your import paths.
### Helm delete
In order to better align the verbiage from other package managers, `helm delete` was re-named to
`helm uninstall`. `helm delete` is still retained as an alias to `helm uninstall`, so either form
can be used.
In Helm 2, in order to purge the release ledger, the `--purge` flag had to be provided. This
functionality is now enabled by default. To retain the previous behaviour, use
`helm uninstall --keep-history`.
## Installing ## Installing

@ -34,6 +34,7 @@ type releaseInfo struct {
Updated string `json:"updated"` Updated string `json:"updated"`
Status string `json:"status"` Status string `json:"status"`
Chart string `json:"chart"` Chart string `json:"chart"`
AppVersion string `json:"app_version"`
Description string `json:"description"` Description string `json:"description"`
} }
@ -142,11 +143,13 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
s := r.Info.Status.String() s := r.Info.Status.String()
v := r.Version v := r.Version
d := r.Info.Description d := r.Info.Description
a := formatAppVersion(r.Chart)
rInfo := releaseInfo{ rInfo := releaseInfo{
Revision: v, Revision: v,
Status: s, Status: s,
Chart: c, Chart: c,
AppVersion: a,
Description: d, Description: d,
} }
if !r.Info.LastDeployed.IsZero() { if !r.Info.LastDeployed.IsZero() {
@ -162,10 +165,10 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
func formatAsTable(releases releaseHistory) []byte { func formatAsTable(releases releaseHistory) []byte {
tbl := uitable.New() tbl := uitable.New()
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION") tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION")
for i := 0; i <= len(releases)-1; i++ { for i := 0; i <= len(releases)-1; i++ {
r := releases[i] r := releases[i]
tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description) tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion, r.Description)
} }
return tbl.Bytes() return tbl.Bytes()
} }
@ -178,3 +181,12 @@ func formatChartname(c *chart.Chart) string {
} }
return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version) return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version)
} }
func formatAppVersion(c *chart.Chart) string {
if c == nil || c.Metadata == nil {
// This is an edge case that has happened in prod, though we don't
// know how: https://github.com/helm/helm/issues/1347
return "MISSING"
}
return c.AppVersion()
}

@ -29,6 +29,8 @@ var (
invalidArchivedChartPath = "../../cmd/helm/testdata/testcharts/invalidcompressedchart0.1.0.tgz" invalidArchivedChartPath = "../../cmd/helm/testdata/testcharts/invalidcompressedchart0.1.0.tgz"
chartDirPath = "../../cmd/helm/testdata/testcharts/decompressedchart/" chartDirPath = "../../cmd/helm/testdata/testcharts/decompressedchart/"
chartMissingManifest = "../../cmd/helm/testdata/testcharts/chart-missing-manifest" chartMissingManifest = "../../cmd/helm/testdata/testcharts/chart-missing-manifest"
chartSchema = "../../cmd/helm/testdata/testcharts/chart-with-schema"
chartSchemaNegative = "../../cmd/helm/testdata/testcharts/chart-with-schema-negative"
) )
func TestLintChart(t *testing.T) { func TestLintChart(t *testing.T) {
@ -47,4 +49,10 @@ func TestLintChart(t *testing.T) {
if _, err := lintChart(chartMissingManifest, values, namespace, strict); err == nil { if _, err := lintChart(chartMissingManifest, values, namespace, strict); err == nil {
t.Error("Expected a chart parsing error") t.Error("Expected a chart parsing error")
} }
if _, err := lintChart(chartSchema, values, namespace, strict); err != nil {
t.Error(err)
}
if _, err := lintChart(chartSchemaNegative, values, namespace, strict); err != nil {
t.Error(err)
}
} }

@ -38,7 +38,7 @@ type Uninstall struct {
DisableHooks bool DisableHooks bool
DryRun bool DryRun bool
Purge bool KeepHistory bool
Timeout int64 Timeout int64
} }
@ -78,7 +78,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
// TODO: Are there any cases where we want to force a delete even if it's // TODO: Are there any cases where we want to force a delete even if it's
// already marked deleted? // already marked deleted?
if rel.Info.Status == release.StatusUninstalled { if rel.Info.Status == release.StatusUninstalled {
if u.Purge { if !u.KeepHistory {
if err := u.purgeReleases(rels...); err != nil { if err := u.purgeReleases(rels...); err != nil {
return nil, errors.Wrap(err, "uninstall: Failed to purge the release") return nil, errors.Wrap(err, "uninstall: Failed to purge the release")
} }
@ -119,7 +119,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
rel.Info.Status = release.StatusUninstalled rel.Info.Status = release.StatusUninstalled
rel.Info.Description = "Uninstallation complete" rel.Info.Description = "Uninstallation complete"
if u.Purge { if !u.KeepHistory {
u.cfg.Log("purge requested for %s", name) u.cfg.Log("purge requested for %s", name)
err := u.purgeReleases(rels...) err := u.purgeReleases(rels...)
return res, errors.Wrap(err, "uninstall: Failed to purge the release") return res, errors.Wrap(err, "uninstall: Failed to purge the release")

@ -15,8 +15,8 @@ limitations under the License.
package chart package chart
// APIVersionv1 is the API version number for version 1. // APIVersionV1 is the API version number for version 1.
const APIVersionv1 = "v1" const APIVersionV1 = "v1"
// Chart is a helm package that contains metadata, a default config, zero or more // Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies). // optionally parameterizable templates, and zero or more charts (dependencies).
@ -31,6 +31,8 @@ type Chart struct {
RawValues []byte RawValues []byte
// Values are default config for this template. // Values are default config for this template.
Values map[string]interface{} Values map[string]interface{}
// Schema is an optional JSON schema for imposing structure on Values
Schema []byte
// Files are miscellaneous files in a chart archive, // Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc. // e.g. README, LICENSE, etc.
Files []*File Files []*File
@ -96,3 +98,15 @@ func (ch *Chart) ChartFullPath() string {
} }
return ch.Name() return ch.Name()
} }
func (ch *Chart) Validate() error {
return ch.Metadata.Validate()
}
// AppVersion returns the appversion of the chart.
func (ch *Chart) AppVersion() string {
if ch.Metadata == nil {
return ""
}
return ch.Metadata.AppVersion
}

@ -90,6 +90,8 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
return c, errors.Wrap(err, "cannot load values.yaml") return c, errors.Wrap(err, "cannot load values.yaml")
} }
c.RawValues = f.Data c.RawValues = f.Data
case f.Name == "values.schema.json":
c.Schema = f.Data
case strings.HasPrefix(f.Name, "templates/"): case strings.HasPrefix(f.Name, "templates/"):
c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data}) c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})
case strings.HasPrefix(f.Name, "charts/"): case strings.HasPrefix(f.Name, "charts/"):
@ -106,12 +108,8 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
} }
} }
// Ensure that we got a Chart.yaml file if err := c.Validate(); err != nil {
if c.Metadata == nil { return c, err
return c, errors.New("chart metadata (Chart.yaml) missing")
}
if c.Name() == "" {
return c, errors.New("invalid chart (Chart.yaml): name must not be empty")
} }
for n, files := range subcharts { for n, files := range subcharts {

@ -17,6 +17,7 @@ limitations under the License.
package loader package loader
import ( import (
"bytes"
"testing" "testing"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
@ -78,6 +79,10 @@ icon: https://example.com/64x64.png
Name: "values.yaml", Name: "values.yaml",
Data: []byte("var: some values"), Data: []byte("var: some values"),
}, },
{
Name: "values.schema.json",
Data: []byte("type: Values"),
},
{ {
Name: "templates/deployment.yaml", Name: "templates/deployment.yaml",
Data: []byte("some deployment"), Data: []byte("some deployment"),
@ -101,6 +106,10 @@ icon: https://example.com/64x64.png
t.Error("Expected chart values to be populated with default values") t.Error("Expected chart values to be populated with default values")
} }
if !bytes.Equal(c.Schema, []byte("type: Values")) {
t.Error("Expected chart schema to be populated with default values")
}
if len(c.Templates) != 2 { if len(c.Templates) != 2 {
t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) t.Errorf("Expected number of templates == 2, got %d", len(c.Templates))
} }
@ -109,7 +118,7 @@ icon: https://example.com/64x64.png
if err == nil { if err == nil {
t.Fatal("Expected err to be non-nil") t.Fatal("Expected err to be non-nil")
} }
if err.Error() != "chart metadata (Chart.yaml) missing" { if err.Error() != "metadata is required" {
t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) t.Errorf("Expected chart metadata missing error, got '%s'", err.Error())
} }
} }

@ -1,3 +1,4 @@
apiVersion: v1
name: albatross name: albatross
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
version: 0.1.0 version: 0.1.0

Binary file not shown.

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine name: alpine
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: mast1 name: mast1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine name: alpine
description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
version: 0.1.0 version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: mast1 name: mast1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
version: 0.1.0 version: 0.1.0

@ -6,7 +6,9 @@ tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross
echo "Packing mariner into frobnitz" echo "Packing mariner into frobnitz"
tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner
tar -zcvf frobnitz_backslash/charts/mariner-4.3.2.tgz mariner
# Pack the frobnitz chart. # Pack the frobnitz chart.
echo "Packing frobnitz" echo "Packing frobnitz"
tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz
tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash

@ -1,3 +1,4 @@
apiVersion: v1
name: mariner name: mariner
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
version: 4.3.2 version: 4.3.2

@ -15,6 +15,8 @@ limitations under the License.
package chart package chart
import "errors"
// Maintainer describes a Chart maintainer. // Maintainer describes a Chart maintainer.
type Maintainer struct { type Maintainer struct {
// Name is a user name or organization name // Name is a user name or organization name
@ -65,3 +67,20 @@ type Metadata struct {
// Specifies the chart type: application or library // Specifies the chart type: application or library
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
} }
func (md *Metadata) Validate() error {
if md == nil {
return errors.New("metadata is required")
}
if md.APIVersion == "" {
return errors.New("metadata apiVersion is required")
}
if md.Name == "" {
return errors.New("metadata name is required")
}
if md.Version == "" {
return errors.New("metadata version is required")
}
// TODO validate valid semver here?
return nil
}

@ -40,8 +40,8 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
} }
// Api instead of API because it was generated via protobuf. // Api instead of API because it was generated via protobuf.
if f.APIVersion != chart.APIVersionv1 { if f.APIVersion != chart.APIVersionV1 {
t.Errorf("Expected API Version %q, got %q", chart.APIVersionv1, f.APIVersion) t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion)
} }
if f.Name != name { if f.Name != name {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save