Commit 1c6fc9c0 authored by Matt Butcher's avatar Matt Butcher

feat(helm): remove the requirement that fetch/install need version

This removes the requirement that a fetch or install command must
explicitly state the version number to install. Instead, this goes to
the strategy used by OS package managers: Install the latest until told
to do otherwise.

Closes #1198
parent 7b222cef
......@@ -32,6 +32,8 @@ Think of it like apt/yum/homebrew for Kubernetes.
Download a [release tarball of helm for your platform](https://github.com/kubernetes/helm/releases). Unpack the `helm` binary and add it to your PATH and you are good to go! OS X/[Cask](https://caskroom.github.io/) users can `brew cask install helm`.
To rapidly get Helm up and running, start with the [Quick Start Guide](docs/quickstart.md).
See the [installation guide](docs/install.md) for more options,
including installing pre-releases.
......
......@@ -24,6 +24,7 @@ import (
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
......@@ -67,21 +68,24 @@ type ChartDownloader struct {
// If Verify is set to VerifyAlways, this will return a verification or an error if the verification fails.
//
// For VerifyNever and VerifyIfPossible, the Verification may be empty.
func (c *ChartDownloader) DownloadTo(ref string, dest string) (*provenance.Verification, error) {
//
// Returns a string path to the location where the file was downloaded and a verification
// (if provenance was verified), or an error if something bad happened.
func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) {
// resolve URL
u, err := c.ResolveChartVersion(ref)
u, err := c.ResolveChartVersion(ref, version)
if err != nil {
return nil, err
return "", nil, err
}
data, err := download(u.String())
if err != nil {
return nil, err
return "", nil, err
}
name := filepath.Base(u.Path)
destfile := filepath.Join(dest, name)
if err := ioutil.WriteFile(destfile, data.Bytes(), 0655); err != nil {
return nil, err
return destfile, nil, err
}
// If provenance is requested, verify it.
......@@ -91,31 +95,40 @@ func (c *ChartDownloader) DownloadTo(ref string, dest string) (*provenance.Verif
body, err := download(u.String() + ".prov")
if err != nil {
if c.Verify == VerifyAlways {
return ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov")
return destfile, ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov")
}
fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err)
return ver, nil
return destfile, ver, nil
}
provfile := destfile + ".prov"
if err := ioutil.WriteFile(provfile, body.Bytes(), 0655); err != nil {
return nil, err
return destfile, nil, err
}
ver, err = VerifyChart(destfile, c.Keyring)
if err != nil {
// Fail always in this case, since it means the verification step
// failed.
return ver, err
return destfile, ver, err
}
}
return ver, nil
return destfile, ver, nil
}
// ResolveChartVersion resolves a chart reference to a URL.
//
// A reference may be an HTTP URL, a 'reponame/chartname' reference, or a local path.
func (c *ChartDownloader) ResolveChartVersion(ref string) (*url.URL, error) {
//
// A version is a SemVer string (1.2.3-beta.1+f334a6789).
//
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For a chart reference
// * If version is non-empty, this will return the URL for that version
// * If version is empty, this will return the URL for the latest version
// * If no version can be found, an error is returned
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) {
// See if it's already a full URL.
// FIXME: Why do we use url.ParseRequestURI instead of url.Parse?
u, err := url.ParseRequestURI(ref)
if err == nil {
// If it has a scheme and host and path, it's a full URL
......@@ -131,22 +144,54 @@ func (c *ChartDownloader) ResolveChartVersion(ref string) (*url.URL, error) {
}
// See if it's of the form: repo/path_to_chart
p := strings.Split(ref, "/")
if len(p) > 1 {
rf, err := findRepoEntry(p[0], r.Repositories)
if err != nil {
return u, err
}
if rf.URL == "" {
return u, fmt.Errorf("no URL found for repository %q", p[0])
}
baseURL := rf.URL
if !strings.HasSuffix(baseURL, "/") {
baseURL = baseURL + "/"
}
return url.ParseRequestURI(baseURL + strings.Join(p[1:], "/"))
p := strings.SplitN(ref, "/", 2)
if len(p) < 2 {
return u, fmt.Errorf("invalid chart url format: %s", ref)
}
repoName := p[0]
chartName := p[1]
rf, err := findRepoEntry(repoName, r.Repositories)
if err != nil {
return u, err
}
if rf.URL == "" {
return u, fmt.Errorf("no URL found for repository %q", repoName)
}
// Next, we need to load the index, and actually look up the chart.
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(repoName))
if err != nil {
return u, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
}
cv, err := i.Get(chartName, version)
if err != nil {
return u, fmt.Errorf("chart %q not found in %s index. (try 'helm repo update'). %s", chartName, repoName, err)
}
if len(cv.URLs) == 0 {
return u, fmt.Errorf("chart %q has no downloadable URLs", ref)
}
return url.Parse(cv.URLs[0])
}
// urlJoin joins a base URL to one or more path components.
//
// It's like filepath.Join for URLs. If the baseURL is pathish, this will still
// perform a join.
//
// If the URL is unparsable, this returns an error.
func urlJoin(baseURL string, paths ...string) (string, error) {
u, err := url.Parse(baseURL)
if err != nil {
return "", err
}
return u, fmt.Errorf("invalid chart url format: %s", ref)
// We want path instead of filepath because path always uses /.
all := []string{u.Path}
all = append(all, paths...)
u.Path = path.Join(all...)
return u.String(), nil
}
func findRepoEntry(name string, repos []*repo.Entry) (*repo.Entry, error) {
......
......@@ -30,12 +30,14 @@ import (
func TestResolveChartRef(t *testing.T) {
tests := []struct {
name, ref, expect string
fail bool
name, ref, expect, version string
fail bool
}{
{name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"},
{name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"},
{name: "reference, testing repo", ref: "testing/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"},
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz"},
{name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"},
{name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"},
{name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true},
{name: "invalid", ref: "invalid-1.2.3", fail: true},
{name: "not found", ref: "nosuchthing/invalid-1.2.3", fail: true},
......@@ -47,7 +49,7 @@ func TestResolveChartRef(t *testing.T) {
}
for _, tt := range tests {
u, err := c.ResolveChartVersion(tt.ref)
u, err := c.ResolveChartVersion(tt.ref, tt.version)
if err != nil {
if tt.fail {
continue
......@@ -132,12 +134,16 @@ func TestDownloadTo(t *testing.T) {
Keyring: "testdata/helm-test-key.pub",
}
cname := "/signtest-0.1.0.tgz"
v, err := c.DownloadTo(srv.URL()+cname, dest)
where, v, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil {
t.Error(err)
return
}
if expect := filepath.Join(dest, cname); where != expect {
t.Errorf("Expected download to %s, got %s", expect, where)
}
if v.FileHash == "" {
t.Error("File hash was empty, but verification is required.")
}
......@@ -147,3 +153,24 @@ func TestDownloadTo(t *testing.T) {
return
}
}
func TestUrlJoin(t *testing.T) {
tests := []struct {
name, url, expect string
paths []string
}{
{name: "URL, one path", url: "http://example.com", paths: []string{"hello"}, expect: "http://example.com/hello"},
{name: "Long URL, one path", url: "http://example.com/but/first", paths: []string{"slurm"}, expect: "http://example.com/but/first/slurm"},
{name: "URL, two paths", url: "http://example.com", paths: []string{"hello", "world"}, expect: "http://example.com/hello/world"},
{name: "URL, no paths", url: "http://example.com", paths: []string{}, expect: "http://example.com"},
{name: "basepath, two paths", url: "../example.com", paths: []string{"hello", "world"}, expect: "../example.com/hello/world"},
}
for _, tt := range tests {
if got, err := urlJoin(tt.url, tt.paths...); err != nil {
t.Errorf("%s: error %q", tt.name, err)
} else if got != tt.expect {
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
}
}
}
......@@ -184,7 +184,7 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
}
dest := filepath.Join(m.ChartPath, "charts")
if _, err := dl.DownloadTo(churl, dest); err != nil {
if _, _, err := dl.DownloadTo(churl, "", dest); err != nil {
fmt.Fprintf(m.Out, "WARNING: Could not download %s: %s (skipped)", churl, err)
continue
}
......@@ -270,36 +270,61 @@ func urlsAreEqual(a, b string) bool {
return au.String() == bu.String()
}
// findChartURL searches the cache of repo data for a chart that has the name and the repourl specified.
// findChartURL searches the cache of repo data for a chart that has the name and the repoURL specified.
//
// 'name' is the name of the chart. Version is an exact semver, or an empty string. If empty, the
// newest version will be returned.
//
// repourl is the repository to search
// repoURL is the repository to search
//
// If it finds a URL that is "relative", it will prepend the repourl.
func findChartURL(name, version, repourl string, repos map[string]*repo.ChartRepository) (string, error) {
// If it finds a URL that is "relative", it will prepend the repoURL.
func findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (string, error) {
for _, cr := range repos {
if urlsAreEqual(repourl, cr.URL) {
for ename, entry := range cr.IndexFile.Entries {
if ename == name {
for _, verEntry := range entry {
if len(verEntry.URLs) == 0 {
// Not a legit entry.
continue
}
if version == "" || versionEquals(version, verEntry.Version) {
return normalizeURL(repourl, verEntry.URLs[0])
}
return normalizeURL(repourl, verEntry.URLs[0])
}
}
if urlsAreEqual(repoURL, cr.URL) {
entry, err := findEntryByName(name, cr)
if err != nil {
return "", err
}
ve, err := findVersionedEntry(version, entry)
if err != nil {
return "", err
}
return normalizeURL(repoURL, ve.URLs[0])
}
}
return "", fmt.Errorf("chart %s not found in %s", name, repourl)
return "", fmt.Errorf("chart %s not found in %s", name, repoURL)
}
// findEntryByName finds an entry in the chart repository whose name matches the given name.
//
// It returns the ChartVersions for that entry.
func findEntryByName(name string, cr *repo.ChartRepository) (repo.ChartVersions, error) {
for ename, entry := range cr.IndexFile.Entries {
if ename == name {
return entry, nil
}
}
return nil, errors.New("entry not found")
}
// findVersionedEntry takes a ChartVersions list and returns a single chart version that satisfies the version constraints.
//
// If version is empty, the first chart found is returned.
func findVersionedEntry(version string, vers repo.ChartVersions) (*repo.ChartVersion, error) {
for _, verEntry := range vers {
if len(verEntry.URLs) == 0 {
// Not a legit entry.
continue
}
if version == "" || versionEquals(version, verEntry.Version) {
return verEntry, nil
}
return verEntry, nil
}
return nil, errors.New("no matching version")
}
func versionEquals(v1, v2 string) bool {
......
......@@ -20,7 +20,6 @@ import (
"testing"
"k8s.io/helm/cmd/helm/helmpath"
//"k8s.io/helm/pkg/repo"
)
func TestVersionEquals(t *testing.T) {
......
apiVersion: v1
entries:
alpine:
- name: alpine
urls:
- http://example.com/alpine-1.2.3.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://k8s.io/helm
sources:
- https://github.com/kubernetes/helm
version: 1.2.3
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
engine: ""
icon: ""
- name: alpine
urls:
- http://example.com/alpine-0.2.0.tgz
- http://storage.googleapis.com/kubernetes-charts/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://k8s.io/helm
sources:
- https://github.com/kubernetes/helm
version: 0.2.0
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
engine: ""
icon: ""
......@@ -49,6 +49,7 @@ type fetchCmd struct {
untardir string
chartRef string
destdir string
version string
verify bool
keyring string
......@@ -81,6 +82,7 @@ func newFetchCmd(out io.Writer) *cobra.Command {
f.BoolVar(&fch.untar, "untar", false, "If set to true, will untar the chart after downloading it.")
f.StringVar(&fch.untardir, "untardir", ".", "If untar is specified, this flag specifies the name of the directory into which the chart is expanded.")
f.BoolVar(&fch.verify, "verify", false, "Verify the package against its signature.")
f.StringVar(&fch.version, "version", "", "The specific version of a chart. Without this, the latest version is fetched.")
f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "The keyring containing public keys.")
f.StringVarP(&fch.destdir, "destination", "d", ".", "The location to write the chart. If this and tardir are specified, tardir is appended to this.")
......@@ -89,10 +91,6 @@ func newFetchCmd(out io.Writer) *cobra.Command {
func (f *fetchCmd) run() error {
pname := f.chartRef
if filepath.Ext(pname) != ".tgz" {
pname += ".tgz"
}
c := downloader.ChartDownloader{
HelmHome: helmpath.Home(homePath()),
Out: f.out,
......@@ -116,7 +114,7 @@ func (f *fetchCmd) run() error {
defer os.RemoveAll(dest)
}
v, err := c.DownloadTo(pname, dest)
saved, v, err := c.DownloadTo(pname, f.version, dest)
if err != nil {
return err
}
......@@ -140,8 +138,7 @@ func (f *fetchCmd) run() error {
return fmt.Errorf("Failed to untar: %s is not a directory", ud)
}
from := filepath.Join(dest, filepath.Base(pname))
return chartutil.ExpandFile(ud, from)
return chartutil.ExpandFile(ud, saved)
}
return nil
}
......
......@@ -49,38 +49,51 @@ func TestFetchCmd(t *testing.T) {
}{
{
name: "Basic chart fetch",
chart: "test/signtest-0.1.0",
chart: "test/signtest",
expectFile: "./signtest-0.1.0.tgz",
},
{
name: "Chart fetch with version",
chart: "test/signtest",
flags: []string{"--version", "0.1.0"},
expectFile: "./signtest-0.1.0.tgz",
},
{
name: "Fail chart fetch with non-existent version",
chart: "test/signtest",
flags: []string{"--version", "99.1.0"},
fail: true,
failExpect: "no such chart",
},
{
name: "Fail fetching non-existent chart",
chart: "test/nosuchthing-0.1.0",
chart: "test/nosuchthing",
failExpect: "Failed to fetch",
fail: true,
},
{
name: "Fetch and verify",
chart: "test/signtest-0.1.0",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"},
expectFile: "./signtest-0.1.0.tgz",
},
{
name: "Fetch and fail verify",
chart: "test/reqtest-0.1.0",
chart: "test/reqtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"},
failExpect: "Failed to fetch provenance",
fail: true,
},
{
name: "Fetch and untar",
chart: "test/signtest-0.1.0",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
expectFile: "./signtest",
expectDir: true,
},
{
name: "Fetch, verify, untar",
chart: "test/signtest-0.1.0",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
expectFile: "./signtest",
expectDir: true,
......@@ -93,8 +106,9 @@ func TestFetchCmd(t *testing.T) {
if _, err := srv.CopyCharts("testdata/testcharts/*.tgz*"); err != nil {
t.Fatal(err)
}
t.Logf("HELM_HOME=%s", homePath())
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
for _, tt := range tests {
outdir := filepath.Join(hh, "testout")
......
......@@ -28,7 +28,8 @@ import (
)
const inspectDesc = `
This command inspects a chart (directory, file, or URL) and displays information.
This command inspects a chart and displays information. It takes a chart reference
('stable/drupal'), a full path to a directory or packaged chart, or a URL.
Inspect prints the contents of the Chart.yaml file and the values.yaml file.
`
......@@ -50,6 +51,7 @@ type inspectCmd struct {
keyring string
out io.Writer
client helm.Interface
version string
}
const (
......@@ -73,7 +75,7 @@ func newInspectCmd(c helm.Interface, out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(args[0], insp.verify, insp.keyring)
cp, err := locateChartPath(args[0], insp.version, insp.verify, insp.keyring)
if err != nil {
return err
}
......@@ -88,7 +90,7 @@ func newInspectCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: inspectValuesDesc,
RunE: func(cmd *cobra.Command, args []string) error {
insp.output = valuesOnly
cp, err := locateChartPath(args[0], insp.verify, insp.keyring)
cp, err := locateChartPath(args[0], insp.version, insp.verify, insp.keyring)
if err != nil {
return err
}
......@@ -103,7 +105,7 @@ func newInspectCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: inspectChartDesc,
RunE: func(cmd *cobra.Command, args []string) error {
insp.output = chartOnly
cp, err := locateChartPath(args[0], insp.verify, insp.keyring)
cp, err := locateChartPath(args[0], insp.version, insp.verify, insp.keyring)
if err != nil {
return err
}
......@@ -125,6 +127,12 @@ func newInspectCmd(c helm.Interface, out io.Writer) *cobra.Command {
valuesSubCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
chartSubCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
verflag := "version"
verdesc := "the version of the chart. By default, the newest chart is shown."
inspectCommand.Flags().StringVar(&insp.version, verflag, "", verdesc)
valuesSubCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
chartSubCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
inspectCommand.AddCommand(valuesSubCmd)
inspectCommand.AddCommand(chartSubCmd)
......
......@@ -48,11 +48,11 @@ name of a chart in the current working directory.
To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line.
$ helm install -f myvalues.yaml redis
$ helm install -f myvalues.yaml ./redis
or
$ helm install --set name=prod redis
$ helm install --set name=prod ./redis
To check the generated manifests of a release without installing the chart,
the '--debug' and '--dry-run' flags can be combined. This will still require a
......@@ -60,6 +60,26 @@ round-trip to the Tiller server.
If --verify is set, the chart MUST have a provenance file, and the provenenace
fall MUST pass all verification steps.
There are four different ways you can express the chart you want to install:
1. By chart reference: helm install stable/mariadb
2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz
3. By path to an unpacked chart directory: helm install ./nginx
4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz
CHART REFERENCES
A chart reference is a convenient way of reference a chart in a chart repository.
When you use a chart reference ('stable/mariadb'), Helm will look in the local
configuration for a chart repository named 'stable', and will then look for a
chart in that repository whose name is 'mariadb'. It will install the latest
version of that chart unless you also supply a version number with the
'--version' flag.
To see the list of chart repositories, use 'helm repo list'. To search for
charts in a repository, use 'helm search'.
`
type installCmd struct {
......@@ -76,6 +96,7 @@ type installCmd struct {
client helm.Interface
values *values
nameTemplate string
version string
}
func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
......@@ -94,7 +115,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(args[0], inst.verify, inst.keyring)
cp, err := locateChartPath(args[0], inst.version, inst.verify, inst.keyring)
if err != nil {
return err
}
......@@ -116,6 +137,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it")
f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed.")
return cmd
}
......@@ -276,9 +298,12 @@ func splitPair(item string) (name string, value interface{}) {
// - 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(name string, verify bool, keyring string) (string, error) {
func locateChartPath(name, version string, verify bool, keyring string) (string, error) {
name = strings.TrimSpace(name)
version = strings.TrimSpace(version)
if fi, err := os.Stat(name); err == nil {
abs, err := filepath.Abs(name)
if err != nil {
......@@ -303,12 +328,6 @@ func locateChartPath(name string, verify bool, keyring string) (string, error) {
return filepath.Abs(crepo)
}
// Try fetching the chart from a remote repo into a tmpdir
origname := name
if filepath.Ext(name) != ".tgz" {
name += ".tgz"
}
dl := downloader.ChartDownloader{
HelmHome: helmpath.Home(homePath()),
Out: os.Stdout,
......@@ -318,16 +337,19 @@ func locateChartPath(name string, verify bool, keyring string) (string, error) {
dl.Verify = downloader.VerifyAlways
}
if _, err := dl.DownloadTo(name, "."); err == nil {
lname, err := filepath.Abs(filepath.Base(name))
filename, _, err := dl.DownloadTo(name, version, ".")
if err == nil {
lname, err := filepath.Abs(filename)
if err != nil {
return lname, err
return filename, err
}
fmt.Printf("Fetched %s to %s\n", origname, lname)
fmt.Printf("Fetched %s to %s\n", name, filename)
return lname, nil
} else if flagDebug {
return filename, err
}
return name, fmt.Errorf("file %q not found", origname)
return filename, fmt.Errorf("file %q not found", name)
}
func generateName(nameTemplate string) (string, error) {
......
......@@ -97,6 +97,9 @@ func (s *searchCmd) showAllCharts(i *search.Index) {
}
func (s *searchCmd) formatSearchResults(res []*search.Result) string {
if len(res) == 0 {
return "No results found"
}
table := uitable.New()
table.MaxColWidth = 50
table.AddRow("NAME", "VERSION", "DESCRIPTION")
......@@ -119,7 +122,7 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
f := s.helmhome.CacheIndex(n)
ind, err := repo.LoadIndexFile(f)
if err != nil {
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update':\n\t%s\n", f, err)
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
continue
}
......
......@@ -50,7 +50,7 @@ func TestSearchCmd(t *testing.T) {
{
name: "search for 'syzygy', expect no matches",
args: []string{"syzygy"},
expect: "NAME\tVERSION\tDESCRIPTION",
expect: "No results found",
},
{
name: "search for 'alp[a-z]+', expect two matches",
......
......@@ -33,7 +33,9 @@ const upgradeDesc = `
This command upgrades a release to a new version of a chart.
The upgrade arguments must be a release and a chart. The chart
argument can be a relative path to a packaged or unpackaged chart.
argument can a chart reference ('stable/mariadb'), a path to a chart directory
or packaged chart, or a fully qualified URL. For chart references, the latest
version will be specified unless the '--version' flag is set.
To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line.
......@@ -52,6 +54,7 @@ type upgradeCmd struct {
keyring string
install bool
namespace string
version string
}
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
......@@ -89,12 +92,13 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "the path to the keyring that contains public singing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "default", "the namespace to install the release into (only used if --install is set)")
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used.")
return cmd
}
func (u *upgradeCmd) run() error {
chartPath, err := locateChartPath(u.chart, u.verify, u.keyring)
chartPath, err := locateChartPath(u.chart, u.version, u.verify, u.keyring)
if err != nil {
return err
}
......
......@@ -7,6 +7,11 @@ This guide covers how you can quickly get started using Helm.
- You must have Kubernetes installed, and have a local configured copy
of `kubectl`.
Helm will figure out where to install Tiller by reading your Kubernetes
configuration file (usually `$HOME/.kube/config`). This is the same file
that `kubectl` uses, so to find out which cluster Tiller would install
to, you can run `kubectl cluster-info`.
## Install Helm
Download a binary release of the Helm client from
......@@ -27,20 +32,19 @@ $ helm init
## Install an Example Chart
To install a chart, you can run the `helm install` command.
Let's use an example chart from this repository.
To install a chart, you can run the `helm install` command.
Let's use an example chart from this repository.
Make sure you are in the root directory of this repo.
```console
$ helm install docs/examples/alpine
$ helm install stable/mysql
Released smiling-penguin
```
In the example above, the `alpine` chart was released, and the name of
our new release is `smiling-penguin`. You can view the details of the chart we just
installed by taking a look at the nginx chart in
[docs/examples/alpine/Chart.yaml](examples/alpine/Chart.yaml).
In the example above, the `stable/mysql` chart was released, and the name of
our new release is `smiling-penguin`. You get a simple idea of this
MySQL chart by running `helm inspect stable/mysql`.
## Change a Default Chart Value
......@@ -48,7 +52,7 @@ A nice feature of helm is the ability to change certain values of the package fo
Let's install the `nginx` example from this repository but change the `replicaCount` to 7.
```console
$ helm install --set replicaCount=7 docs/examples/nginx
$ helm install --set replicaCount=7 ./docs/examples/nginx
happy-panda
```
......@@ -65,6 +69,9 @@ $ helm status smiling-penguin
Status: DEPLOYED
```
The `status` command will display information about a release in your
cluster.
## Uninstall a Release
To uninstall a release, use the `helm delete` command:
......
......@@ -181,7 +181,7 @@ To see what options are configurable on a chart, use `helm inspect
values`:
```console
helm inspect values stable/mariadb-0.3.0.tgz
helm inspect values stable/mariadb
Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz
## Bitnami MariaDB image version
## ref: https://hub.docker.com/r/bitnami/mariadb/tags/
......@@ -235,7 +235,7 @@ complex, Helm tries to perform the least invasive upgrade. It will only
update things that have changed since the last release.
```console
$ helm upgrade -f panda.yaml happy-panda stable/mariadb-0.3.0.tgz 1 ↵
$ helm upgrade -f panda.yaml happy-panda stable/mariadb
Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz
happy-panda has been upgraded. Happy Helming!
Last Deployed: Wed Sep 28 12:47:54 2016
......
......@@ -60,6 +60,8 @@ type Verification struct {
SignedBy *openpgp.Entity
// FileHash is the hash, prepended with the scheme, for the file that was verified.
FileHash string
// FileName is the name of the file that FileHash verifies.
FileName string
}
// Signatory signs things.
......@@ -221,6 +223,7 @@ func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
return ver, fmt.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
}
ver.FileHash = sum
ver.FileName = basename
// TODO: when image signing is added, verify that here.
......
......@@ -18,6 +18,7 @@ package provenance
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
......@@ -246,6 +247,8 @@ func TestVerify(t *testing.T) {
t.Error("Verification is missing hash.")
} else if ver.SignedBy == nil {
t.Error("No SignedBy field")
} else if ver.FileName != filepath.Base(testChartfile) {
t.Errorf("FileName is unexpectedly %q", ver.FileName)
}
if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
......
......@@ -138,7 +138,10 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
if !ok {
return nil, ErrNoChartName
}
if version == "" && len(vs) > 0 {
if len(vs) == 0 {
return nil, ErrNoChartVersion
}
if len(version) == 0 {
return vs[0], nil
}
for _, ver := range vs {
......@@ -147,7 +150,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
return ver, nil
}
}
return nil, ErrNoChartVersion
return nil, fmt.Errorf("No chart version found for %s-%s", name, version)
}
// WriteFile writes an index file to the given destination path.
......
......@@ -123,8 +123,6 @@ func (s *Server) CreateIndex() error {
return err
}
println(string(d))
ifile := filepath.Join(s.docroot, "index.yaml")
return ioutil.WriteFile(ifile, d, 0755)
}
......@@ -148,11 +146,23 @@ func (s *Server) URL() string {
return s.srv.URL
}
// LinkIndices links the index created with CreateIndex and makes a symboic link to the repositories/cache directory.
//
// This makes it possible to simulate a local cache of a repository.
func (s *Server) LinkIndices() error {
destfile := "test-index.yaml"
// Link the index.yaml file to the
lstart := filepath.Join(s.docroot, "index.yaml")
ldest := filepath.Join(s.docroot, "repository/cache", destfile)
return os.Symlink(lstart, ldest)
}
// setTestingRepository sets up a testing repository.yaml with only the given name/URL.
func setTestingRepository(helmhome, name, url string) error {
rf := repo.NewRepoFile()
rf.Add(&repo.Entry{Name: name, URL: url})
os.MkdirAll(filepath.Join(helmhome, "repository", name), 0755)
dest := filepath.Join(helmhome, "repository/repositories.yaml")
return rf.WriteFile(dest, 0644)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment