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!
|
Release "crazy-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: crazy-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
Release "zany-bunny" has been upgraded. Happy Helming!
|
Release "zany-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: zany-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: funny-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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,5 +1,11 @@
|
|||||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: funny-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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,5 +1,11 @@
|
|||||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: funny-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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,5 +1,11 @@
|
|||||||
Release "crazy-bunny" has been upgraded. Happy Helming!
|
Release "crazy-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: crazy-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
Release "funny-bunny" has been upgraded. Happy Helming!
|
Release "funny-bunny" has been upgraded. Happy Helming!
|
||||||
|
NAME: funny-bunny
|
||||||
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
|
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
|
||||||
|
@ -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