mirror of https://github.com/helm/helm
Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>pull/5365/head
parent
62f144a9d8
commit
2571dbf82f
@ -1,225 +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 "k8s.io/helm/cmd/helm"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/downloader"
|
||||
"k8s.io/helm/pkg/getter"
|
||||
"k8s.io/helm/pkg/repo"
|
||||
"k8s.io/helm/pkg/strvals"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Values Options
|
||||
|
||||
type valuesOptions struct {
|
||||
valueFiles []string // --values
|
||||
values []string // --set
|
||||
stringValues []string // --set-string
|
||||
}
|
||||
|
||||
func (o *valuesOptions) addFlags(fs *pflag.FlagSet) {
|
||||
fs.StringSliceVarP(&o.valueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
|
||||
fs.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
fs.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
}
|
||||
|
||||
// mergeValues merges values from files specified via -f/--values and
|
||||
// directly via --set or --set-string, marshaling them to YAML
|
||||
func (o *valuesOptions) mergedValues() (map[string]interface{}, error) {
|
||||
base := map[string]interface{}{}
|
||||
|
||||
// User specified a values files via -f/--values
|
||||
for _, filePath := range o.valueFiles {
|
||||
currentMap := map[string]interface{}{}
|
||||
|
||||
bytes, err := readFile(filePath)
|
||||
if err != nil {
|
||||
return base, err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(bytes, ¤tMap); err != nil {
|
||||
return base, errors.Wrapf(err, "failed to parse %s", filePath)
|
||||
}
|
||||
// Merge with the previous map
|
||||
base = mergeValues(base, currentMap)
|
||||
}
|
||||
|
||||
// User specified a value via --set
|
||||
for _, value := range o.values {
|
||||
if err := strvals.ParseInto(value, base); err != nil {
|
||||
return base, errors.Wrap(err, "failed parsing --set data")
|
||||
}
|
||||
}
|
||||
|
||||
// User specified a value via --set-string
|
||||
for _, value := range o.stringValues {
|
||||
if err := strvals.ParseIntoString(value, base); err != nil {
|
||||
return base, errors.Wrap(err, "failed parsing --set-string data")
|
||||
}
|
||||
}
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// readFile load a file from stdin, the local directory, or a remote file with a url.
|
||||
func readFile(filePath string) ([]byte, error) {
|
||||
if strings.TrimSpace(filePath) == "-" {
|
||||
return ioutil.ReadAll(os.Stdin)
|
||||
}
|
||||
u, _ := url.Parse(filePath)
|
||||
p := getter.All(settings)
|
||||
|
||||
// FIXME: maybe someone handle other protocols like ftp.
|
||||
getterConstructor, err := p.ByScheme(u.Scheme)
|
||||
|
||||
if err != nil {
|
||||
return ioutil.ReadFile(filePath)
|
||||
}
|
||||
|
||||
getter, err := getterConstructor(filePath, "", "", "")
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
data, err := getter.Get(filePath)
|
||||
return data.Bytes(), err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Chart Path Options
|
||||
|
||||
type chartPathOptions struct {
|
||||
caFile string // --ca-file
|
||||
certFile string // --cert-file
|
||||
keyFile string // --key-file
|
||||
keyring string // --keyring
|
||||
password string // --password
|
||||
repoURL string // --repo
|
||||
username string // --username
|
||||
verify bool // --verify
|
||||
version string // --version
|
||||
}
|
||||
|
||||
// defaultKeyring returns the expanded path to the default keyring.
|
||||
func defaultKeyring() string {
|
||||
if v, ok := os.LookupEnv("GNUPGHOME"); ok {
|
||||
return filepath.Join(v, "pubring.gpg")
|
||||
}
|
||||
return os.ExpandEnv("$HOME/.gnupg/pubring.gpg")
|
||||
}
|
||||
|
||||
func (o *chartPathOptions) addFlags(fs *pflag.FlagSet) {
|
||||
fs.StringVar(&o.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
|
||||
fs.BoolVar(&o.verify, "verify", false, "verify the package before installing it")
|
||||
fs.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
|
||||
fs.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart")
|
||||
fs.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart")
|
||||
fs.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart")
|
||||
fs.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
|
||||
fs.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
|
||||
fs.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
}
|
||||
|
||||
func (o *chartPathOptions) locateChart(name string) (string, error) {
|
||||
return locateChartPath(o.repoURL, o.username, o.password, name, o.version, o.keyring, o.certFile, o.keyFile, o.caFile, o.verify)
|
||||
}
|
||||
|
||||
// locateChartPath looks for a chart directory in known places, and returns either the full path or an error.
|
||||
//
|
||||
// This does not ensure that the chart is well-formed; only that the requested filename exists.
|
||||
//
|
||||
// Order of resolution:
|
||||
// - relative to current working directory
|
||||
// - if path is absolute or begins with '.', error out here
|
||||
// - chart repos in $HELM_HOME
|
||||
// - URL
|
||||
//
|
||||
// If 'verify' is true, this will attempt to also verify the chart.
|
||||
func locateChartPath(repoURL, username, password, name, version, keyring,
|
||||
certFile, keyFile, caFile string, verify bool) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
version = strings.TrimSpace(version)
|
||||
|
||||
if _, err := os.Stat(name); err == nil {
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return abs, err
|
||||
}
|
||||
if verify {
|
||||
if _, err := downloader.VerifyChart(abs, keyring); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
|
||||
return name, errors.Errorf("path %q not found", name)
|
||||
}
|
||||
|
||||
crepo := filepath.Join(settings.Home.Repository(), name)
|
||||
if _, err := os.Stat(crepo); err == nil {
|
||||
return filepath.Abs(crepo)
|
||||
}
|
||||
|
||||
dl := downloader.ChartDownloader{
|
||||
HelmHome: settings.Home,
|
||||
Out: os.Stdout,
|
||||
Keyring: keyring,
|
||||
Getters: getter.All(settings),
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if verify {
|
||||
dl.Verify = downloader.VerifyAlways
|
||||
}
|
||||
if repoURL != "" {
|
||||
chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version,
|
||||
certFile, keyFile, caFile, getter.All(settings))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = chartURL
|
||||
}
|
||||
|
||||
if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) {
|
||||
os.MkdirAll(settings.Home.Archive(), 0744)
|
||||
}
|
||||
|
||||
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive())
|
||||
if err == nil {
|
||||
lname, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return filename, err
|
||||
}
|
||||
debug("Fetched %s to %s\n", name, filename)
|
||||
return lname, nil
|
||||
} else if settings.Debug {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
|
||||
}
|
@ -1,66 +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"
|
||||
|
||||
"k8s.io/helm/pkg/hapi/release"
|
||||
)
|
||||
|
||||
func TestReleaseTesting(t *testing.T) {
|
||||
tests := []cmdTestCase{{
|
||||
name: "basic test",
|
||||
cmd: "test example-release",
|
||||
testRunStatus: map[string]release.TestRunStatus{"PASSED: green lights everywhere": release.TestRunSuccess},
|
||||
golden: "output/test.txt",
|
||||
}, {
|
||||
name: "test failure",
|
||||
cmd: "test example-fail",
|
||||
testRunStatus: map[string]release.TestRunStatus{"FAILURE: red lights everywhere": release.TestRunFailure},
|
||||
wantError: true,
|
||||
golden: "output/test-failure.txt",
|
||||
}, {
|
||||
name: "test unknown",
|
||||
cmd: "test example-unknown",
|
||||
testRunStatus: map[string]release.TestRunStatus{"UNKNOWN: yellow lights everywhere": release.TestRunUnknown},
|
||||
golden: "output/test-unknown.txt",
|
||||
}, {
|
||||
name: "test error",
|
||||
cmd: "test example-error",
|
||||
testRunStatus: map[string]release.TestRunStatus{"ERROR: yellow lights everywhere": release.TestRunFailure},
|
||||
wantError: true,
|
||||
golden: "output/test-error.txt",
|
||||
}, {
|
||||
name: "test running",
|
||||
cmd: "test example-running",
|
||||
testRunStatus: map[string]release.TestRunStatus{"RUNNING: things are happpeningggg": release.TestRunRunning},
|
||||
golden: "output/test-running.txt",
|
||||
}, {
|
||||
name: "multiple tests example",
|
||||
cmd: "test example-suite",
|
||||
testRunStatus: map[string]release.TestRunStatus{
|
||||
"RUNNING: things are happpeningggg": release.TestRunRunning,
|
||||
"PASSED: party time": release.TestRunSuccess,
|
||||
"RUNNING: things are happening again": release.TestRunRunning,
|
||||
"FAILURE: good thing u checked :)": release.TestRunFailure,
|
||||
"RUNNING: things are happpeningggg yet again": release.TestRunRunning,
|
||||
"PASSED: feel free to party again": release.TestRunSuccess},
|
||||
wantError: true,
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
ERROR: yellow lights everywhere
|
||||
Error: 1 test(s) failed
|
@ -1,2 +0,0 @@
|
||||
FAILURE: red lights everywhere
|
||||
Error: 1 test(s) failed
|
@ -1 +0,0 @@
|
||||
RUNNING: things are happpeningggg
|
@ -1 +0,0 @@
|
||||
UNKNOWN: yellow lights everywhere
|
@ -1 +0,0 @@
|
||||
PASSED: green lights everywhere
|
@ -1,5 +1,11 @@
|
||||
Release "crazy-bunny" has been upgraded. Happy Helming!
|
||||
NAME: crazy-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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
|
||||
|
@ -1,5 +1,11 @@
|
||||
Release "zany-bunny" has been upgraded. Happy Helming!
|
||||
NAME: zany-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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
|
||||
|
@ -1,5 +1,11 @@
|
||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||
NAME: funny-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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,5 +1,11 @@
|
||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||
NAME: funny-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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,5 +1,11 @@
|
||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||
NAME: funny-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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,5 +1,11 @@
|
||||
Release "crazy-bunny" has been upgraded. Happy Helming!
|
||||
NAME: crazy-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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
|
||||
|
@ -1,5 +1,11 @@
|
||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||
NAME: funny-bunny
|
||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
||||
NAMESPACE: default
|
||||
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
|
||||
|
@ -0,0 +1,195 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/chart"
|
||||
"k8s.io/helm/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// Dependency is the action for building a given chart's dependency tree.
|
||||
//
|
||||
// It provides the implementation of 'helm dependency' and its respective subcommands.
|
||||
type Dependency struct {
|
||||
Verify bool
|
||||
Keyring string
|
||||
SkipRefresh bool
|
||||
}
|
||||
|
||||
// NewDependency creates a new Dependency object with the given configuration.
|
||||
func NewDependency() *Dependency {
|
||||
return &Dependency{}
|
||||
}
|
||||
|
||||
func (d *Dependency) AddBuildFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&d.Verify, "verify", false, "verify the packages against signatures")
|
||||
f.StringVar(&d.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
|
||||
}
|
||||
|
||||
func (d *Dependency) AddUpdateFlags(f *pflag.FlagSet) {
|
||||
d.AddBuildFlags(f)
|
||||
f.BoolVar(&d.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
|
||||
}
|
||||
|
||||
// List executes 'helm dependency list'.
|
||||
func (d *Dependency) List(chartpath string, out io.Writer) error {
|
||||
c, err := loader.Load(chartpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Metadata.Dependencies == nil {
|
||||
fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(chartpath, "charts"))
|
||||
return nil
|
||||
}
|
||||
|
||||
d.printDependencies(chartpath, out, c.Metadata.Dependencies)
|
||||
fmt.Fprintln(out)
|
||||
d.printMissing(chartpath, out, c.Metadata.Dependencies)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency) string {
|
||||
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
|
||||
archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename))
|
||||
if err != nil {
|
||||
return "bad pattern"
|
||||
} else if len(archives) > 1 {
|
||||
return "too many matches"
|
||||
} else if len(archives) == 1 {
|
||||
archive := archives[0]
|
||||
if _, err := os.Stat(archive); err == nil {
|
||||
c, err := loader.Load(archive)
|
||||
if err != nil {
|
||||
return "corrupt"
|
||||
}
|
||||
if c.Name() != dep.Name {
|
||||
return "misnamed"
|
||||
}
|
||||
|
||||
if c.Metadata.Version != dep.Version {
|
||||
constraint, err := semver.NewConstraint(dep.Version)
|
||||
if err != nil {
|
||||
return "invalid version"
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion(c.Metadata.Version)
|
||||
if err != nil {
|
||||
return "invalid version"
|
||||
}
|
||||
|
||||
if constraint.Check(v) {
|
||||
return "ok"
|
||||
}
|
||||
return "wrong version"
|
||||
}
|
||||
return "ok"
|
||||
}
|
||||
}
|
||||
|
||||
folder := filepath.Join(chartpath, "charts", dep.Name)
|
||||
if fi, err := os.Stat(folder); err != nil {
|
||||
return "missing"
|
||||
} else if !fi.IsDir() {
|
||||
return "mispackaged"
|
||||
}
|
||||
|
||||
c, err := loader.Load(folder)
|
||||
if err != nil {
|
||||
return "corrupt"
|
||||
}
|
||||
|
||||
if c.Name() != dep.Name {
|
||||
return "misnamed"
|
||||
}
|
||||
|
||||
if c.Metadata.Version != dep.Version {
|
||||
constraint, err := semver.NewConstraint(dep.Version)
|
||||
if err != nil {
|
||||
return "invalid version"
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion(c.Metadata.Version)
|
||||
if err != nil {
|
||||
return "invalid version"
|
||||
}
|
||||
|
||||
if constraint.Check(v) {
|
||||
return "unpacked"
|
||||
}
|
||||
return "wrong version"
|
||||
}
|
||||
|
||||
return "unpacked"
|
||||
}
|
||||
|
||||
// printDependencies prints all of the dependencies in the yaml file.
|
||||
func (d *Dependency) printDependencies(chartpath string, out io.Writer, reqs []*chart.Dependency) {
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = 80
|
||||
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
|
||||
for _, row := range reqs {
|
||||
table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row))
|
||||
}
|
||||
fmt.Fprintln(out, table)
|
||||
}
|
||||
|
||||
// printMissing prints warnings about charts that are present on disk, but are
|
||||
// not in Charts.yaml.
|
||||
func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) {
|
||||
folder := filepath.Join(chartpath, "charts/*")
|
||||
files, err := filepath.Glob(folder)
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
fi, err := os.Stat(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Warning: %s\n", err)
|
||||
}
|
||||
// Skip anything that is not a directory and not a tgz file.
|
||||
if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
|
||||
continue
|
||||
}
|
||||
c, err := loader.Load(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for _, d := range reqs {
|
||||
if d.Name == c.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/release"
|
||||
)
|
||||
|
||||
// Get is the action for checking a given release's information.
|
||||
//
|
||||
// It provides the implementation of 'helm get' and its respective subcommands (except `helm get values`).
|
||||
type Get struct {
|
||||
cfg *Configuration
|
||||
|
||||
Version int
|
||||
}
|
||||
|
||||
// NewGet creates a new Get object with the given configuration.
|
||||
func NewGet(cfg *Configuration) *Get {
|
||||
return &Get{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes 'helm get' against the given release.
|
||||
func (g *Get) Run(name string) (*release.Release, error) {
|
||||
return g.cfg.releaseContent(name, g.Version)
|
||||
}
|
||||
|
||||
func (g *Get) AddFlags(f *pflag.FlagSet) {
|
||||
f.IntVar(&g.Version, "revision", 0, "get the named release with revision")
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
)
|
||||
|
||||
// GetValues is the action for checking a given release's values.
|
||||
//
|
||||
// It provides the implementation of 'helm get values'.
|
||||
type GetValues struct {
|
||||
cfg *Configuration
|
||||
|
||||
Version int
|
||||
AllValues bool
|
||||
}
|
||||
|
||||
// NewGetValues creates a new GetValues object with the given configuration.
|
||||
func NewGetValues(cfg *Configuration) *GetValues {
|
||||
return &GetValues{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes 'helm get values' against the given release.
|
||||
func (g *GetValues) Run(name string) (string, error) {
|
||||
res, err := g.cfg.releaseContent(name, g.Version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If the user wants all values, compute the values and return.
|
||||
if g.AllValues {
|
||||
cfg, err := chartutil.CoalesceValues(res.Chart, res.Config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cfgStr, err := cfg.YAML()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cfgStr, nil
|
||||
}
|
||||
|
||||
resConfig, err := yaml.Marshal(res.Config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(resConfig), nil
|
||||
}
|
||||
|
||||
func (g *GetValues) AddFlags(f *pflag.FlagSet) {
|
||||
f.IntVar(&g.Version, "revision", 0, "get the named release with revision")
|
||||
f.BoolVarP(&g.AllValues, "all", "a", false, "dump all (computed) values")
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/chart"
|
||||
"k8s.io/helm/pkg/release"
|
||||
"k8s.io/helm/pkg/releaseutil"
|
||||
)
|
||||
|
||||
type releaseInfo struct {
|
||||
Revision int `json:"revision"`
|
||||
Updated string `json:"updated"`
|
||||
Status string `json:"status"`
|
||||
Chart string `json:"chart"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type releaseHistory []releaseInfo
|
||||
|
||||
type OutputFormat string
|
||||
|
||||
const (
|
||||
Table OutputFormat = "table"
|
||||
JSON OutputFormat = "json"
|
||||
YAML OutputFormat = "yaml"
|
||||
)
|
||||
|
||||
var ErrInvalidFormatType = errors.New("invalid format type")
|
||||
|
||||
func (o OutputFormat) String() string {
|
||||
return string(o)
|
||||
}
|
||||
|
||||
func ParseOutputFormat(s string) (out OutputFormat, err error) {
|
||||
switch s {
|
||||
case Table.String():
|
||||
out, err = Table, nil
|
||||
case JSON.String():
|
||||
out, err = JSON, nil
|
||||
case YAML.String():
|
||||
out, err = YAML, nil
|
||||
default:
|
||||
out, err = "", ErrInvalidFormatType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o OutputFormat) MarshalHistory(hist releaseHistory) (byt []byte, err error) {
|
||||
switch o {
|
||||
case YAML:
|
||||
byt, err = yaml.Marshal(hist)
|
||||
case JSON:
|
||||
byt, err = json.Marshal(hist)
|
||||
case Table:
|
||||
byt = formatAsTable(hist)
|
||||
default:
|
||||
err = ErrInvalidFormatType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// History is the action for checking the release's ledger.
|
||||
//
|
||||
// It provides the implementation of 'helm history'.
|
||||
type History struct {
|
||||
cfg *Configuration
|
||||
|
||||
Max int
|
||||
OutputFormat string
|
||||
}
|
||||
|
||||
// NewHistory creates a new History object with the given configuration.
|
||||
func NewHistory(cfg *Configuration) *History {
|
||||
return &History{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes 'helm history' against the given release.
|
||||
func (h *History) Run(name string) (string, error) {
|
||||
if err := validateReleaseName(name); err != nil {
|
||||
return "", errors.Errorf("getHistory: Release name is invalid: %s", name)
|
||||
}
|
||||
|
||||
h.cfg.Log("getting history for release %s", name)
|
||||
hist, err := h.cfg.Releases.History(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
releaseutil.Reverse(hist, releaseutil.SortByRevision)
|
||||
|
||||
var rels []*release.Release
|
||||
for i := 0; i < min(len(hist), h.Max); i++ {
|
||||
rels = append(rels, hist[i])
|
||||
}
|
||||
|
||||
if len(rels) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
releaseHistory := getReleaseHistory(rels)
|
||||
|
||||
outputFormat, err := ParseOutputFormat(h.OutputFormat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
history, formattingError := outputFormat.MarshalHistory(releaseHistory)
|
||||
if formattingError != nil {
|
||||
return "", formattingError
|
||||
}
|
||||
|
||||
return string(history), nil
|
||||
}
|
||||
|
||||
func (h *History) AddFlags(f *pflag.FlagSet) {
|
||||
f.StringVarP(&h.OutputFormat, "output", "o", Table.String(), "prints the output in the specified format (json|table|yaml)")
|
||||
f.IntVar(&h.Max, "max", 256, "maximum number of revision to include in history")
|
||||
}
|
||||
|
||||
func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
|
||||
for i := len(rls) - 1; i >= 0; i-- {
|
||||
r := rls[i]
|
||||
c := formatChartname(r.Chart)
|
||||
s := r.Info.Status.String()
|
||||
v := r.Version
|
||||
d := r.Info.Description
|
||||
|
||||
rInfo := releaseInfo{
|
||||
Revision: v,
|
||||
Status: s,
|
||||
Chart: c,
|
||||
Description: d,
|
||||
}
|
||||
if !r.Info.LastDeployed.IsZero() {
|
||||
rInfo.Updated = r.Info.LastDeployed.String()
|
||||
|
||||
}
|
||||
history = append(history, rInfo)
|
||||
}
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
func formatAsTable(releases releaseHistory) []byte {
|
||||
tbl := uitable.New()
|
||||
|
||||
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
|
||||
for i := 0; i <= len(releases)-1; i++ {
|
||||
r := releases[i]
|
||||
tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description)
|
||||
}
|
||||
return tbl.Bytes()
|
||||
}
|
||||
|
||||
func formatChartname(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 fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version)
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/lint"
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
)
|
||||
|
||||
var errLintNoChart = errors.New("no chart found for linting (missing Chart.yaml)")
|
||||
|
||||
// Lint is the action for checking that the semantics of a chart are well-formed.
|
||||
//
|
||||
// It provides the implementation of 'helm lint'.
|
||||
type Lint struct {
|
||||
ValueOptions
|
||||
|
||||
Strict bool
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type LintResult struct {
|
||||
TotalChartsLinted int
|
||||
Messages []support.Message
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// NewLint creates a new Lint object with the given configuration.
|
||||
func NewLint() *Lint {
|
||||
return &Lint{}
|
||||
}
|
||||
|
||||
// Run executes 'helm Lint' against the given chart.
|
||||
func (l *Lint) Run(paths []string) *LintResult {
|
||||
lowestTolerance := support.ErrorSev
|
||||
if l.Strict {
|
||||
lowestTolerance = support.WarningSev
|
||||
}
|
||||
|
||||
result := &LintResult{}
|
||||
for _, path := range paths {
|
||||
if linter, err := lintChart(path, l.ValueOptions.rawValues, l.Namespace, l.Strict); err != nil {
|
||||
if err == errLintNoChart {
|
||||
result.Errors = append(result.Errors, err)
|
||||
}
|
||||
} else {
|
||||
result.Messages = append(result.Messages, linter.Messages...)
|
||||
result.TotalChartsLinted++
|
||||
if linter.HighestSeverity >= lowestTolerance {
|
||||
result.Errors = append(result.Errors, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
|
||||
var chartPath string
|
||||
linter := support.Linter{}
|
||||
|
||||
if strings.HasSuffix(path, ".tgz") {
|
||||
tempDir, err := ioutil.TempDir("", "helm-lint")
|
||||
if err != nil {
|
||||
return linter, err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return linter, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err = chartutil.Expand(tempDir, file); err != nil {
|
||||
return linter, err
|
||||
}
|
||||
|
||||
lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-")
|
||||
if lastHyphenIndex <= 0 {
|
||||
return linter, errors.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path))
|
||||
}
|
||||
base := filepath.Base(path)[:lastHyphenIndex]
|
||||
chartPath = filepath.Join(tempDir, base)
|
||||
} else {
|
||||
chartPath = path
|
||||
}
|
||||
|
||||
// Guard: Error out of this is not a chart.
|
||||
if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
|
||||
return linter, errLintNoChart
|
||||
}
|
||||
|
||||
return lint.All(chartPath, vals, namespace, strict), nil
|
||||
}
|
||||
|
||||
func (l *Lint) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&l.Strict, "strict", false, "fail on lint warnings")
|
||||
l.ValueOptions.AddFlags(f)
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"k8s.io/helm/pkg/chart"
|
||||
"k8s.io/helm/pkg/chart/loader"
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/provenance"
|
||||
)
|
||||
|
||||
// Package is the action for packaging a chart.
|
||||
//
|
||||
// It provides the implementation of 'helm package'.
|
||||
type Package struct {
|
||||
ValueOptions
|
||||
|
||||
Sign bool
|
||||
Key string
|
||||
Keyring string
|
||||
Version string
|
||||
AppVersion string
|
||||
Destination string
|
||||
DependencyUpdate bool
|
||||
}
|
||||
|
||||
// NewPackage creates a new Package object with the given configuration.
|
||||
func NewPackage() *Package {
|
||||
return &Package{}
|
||||
}
|
||||
|
||||
// Run executes 'helm package' against the given chart and returns the path to the packaged chart.
|
||||
func (p *Package) Run(path string) (string, error) {
|
||||
ch, err := loader.LoadDir(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
validChartType, err := chartutil.IsValidChartType(ch)
|
||||
if !validChartType {
|
||||
return "", err
|
||||
}
|
||||
|
||||
combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ch.Values = combinedVals
|
||||
|
||||
// If version is set, modify the version.
|
||||
if len(p.Version) != 0 {
|
||||
if err := setVersion(ch, p.Version); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if p.AppVersion != "" {
|
||||
ch.Metadata.AppVersion = p.AppVersion
|
||||
}
|
||||
|
||||
if reqs := ch.Metadata.Dependencies; reqs != nil {
|
||||
if err := CheckDependencies(ch, reqs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
var dest string
|
||||
if p.Destination == "." {
|
||||
// Save to the current working directory.
|
||||
dest, err = os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
// Otherwise save to set destination
|
||||
dest = p.Destination
|
||||
}
|
||||
|
||||
name, err := chartutil.Save(ch, dest)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to save")
|
||||
}
|
||||
|
||||
if p.Sign {
|
||||
err = p.Clearsign(name)
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (p *Package) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&p.Sign, "sign", false, "use a PGP private key to sign this package")
|
||||
f.StringVar(&p.Key, "key", "", "name of the key to use when signing. Used if --sign is true")
|
||||
f.StringVar(&p.Keyring, "keyring", defaultKeyring(), "location of a public keyring")
|
||||
f.StringVar(&p.Version, "version", "", "set the version on the chart to this semver version")
|
||||
f.StringVar(&p.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
|
||||
f.StringVarP(&p.Destination, "destination", "d", ".", "location to write the chart.")
|
||||
f.BoolVarP(&p.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
|
||||
p.ValueOptions.AddFlags(f)
|
||||
}
|
||||
|
||||
func setVersion(ch *chart.Chart, ver string) error {
|
||||
// Verify that version is a Version, and error out if it is not.
|
||||
if _, err := semver.NewVersion(ver); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the version field on the chart.
|
||||
ch.Metadata.Version = ver
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Package) Clearsign(filename string) error {
|
||||
// Load keyring
|
||||
signer, err := provenance.NewFromKeyring(p.Keyring, p.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := signer.DecryptKey(promptUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig, err := signer.ClearSign(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
|
||||
}
|
||||
|
||||
// promptUser implements provenance.PassphraseFetcher
|
||||
func promptUser(name string) ([]byte, error) {
|
||||
fmt.Printf("Password for key %q > ", name)
|
||||
pw, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println()
|
||||
return pw, err
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/gosuri/uitable/util/strutil"
|
||||
|
||||
"k8s.io/helm/pkg/release"
|
||||
)
|
||||
|
||||
// PrintRelease prints info about a release
|
||||
func PrintRelease(out io.Writer, rel *release.Release) {
|
||||
if rel == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(out, "NAME: %s\n", rel.Name)
|
||||
if !rel.Info.LastDeployed.IsZero() {
|
||||
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", rel.Info.LastDeployed)
|
||||
}
|
||||
fmt.Fprintf(out, "NAMESPACE: %s\n", rel.Namespace)
|
||||
fmt.Fprintf(out, "STATUS: %s\n", rel.Info.Status.String())
|
||||
fmt.Fprintf(out, "\n")
|
||||
if len(rel.Info.Resources) > 0 {
|
||||
re := regexp.MustCompile(" +")
|
||||
|
||||
w := tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent)
|
||||
fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(rel.Info.Resources, "\t"))
|
||||
w.Flush()
|
||||
}
|
||||
if rel.Info.LastTestSuiteRun != nil {
|
||||
lastRun := rel.Info.LastTestSuiteRun
|
||||
fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n",
|
||||
fmt.Sprintf("Last Started: %s", lastRun.StartedAt),
|
||||
fmt.Sprintf("Last Completed: %s", lastRun.CompletedAt),
|
||||
formatTestResults(lastRun.Results))
|
||||
}
|
||||
|
||||
if len(rel.Info.Notes) > 0 {
|
||||
fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(rel.Info.Notes))
|
||||
}
|
||||
}
|
||||
|
||||
func formatTestResults(results []*release.TestRun) string {
|
||||
tbl := uitable.New()
|
||||
tbl.MaxColWidth = 50
|
||||
tbl.AddRow("TEST", "STATUS", "INFO", "STARTED", "COMPLETED")
|
||||
for i := 0; i < len(results); i++ {
|
||||
r := results[i]
|
||||
n := r.Name
|
||||
s := strutil.PadRight(r.Status.String(), 10, ' ')
|
||||
i := r.Info
|
||||
ts := r.StartedAt
|
||||
tc := r.CompletedAt
|
||||
tbl.AddRow(n, s, i, ts, tc)
|
||||
}
|
||||
return tbl.String()
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/cli"
|
||||
"k8s.io/helm/pkg/downloader"
|
||||
"k8s.io/helm/pkg/getter"
|
||||
"k8s.io/helm/pkg/repo"
|
||||
)
|
||||
|
||||
// Pull is the action for checking a given release's information.
|
||||
//
|
||||
// It provides the implementation of 'helm pull'.
|
||||
type Pull struct {
|
||||
ChartPathOptions
|
||||
|
||||
Out io.Writer // TODO: refactor this out of pkg/action
|
||||
Settings cli.EnvSettings // TODO: refactor this out of pkg/action
|
||||
|
||||
Devel bool
|
||||
Untar bool
|
||||
VerifyLater bool
|
||||
UntarDir string
|
||||
DestDir string
|
||||
}
|
||||
|
||||
// NewPull creates a new Pull object with the given configuration.
|
||||
func NewPull() *Pull {
|
||||
return &Pull{}
|
||||
}
|
||||
|
||||
// Run executes 'helm pull' against the given release.
|
||||
func (p *Pull) Run(chartRef string) error {
|
||||
c := downloader.ChartDownloader{
|
||||
HelmHome: p.Settings.Home,
|
||||
Out: p.Out,
|
||||
Keyring: p.Keyring,
|
||||
Verify: downloader.VerifyNever,
|
||||
Getters: getter.All(p.Settings),
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
}
|
||||
|
||||
if p.Verify {
|
||||
c.Verify = downloader.VerifyAlways
|
||||
} else if p.VerifyLater {
|
||||
c.Verify = downloader.VerifyLater
|
||||
}
|
||||
|
||||
// If untar is set, we fetch to a tempdir, then untar and copy after
|
||||
// verification.
|
||||
dest := p.DestDir
|
||||
if p.Untar {
|
||||
var err error
|
||||
dest, err = ioutil.TempDir("", "helm-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to untar")
|
||||
}
|
||||
defer os.RemoveAll(dest)
|
||||
}
|
||||
|
||||
if p.RepoURL != "" {
|
||||
chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, getter.All(p.Settings))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chartRef = chartURL
|
||||
}
|
||||
|
||||
saved, v, err := c.DownloadTo(chartRef, p.Version, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Verify {
|
||||
fmt.Fprintf(p.Out, "Verification: %v\n", v)
|
||||
}
|
||||
|
||||
// After verification, untar the chart into the requested directory.
|
||||
if p.Untar {
|
||||
ud := p.UntarDir
|
||||
if !filepath.IsAbs(ud) {
|
||||
ud = filepath.Join(p.DestDir, ud)
|
||||
}
|
||||
if fi, err := os.Stat(ud); err != nil {
|
||||
if err := os.MkdirAll(ud, 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to untar (mkdir)")
|
||||
}
|
||||
|
||||
} else if !fi.IsDir() {
|
||||
return errors.Errorf("failed to untar: %s is not a directory", ud)
|
||||
}
|
||||
|
||||
return chartutil.ExpandFile(ud, saved)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pull) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&p.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
|
||||
f.BoolVar(&p.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
|
||||
f.BoolVar(&p.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
|
||||
f.StringVar(&p.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
|
||||
f.StringVarP(&p.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
|
||||
p.ChartPathOptions.AddFlags(f)
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/helm/pkg/release"
|
||||
reltesting "k8s.io/helm/pkg/releasetesting"
|
||||
)
|
||||
|
||||
// ReleaseTesting is the action for testing a release.
|
||||
//
|
||||
// It provides the implementation of 'helm test'.
|
||||
type ReleaseTesting struct {
|
||||
cfg *Configuration
|
||||
|
||||
Timeout int64
|
||||
Cleanup bool
|
||||
}
|
||||
|
||||
// NewReleaseTesting creates a new ReleaseTesting object with the given configuration.
|
||||
func NewReleaseTesting(cfg *Configuration) *ReleaseTesting {
|
||||
return &ReleaseTesting{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReleaseTesting) AddFlags(f *pflag.FlagSet) {
|
||||
f.Int64Var(&r.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
f.BoolVar(&r.Cleanup, "cleanup", false, "delete test pods upon completion")
|
||||
}
|
||||
|
||||
// Run executes 'helm test' against the given release.
|
||||
func (r *ReleaseTesting) Run(name string) (<-chan *release.TestReleaseResponse, <-chan error) {
|
||||
errc := make(chan error, 1)
|
||||
if err := validateReleaseName(name); err != nil {
|
||||
errc <- errors.Errorf("releaseTest: Release name is invalid: %s", name)
|
||||
return nil, errc
|
||||
}
|
||||
|
||||
// finds the non-deleted release with the given name
|
||||
rel, err := r.cfg.Releases.Last(name)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return nil, errc
|
||||
}
|
||||
|
||||
ch := make(chan *release.TestReleaseResponse, 1)
|
||||
testEnv := &reltesting.Environment{
|
||||
Namespace: rel.Namespace,
|
||||
KubeClient: r.cfg.KubeClient,
|
||||
Timeout: r.Timeout,
|
||||
Messages: ch,
|
||||
}
|
||||
r.cfg.Log("running tests for release %s", rel.Name)
|
||||
tSuite := reltesting.NewTestSuite(rel)
|
||||
|
||||
go func() {
|
||||
defer close(errc)
|
||||
defer close(ch)
|
||||
|
||||
if err := tSuite.Run(testEnv); err != nil {
|
||||
errc <- errors.Wrapf(err, "error running test suite for %s", rel.Name)
|
||||
return
|
||||
}
|
||||
|
||||
rel.Info.LastTestSuiteRun = &release.TestSuite{
|
||||
StartedAt: tSuite.StartedAt,
|
||||
CompletedAt: tSuite.CompletedAt,
|
||||
Results: tSuite.Results,
|
||||
}
|
||||
|
||||
if r.Cleanup {
|
||||
testEnv.DeleteTestPods(tSuite.TestManifests)
|
||||
}
|
||||
|
||||
if err := r.cfg.Releases.Update(rel); err != nil {
|
||||
r.cfg.Log("test: Failed to store updated release: %s", err)
|
||||
}
|
||||
}()
|
||||
return ch, errc
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue