diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto index aeb92e7b8..c8b5e75c3 100644 --- a/_proto/hapi/chart/metadata.proto +++ b/_proto/hapi/chart/metadata.proto @@ -37,4 +37,7 @@ message Metadata { // A list of name and URL/email address combinations for the maintainer(s) repeated Maintainer maintainers = 7; + + // The name of the template engine to use. Defaults to 'gotpl'. + string engine = 8; } diff --git a/cmd/helm/create.go b/cmd/helm/create.go index 1cf1e949c..fff8778ae 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -55,9 +55,6 @@ func runCreate(cmd *cobra.Command, args []string) error { Version: "0.1.0", } - if _, err := chart.Create(&cfile, filepath.Dir(cname)); err != nil { - return err - } - - return nil + _, err := chart.Create(&cfile, filepath.Dir(cname)) + return err } diff --git a/cmd/helm/delete.go b/cmd/helm/delete.go index dabc11c76..73187ee82 100644 --- a/cmd/helm/delete.go +++ b/cmd/helm/delete.go @@ -18,12 +18,13 @@ deleting them. var deleteDryRun bool var deleteCommand = &cobra.Command{ - Use: "delete [flags] RELEASE_NAME", - Aliases: []string{"del"}, - SuggestFor: []string{"remove", "rm"}, - Short: "Given a release name, delete the release from Kubernetes", - Long: deleteDesc, - RunE: delRelease, + Use: "delete [flags] RELEASE_NAME", + Aliases: []string{"del"}, + SuggestFor: []string{"remove", "rm"}, + Short: "Given a release name, delete the release from Kubernetes", + Long: deleteDesc, + RunE: delRelease, + PersistentPreRunE: setupConnection, } func init() { diff --git a/cmd/helm/get.go b/cmd/helm/get.go index 9bc976d8f..015cb815b 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -46,10 +46,11 @@ var getOut = "" var errReleaseRequired = errors.New("release name is required") var getCommand = &cobra.Command{ - Use: "get [flags] RELEASE_NAME", - Short: "Download a named release", - Long: getHelp, - RunE: getCmd, + Use: "get [flags] RELEASE_NAME", + Short: "Download a named release", + Long: getHelp, + RunE: getCmd, + PersistentPreRunE: setupConnection, } var getValuesCommand = &cobra.Command{ diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 9590ead7e..0c0a81f0c 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -2,6 +2,7 @@ package main import ( "errors" + "flag" "fmt" "os" "strings" @@ -12,17 +13,15 @@ import ( ) const ( - homeEnvVar = "HELM_HOME" - defaultHome = "$HOME/.helm" // FIXME: is $HOME windows compatible? - hostEnvVar = "HELM_HOST" - defaultHost = ":44134" + homeEnvVar = "HELM_HOME" + hostEnvVar = "HELM_HOST" ) var helmHome string var tillerHost string -// flagVerbose is a signal that the user wants additional output. -var flagVerbose bool +// flagDebug is a signal that the user wants additional output. +var flagDebug bool var globalUsage = `The Kubernetes package manager @@ -35,22 +34,22 @@ It will also set up any necessary local configuration. Common actions from this point include: -- helm search: search for charts -- helm fetch: download a chart to your local directory to view -- helm install: upload the chart to Kubernetes -- helm list: list releases of charts +- helm search: search for charts +- helm fetch: download a chart to your local directory to view +- helm install: upload the chart to Kubernetes +- helm list: list releases of charts Environment: - $HELM_HOME Set an alternative location for Helm files. By default, these are stored in ~/.helm - $HELM_HOST Set an alternative Tiller host. The format is host:port (default ":44134"). + $HELM_HOME Set an alternative location for Helm files. By default, these are stored in ~/.helm + $HELM_HOST Set an alternative Tiller host. The format is host:port (default ":44134"). ` // RootCommand is the top-level command for Helm. var RootCommand = &cobra.Command{ - Use: "helm", - Short: "The Helm package manager for Kubernetes.", - Long: globalUsage, - PersistentPreRun: bootstrap, + Use: "helm", + Short: "The Helm package manager for Kubernetes.", + Long: globalUsage, + PersistentPostRun: teardown, } func init() { @@ -59,13 +58,11 @@ func init() { home = "$HOME/.helm" } thost := os.Getenv(hostEnvVar) - if thost == "" { - thost = defaultHost - } p := RootCommand.PersistentFlags() p.StringVar(&helmHome, "home", home, "location of your Helm config. Overrides $HELM_HOME.") p.StringVar(&tillerHost, "host", thost, "address of tiller. Overrides $HELM_HOST.") - p.BoolVarP(&flagVerbose, "verbose", "v", false, "enable verbose output") + p.BoolVarP(&flagDebug, "debug", "", false, "enable verbose output") + p.AddGoFlagSet(flag.CommandLine) } func main() { @@ -74,12 +71,32 @@ func main() { } } -func bootstrap(c *cobra.Command, args []string) { +func setupConnection(c *cobra.Command, args []string) error { + if tillerHost == "" { + // Should failure fall back to default host? + tunnel, err := newTillerPortForwarder() + if err != nil { + return err + } + + tillerHost = fmt.Sprintf(":%d", tunnel.Local) + if flagDebug { + fmt.Printf("Created tunnel using local port: '%d'\n", tunnel.Local) + } + } + // Set up the gRPC config. helm.Config.ServAddr = tillerHost - if flagVerbose { + if flagDebug { fmt.Printf("Server: %q\n", helm.Config.ServAddr) } + return nil +} + +func teardown(c *cobra.Command, args []string) { + if tunnel != nil { + tunnel.Close() + } } func checkArgsLength(expectedNum, actualNum int, requiredArgs ...string) error { diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 2b5e90d94..0a2e0d2da 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -15,10 +15,12 @@ Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.he ` var ( - tillerImg string - tillerNamespace string - clientOnly bool - initSkipNamespace bool + tillerImg string + tillerNamespace string + clientOnly bool + initSkipNamespace bool + defaultRepository = "kubernetes-charts" + defaultRepositoryURL = "http://storage.googleapis.com/kubernetes-charts" ) func init() { @@ -63,7 +65,7 @@ func installTiller() error { i := client.NewInstaller() i.Tiller["Image"] = tillerImg i.Tiller["Namespace"] = tillerNamespace - err := i.Install(flagVerbose, !initSkipNamespace) + err := i.Install(flagDebug, !initSkipNamespace) if err != nil { return fmt.Errorf("error installing: %s", err) @@ -96,7 +98,7 @@ func ensureHome() error { if _, err := os.Create(repoFile); err != nil { return err } - if err := insertRepoLine("local", "http://localhost:8879/charts"); err != nil { + if err := addRepository(defaultRepository, defaultRepositoryURL); err != nil { return err } } else if fi.IsDir() { diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 19414478f..fba99e2c6 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -1,12 +1,21 @@ package main import ( + "fmt" "io/ioutil" + "net/http" + "net/http/httptest" "os" "testing" ) func TestEnsureHome(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "OK") + })) + defaultRepositoryURL = ts.URL + home := createTmpHome() helmHome = home if err := ensureHome(); err != nil { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 3863a0a69..7ff8886a6 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -24,8 +24,6 @@ chart in the current working directory. // install flags & args var ( - // installArg is the name or relative path of the chart to install - installArg string // installDryRun performs a dry-run install installDryRun bool // installValues is the filename of supplied values. @@ -35,10 +33,11 @@ var ( ) var installCmd = &cobra.Command{ - Use: "install [CHART]", - Short: "install a chart archive.", - Long: installDesc, - RunE: runInstall, + Use: "install [CHART]", + Short: "install a chart archive.", + Long: installDesc, + RunE: runInstall, + PersistentPreRunE: setupConnection, } func init() { @@ -58,7 +57,7 @@ func runInstall(cmd *cobra.Command, args []string) error { if err != nil { return err } - if flagVerbose { + if flagDebug { fmt.Printf("Chart path: %s\n", chartpath) } @@ -88,7 +87,7 @@ func printRelease(rel *release.Release) { if rel == nil { return } - if flagVerbose { + if flagDebug { fmt.Printf("NAME: %s\n", rel.Name) fmt.Printf("INFO: %s %s\n", timeconv.String(rel.Info.LastDeployed), rel.Info.Status) fmt.Printf("CHART: %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 1f1bd0fca..2a8751845 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -37,11 +37,12 @@ flag with the '--offset' flag allows you to page through results. ` var listCommand = &cobra.Command{ - Use: "list [flags] [FILTER]", - Short: "List releases", - Long: listHelp, - RunE: listCmd, - Aliases: []string{"ls"}, + Use: "list [flags] [FILTER]", + Short: "List releases", + Long: listHelp, + RunE: listCmd, + Aliases: []string{"ls"}, + PersistentPreRunE: setupConnection, } var ( diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 880b11fe4..f5ac902d8 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -60,7 +60,7 @@ func runPackage(cmd *cobra.Command, args []string) error { return err } name, err := chart.Save(ch, cwd) - if err == nil && flagVerbose { + if err == nil && flagDebug { cmd.Printf("Saved %s to current directory\n", name) } @@ -69,7 +69,7 @@ func runPackage(cmd *cobra.Command, args []string) error { if save { if err := repo.AddChartToLocalRepo(ch, localRepoDirectory()); err != nil { return err - } else if flagVerbose { + } else if flagDebug { cmd.Printf("Saved %s to %s\n", name, localRepoDirectory()) } } diff --git a/cmd/helm/repo.go b/cmd/helm/repo.go index cb09b0046..cce323e1c 100644 --- a/cmd/helm/repo.go +++ b/cmd/helm/repo.go @@ -55,15 +55,11 @@ func runRepoAdd(cmd *cobra.Command, args []string) error { } name, url := args[0], args[1] - if err := repo.DownloadIndexFile(name, url, cacheDirectory(name, "-index.yaml")); err != nil { - return errors.New("Oops! Looks like " + url + " is not a valid chart repository or cannot be reached\n") - } - - if err := insertRepoLine(name, url); err != nil { + if err := addRepository(name, url); err != nil { return err } - fmt.Println(args[0] + " has been added to your repositories") + fmt.Println(name + " has been added to your repositories") return nil } @@ -89,10 +85,7 @@ func runRepoRemove(cmd *cobra.Command, args []string) error { if err := checkArgsLength(1, len(args), "name of chart repository"); err != nil { return err } - if err := removeRepoLine(args[0]); err != nil { - return err - } - return nil + return removeRepoLine(args[0]) } func runRepoIndex(cmd *cobra.Command, args []string) error { @@ -105,11 +98,7 @@ func runRepoIndex(cmd *cobra.Command, args []string) error { return err } - if err := index(path, args[1]); err != nil { - return err - } - - return nil + return index(path, args[1]) } func index(dir, url string) error { @@ -118,10 +107,15 @@ func index(dir, url string) error { return err } - if err := chartRepo.Index(); err != nil { - return err + return chartRepo.Index() +} + +func addRepository(name, url string) error { + if err := repo.DownloadIndexFile(name, url, cacheDirectory(name+"-index.yaml")); err != nil { + return errors.New("Looks like " + url + " is not a valid chart repository or cannot be reached: " + err.Error()) } - return nil + + return insertRepoLine(name, url) } func removeRepoLine(name string) error { @@ -165,9 +159,5 @@ func insertRepoLine(name, url string) error { f.Repositories[name] = url b, _ := yaml.Marshal(&f.Repositories) - if err := ioutil.WriteFile(repositoriesFile(), b, 0666); err != nil { - return err - } - - return nil + return ioutil.WriteFile(repositoriesFile(), b, 0666) } diff --git a/cmd/helm/repo_test.go b/cmd/helm/repo_test.go index 972051108..b65013e10 100644 --- a/cmd/helm/repo_test.go +++ b/cmd/helm/repo_test.go @@ -1,6 +1,12 @@ package main import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" "testing" "github.com/kubernetes/helm/pkg/repo" @@ -12,13 +18,21 @@ var ( ) func TestRepoAdd(t *testing.T) { - home := createTmpHome() - helmHome = home - if err := ensureHome(); err != nil { - t.Errorf("%s", err) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "OK") + })) + + helmHome, _ = ioutil.TempDir("", "helm_home") + defer os.Remove(helmHome) + os.Mkdir(filepath.Join(helmHome, repositoryDir), 0755) + os.Mkdir(cacheDirectory(), 0755) + + if err := ioutil.WriteFile(repositoriesFile(), []byte("example-repo: http://exampleurl.com"), 0666); err != nil { + t.Errorf("%#v", err) } - if err := insertRepoLine(testName, testURL); err != nil { + if err := addRepository(testName, ts.URL); err != nil { t.Errorf("%s", err) } @@ -31,7 +45,7 @@ func TestRepoAdd(t *testing.T) { t.Errorf("%s was not successfully inserted into %s", testName, repositoriesFile()) } - if err := insertRepoLine(testName, testURL); err == nil { + if err := insertRepoLine(testName, ts.URL); err == nil { t.Errorf("Duplicate repository name was added") } diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 7a14cc6f0..6e6afa97c 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -13,10 +13,11 @@ This command shows the status of a named release. ` var statusCommand = &cobra.Command{ - Use: "status [flags] RELEASE_NAME", - Short: "Displays the status of the named release", - Long: statusHelp, - RunE: status, + Use: "status [flags] RELEASE_NAME", + Short: "Displays the status of the named release", + Long: statusHelp, + RunE: status, + PersistentPreRunE: setupConnection, } func init() { diff --git a/cmd/helm/tunnel.go b/cmd/helm/tunnel.go new file mode 100644 index 000000000..a626ff59f --- /dev/null +++ b/cmd/helm/tunnel.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/labels" + + "github.com/kubernetes/helm/pkg/kube" +) + +// TODO refactor out this global var +var tunnel *kube.Tunnel + +func newTillerPortForwarder() (*kube.Tunnel, error) { + podName, err := getTillerPodName("helm") + if err != nil { + return nil, err + } + // FIXME use a constain that is accessible on init + const tillerPort = 44134 + return kube.New(nil).ForwardPort("helm", podName, tillerPort) +} + +func getTillerPodName(namespace string) (string, error) { + client, err := kube.New(nil).Client() + if err != nil { + return "", err + } + + // TODO use a const for labels + selector := labels.Set{"app": "helm", "name": "tiller"}.AsSelector() + options := api.ListOptions{LabelSelector: selector} + pods, err := client.Pods(namespace).List(options) + if err != nil { + return "", err + } + if len(pods.Items) < 1 { + return "", fmt.Errorf("I could not find tiller") + } + return pods.Items[0].ObjectMeta.GetName(), nil +} diff --git a/cmd/helm/version.go b/cmd/helm/version.go new file mode 100644 index 000000000..78556f6fb --- /dev/null +++ b/cmd/helm/version.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + + "github.com/kubernetes/helm/pkg/version" + + "github.com/spf13/cobra" +) + +func init() { + RootCommand.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the client version information.", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.Version) + }, +} diff --git a/docs/architecture.md b/docs/architecture.md index 3cf6c186b..de69b7553 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -22,7 +22,7 @@ For Helm, there are three important concepts: 3. A _release_ is a running instance of a _chart_, combined with a specific _config_. -Following the formula made famous by the 12 Factor App, _chart + config +Following the formula made famous by the [12 Factor App](http://12factor.net/), _chart + config = release_. ## Components diff --git a/docs/charts.md b/docs/charts.md index 17a779fc1..1dac3ab67 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -1,7 +1,16 @@ # Charts Helm uses a packaging format called _charts_. A chart is a collection of files -that collectively describe a set of Kubernetes resources. +that describe a related set of Kubernetes resources. A single chart +might be used to deploy something simple, like a memcached pod, or +something complex, like a full web app stack with HTTP servers, +databases, caches, and so on. + +Charts are created as files laid out in a particular directory tree, +then they can be packaged into versioned archives to be deployed. + +This document explains the chart format, and provides basic guidance for +building charts with Helm. ## The Chart File Structure @@ -14,17 +23,19 @@ Inside of this directory, Helm will expect a structure that matches this: ``` wordpress/ Chart.yaml # A YAML file containing information about the chart - LICENSE # A plain text file containing the license for the chart - README.md # A human-readable README file + LICENSE # OPTIONAL: A plain text file containing the license for the chart + README.md # OPTIONAL: A human-readable README file values.toml # The default configuration values for this chart charts/ # A directory containing any charts upon which this chart depends. templates/ # A directory of templates that, when combined with values, # will generate valid Kubernetes manifest files. ``` +Helm will silently strip out any other files. + ## The Chart.yaml File -The Chart.yaml file is required for a chart. It contains the following fields: +The `Chart.yaml` file is required for a chart. It contains the following fields: ```yaml name: The name of the chart (required) @@ -38,12 +49,15 @@ sources: maintainers: # (optional) - name: The maintainer's name (required for each maintainer) email: The maintainer's email (optional for each maintainer) +engine: gotpl # The name of the template engine (optional, defaults to gotpl) ``` -If you are familiar with the Chart.yaml file format for Helm Classic, you will +If you are familiar with the `Chart.yaml` file format for Helm Classic, you will notice that fields specifying dependencies have been removed. That is because the new Chart format expresses dependencies using the `charts/` directory. +Other fields will be silently ignored. + ### Charts and Versioning Every chart must have a version number. A version must follow the @@ -51,12 +65,38 @@ Every chart must have a version number. A version must follow the Helm uses version numbers as release markers. Packages in repositories are identified by name plus version. +For example, an `nginx` chart whose version field is set to `version: +1.2.3` will be named: + +``` +nginix-1.2.3.tgz +``` + +More complex SemVer 2 names are also supported, such as +`version: 1.2.3-alpha.1+ef365`. But non-SemVer names are explicitly +disallowed by the system. + +**NOTE:** Whereas Helm Classic and Deployment Manager were both +very GitHub oriented when it came to charts, Kubernetes Helm does not +rely upon or require GitHub or even Git. Consequently, it does not use +Git SHAs for versioning at all. + +The `version` field inside of the `Chart.yaml` is used by many of the +Helm tools, including the CLI and the Tiller server. When generating a +package, the `helm package` command will use the version that it finds +in the `Chart.yaml` as a token in the package name. The system assumes +that the version number in the chart package name matches the version number in +the `Chart.yaml`. Failure to meet this assumption will cause an error. + ## Chart Dependencies In Helm, one chart may depend on any number of other charts. These dependencies are expressed explicitly by copying the dependency charts into the `charts/` directory. +**Note:** The `dependencies:` section of the `Glide.yaml` from Helm +Classic has been completely removed. + For example, if the Wordpress chart depends on the Apache chart, the Apache chart (of the correct version) is supplied in the Wordpress chart's `charts/` directory: @@ -83,9 +123,10 @@ directory. ## Templates and Values -In Helm Charts, templates are written in the Go template language, with the +By default, Helm Chart templates are written in the Go template language, with the addition of 50 or so [add-on template -functions](https://github.com/Masterminds/sprig). +functions](https://github.com/Masterminds/sprig). (In the future, Helm +may support other template languages, like Python Jinja.) All template files are stored in a chart's `templates/` folder. When Helm renders the charts, it will pass every file in that directory @@ -99,7 +140,6 @@ Values for the templates are supplied two ways: When a user supplies custom values, these values will override the values in the chart's `values.toml` file. - ### Template Files Template files follow the standard conventions for writing Go templates. @@ -134,8 +174,7 @@ spec: value: {{default "minio" .storage}} ``` -The above example, based loosely on [https://github.com/deis/charts](the -chart for Deis), is a template for a Kubernetes replication controller. +The above example, based loosely on [https://github.com/deis/charts](https://github.com/deis/charts), is a template for a Kubernetes replication controller. It can use the following four template values: - `imageRegistry`: The source registry for the Docker image. @@ -146,6 +185,26 @@ It can use the following four template values: All of these values are defined by the template author. Helm does not require or dictate parameters. +### Predefined Values + +The following values are pre-defined, are available to every template, and +cannot be overridden. As with all values, the names are _case +sensitive_. + +- `Release.Name`: The name of the release (not the chart) +- `Release.Time`: The time the chart release was last updated. This will + match the `Last Released` time on a Release object. +- `Release.Namespace`: The namespace the chart was released to. +- `Release.Service`: The service that conducted the release. Usually + this is `Tiller`. +- `Chart`: The contents of the `Chart.yaml`. Thus, the chart version is + obtainable as `Chart.Version` and the maintainers are in + `Chart.Maintainers`. + +**NOTE:** Any unknown Chart.yaml fields will be dropped. They will not +be accessible inside of the `Chart` object. Thus, Chart.yaml cannot be +used to pass arbitrarily structured data into the template. + ### Values files Considering the template in the previous section, a `values.toml` file @@ -158,23 +217,76 @@ pullPolicy = "alwaysPull" storage = "s3" ``` -When a chart includes dependency charts, values can be supplied to those -charts using TOML tables: +A values file is formatted in TOML. A chart may include a default +`values.toml` file. The Helm install command allows a user to override +values by supplying additional TOML values: + +```console +$ helm install --values=myvals.toml wordpress +``` + +When values are passed in this way, they will be merged into the default +values file. For example, consider a `myvals.toml` file that looks like +this: + +```toml +storage = "gcs" +``` + +When this is merged with the `values.toml` in the chart, the resulting +generated content will be: ```toml imageRegistry = "quay.io/deis" dockerTag = "latest" pullPolicy = "alwaysPull" -storage = "s3" +storage = "gcs" +``` + +Note that only the last field was overridden. + +**NOTE:** The default values file included inside of a chart _must_ be named +`values.toml`. But files specified on the command line can be named +anything. + +### Scope, Dependencies, and Values + +Values files can declare values for the top-level chart, as well as for +any of the charts that are included in that chart's `charts/` directory. +Or, to phrase it differently, a values file can supply values to the +chart as well as to any of its dependencies. For example, the +demonstration Wordpress chart above has both `mysql` and `apache` as +dependencies. The values file could supply values to all of these +components: + +```toml +title = "My Wordpress Site" # Sent to the Wordpress template -[router] -hostname = "example.com" +[mysql] +max_connections = 100 # Sent to MySQL +password = "secret" + +[apache] +port = 8080 # Passed to Apache ``` -In the above example, the value of `hostname` will be passed to a chart -named `router` (if it exists) in the `charts/` directory. +Charts at a higher level have access to all of the variables defined +beneath. So the wordpress chart can access `mysql.password`. But lower +level charts cannot access things in parent charts, so MySQL will not be +able to access the `title` property. Nor, for that matter, can it access +`apache.port`. + +Values are namespaced, but namespaces are pruned. So for the Wordpress +chart, it can access the MySQL password field as `mysql.password`. But +for the MySQL chart, the scope of the values has been reduced and the +namespace prefix removed, so it will see the password field simply as +`password`. ### References + +When it comes to writing templates and values files, there are several +standard references that will help you out. + - [Go templates](https://godoc.org/text/template) - [Extra template functions](https://godoc.org/github.com/Masterminds/sprig) - [The TOML format](https://github.com/toml-lang/toml) @@ -205,3 +317,28 @@ formatting or information: $ helm lint mychart No issues found ``` + +## Chart Repositories + +A _chart repository_ is an HTTP server that houses one or more packaged +charts. While `helm` can be used to manage local chart directories, when +it comes to sharing charts, the preferred mechanism is a chart +repository. + +Any HTTP server that can serve YAML files and tar files and can answer +GET requests can be used as a repository server. + +Helm comes with built-in package server for developer testing (`helm +serve`). The Helm team has tested other servers, including Google Cloud +Storage with website mode enabled, and S3 with website mode enabled. + +A repository is characterized primarily by the presence of a special +file called `index.yaml` that has a list of all of the packages supplied +by the repository, together with metadata that allows retrieving and +verifying those packages. + +On the client side, repositories are managed with the `helm repo` +commands. However, Helm does not provide tools for uploading charts to +remote repository servers. This is because doing so would add +substantial requirements to an implementing server, and thus raise the +barrier for setting up a repository. diff --git a/docs/quickstart.md b/docs/quickstart.md index 1fd9e6c72..8d2c505a8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ page. Alternately, you can clone the GitHub project and build your own client from source. The quickest route to installing from source is to -run `make boostrap build`, and then use `bin/helm`. +run `make bootstrap build`, and then use `bin/helm`. ## Initialize Helm and Install Tiller diff --git a/glide.lock b/glide.lock index 6e14b5405..8d962eb56 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,8 @@ -hash: b2c742106f6983fde0ea7c341a50ffdaef78f78e1302d15ec5dd17ea191247de -updated: 2016-05-19T16:12:01.062464013-06:00 +hash: 2ac3dc0e19d5a688173924d35a07b4bad2454c7e6f5ff4d5a6911f33d1037586 +updated: 2016-05-24T09:51:36.455233258-07:00 imports: - name: bitbucket.org/ww/goautoneg - version: 75cd24fc2f2c + version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 - name: github.com/beorn7/perks @@ -22,6 +22,17 @@ imports: subpackages: - digest - reference +- name: github.com/docker/docker + version: 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d + subpackages: + - pkg/jsonmessage + - pkg/mount + - pkg/stdcopy + - pkg/symlink + - pkg/term + - pkg/term/winconsole + - pkg/timeutils + - pkg/units - name: github.com/docker/engine-api version: 3d72d392d07bece8d7d7b2a3b6b2e57c2df376a2 subpackages: @@ -46,6 +57,10 @@ imports: - tlsconfig - name: github.com/docker/go-units version: 0bbddae09c5a5419a8c6dcdd7ff90da3d450393b +- name: github.com/docker/spdystream + version: 449fdfce4d962303d702fec724ef0ad181c92528 + subpackages: + - spdy - name: github.com/emicklei/go-restful version: 496d495156da218b9912f03dfa7df7f80fbd8cc3 subpackages: @@ -140,6 +155,8 @@ imports: - util/wordwrap - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/juju/ratelimit version: 77ed1c8a01217656d2080ad51981f6e99adaa177 - name: github.com/Masterminds/semver @@ -152,6 +169,24 @@ imports: version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a subpackages: - pbutil +- name: github.com/opencontainers/runc + version: 7ca2aa4873aea7cb4265b1726acb24b90d8726c6 + subpackages: + - libcontainer + - libcontainer/apparmor + - libcontainer/cgroups + - libcontainer/cgroups/fs + - libcontainer/cgroups/systemd + - libcontainer/configs + - libcontainer/configs/validate + - libcontainer/criurpc + - libcontainer/label + - libcontainer/seccomp + - libcontainer/selinux + - libcontainer/stacktrace + - libcontainer/system + - libcontainer/user + - libcontainer/utils - name: github.com/pborman/uuid version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 - name: github.com/prometheus/client_golang @@ -170,7 +205,7 @@ imports: - name: github.com/prometheus/procfs version: 490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d - name: github.com/spf13/cobra - version: e14e47b7a916ed178f4559ebd7e625cf16410181 + version: f368244301305f414206f889b1735a54cfc8bde8 subpackages: - cobra - name: github.com/spf13/pflag @@ -190,6 +225,7 @@ imports: - trace - http2/hpack - internal/timeseries + - websocket - context/ctxhttp - name: golang.org/x/oauth2 version: b5adcc2dcdf009d0391547edc6ecbaff889f5bb9 @@ -198,6 +234,18 @@ imports: - internal - jws - jwt +- name: google.golang.org/appengine + version: 12d5545dc1cfa6047a286d5e853841b6471f4c19 + subpackages: + - internal + - internal/app_identity + - internal/base + - internal/datastore + - internal/log + - internal/modules + - internal/remote_api + - urlfetch + - internal/urlfetch - name: google.golang.org/cloud version: eb47ba841d53d93506cfbfbc03927daf9cc48f88 subpackages: @@ -221,12 +269,18 @@ imports: - name: k8s.io/kubernetes version: 9990f843cd62caa90445cf76b07d63ba7b5c86fd subpackages: + - pkg/api + - pkg/api/meta + - pkg/client/restclient + - pkg/client/unversioned - pkg/client/unversioned/clientcmd + - pkg/client/unversioned/fake + - pkg/client/unversioned/portforward + - pkg/client/unversioned/remotecommand - pkg/kubectl/cmd/util - pkg/kubectl/resource - - pkg/api + - pkg/labels - pkg/api/unversioned - - pkg/client/restclient - pkg/client/unversioned/auth - pkg/client/unversioned/clientcmd/api - pkg/client/unversioned/clientcmd/api/latest @@ -234,8 +288,13 @@ imports: - pkg/util/errors - pkg/util/homedir - pkg/util/validation + - pkg/kubelet/server/portforward + - pkg/util/httpstream + - pkg/util/runtime + - pkg/client/transport + - pkg/kubelet/server/remotecommand + - pkg/util/httpstream/spdy - pkg/api/errors - - pkg/api/meta - pkg/api/validation - pkg/apimachinery - pkg/apimachinery/registered @@ -246,11 +305,9 @@ imports: - pkg/apis/metrics - pkg/apis/policy - pkg/client/typed/discovery - - pkg/client/unversioned - pkg/client/unversioned/adapters/internalclientset - pkg/controller - pkg/kubectl - - pkg/labels - pkg/registry/thirdpartyresourcedata - pkg/runtime/serializer/json - pkg/util/flag @@ -269,7 +326,6 @@ imports: - pkg/util/rand - pkg/api/v1 - pkg/client/metrics - - pkg/client/transport - pkg/runtime/serializer/streaming - pkg/util/crypto - pkg/util/flowcontrol @@ -280,6 +336,9 @@ imports: - pkg/runtime/serializer/versioning - pkg/conversion/queryparams - pkg/util/json + - pkg/httplog + - pkg/util/wsstream + - third_party/golang/netutil - pkg/util/validation/field - pkg/api/endpoints - pkg/api/pod @@ -321,7 +380,6 @@ imports: - pkg/registry/generic - pkg/util/framer - third_party/forked/json - - pkg/util/runtime - third_party/forked/reflect - pkg/runtime/serializer/protobuf - pkg/runtime/serializer/recognizer diff --git a/glide.yaml b/glide.yaml index 5bcac90a4..b70e170f1 100644 --- a/glide.yaml +++ b/glide.yaml @@ -24,11 +24,18 @@ import: - package: google.golang.org/grpc version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 - package: k8s.io/kubernetes - version: ^1.2 subpackages: + - pkg/api + - pkg/api/meta + - pkg/client/restclient + - pkg/client/unversioned - pkg/client/unversioned/clientcmd + - pkg/client/unversioned/fake + - pkg/client/unversioned/portforward + - pkg/client/unversioned/remotecommand - pkg/kubectl/cmd/util - pkg/kubectl/resource + - pkg/labels - package: github.com/gosuri/uitable - package: speter.net/go/exp/math/dec/inf vcs: git diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index f7c804c1e..6ed4e4f84 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -191,11 +191,7 @@ func findMember(root, path string, members []*Member) error { for _, member := range members { if member.Path == path { filename := filepath.Join(root, path) - if err := compareContent(filename, member.Content); err != nil { - return err - } - - return nil + return compareContent(filename, member.Content) } } diff --git a/pkg/chart/save.go b/pkg/chart/save.go index d6fc5600e..5b8b302ed 100644 --- a/pkg/chart/save.go +++ b/pkg/chart/save.go @@ -103,11 +103,7 @@ func Save(c *Chart, outDir string) (string, error) { } _, err = io.Copy(twriter, in) in.Close() - if err != nil { - return err - } - - return nil + return err }) if err != nil { rollback = true diff --git a/pkg/client/install.go b/pkg/client/install.go index ae3a4f6ea..1131ba631 100644 --- a/pkg/client/install.go +++ b/pkg/client/install.go @@ -40,17 +40,18 @@ func NewInstaller() *Installer { func (i *Installer) Install(verbose, createNS bool) error { var b bytes.Buffer - t := template.New("manifest").Funcs(sprig.TxtFuncMap()) // Add namespace if createNS { - if err := template.Must(t.Parse(NamespaceYAML)).Execute(&b, i); err != nil { + nstpl := template.New("namespace").Funcs(sprig.TxtFuncMap()) + if err := template.Must(nstpl.Parse(NamespaceYAML)).Execute(&b, i); err != nil { return err } } // Add main install YAML - if err := template.Must(t.Parse(InstallYAML)).Execute(&b, i); err != nil { + istpl := template.New("install").Funcs(sprig.TxtFuncMap()) + if err := template.Must(istpl.Parse(InstallYAML)).Execute(&b, i); err != nil { return err } diff --git a/pkg/helm/convert.go b/pkg/helm/convert.go index 315cedd1c..b6a723b40 100644 --- a/pkg/helm/convert.go +++ b/pkg/helm/convert.go @@ -116,7 +116,8 @@ func ValuesToProto(ch *chartutil.Chart) (*chartpbs.Config, error) { vals, err := ch.LoadValues() if err != nil { - return nil, ErrMissingValues + //return nil, ErrMissingValues + vals = map[string]interface{}{} } var buf bytes.Buffer diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 0078bf12b..b6a22e9ec 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -11,13 +11,13 @@ import ( // Client represents a client capable of communicating with the Kubernetes API. type Client struct { - config clientcmd.ClientConfig + *cmdutil.Factory } // New create a new Client func New(config clientcmd.ClientConfig) *Client { return &Client{ - config: config, + Factory: cmdutil.NewFactory(config), } } @@ -28,22 +28,20 @@ type ResourceActorFunc func(*resource.Info) error // // Namespace will set the namespace func (c *Client) Create(namespace string, reader io.Reader) error { - f := cmdutil.NewFactory(c.config) - return perform(f, namespace, reader, createResource) + return perform(c, namespace, reader, createResource) } // Delete deletes kubernetes resources from an io.reader // // Namespace will set the namespace func (c *Client) Delete(namespace string, reader io.Reader) error { - f := cmdutil.NewFactory(c.config) - return perform(f, namespace, reader, deleteResource) + return perform(c, namespace, reader, deleteResource) } const includeThirdPartyAPIs = false -func perform(f *cmdutil.Factory, namespace string, reader io.Reader, fn ResourceActorFunc) error { - r := f.NewBuilder(includeThirdPartyAPIs). +func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc) error { + r := c.NewBuilder(includeThirdPartyAPIs). ContinueOnError(). NamespaceParam(namespace). RequireNamespace(). diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index c3c81b4a2..7357f584c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -7,7 +7,6 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/client/unversioned/fake" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -46,12 +45,12 @@ func TestPerform(t *testing.T) { return nil } - f := cmdutil.NewFactory(nil) - f.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + c := New(nil) + c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { return &fake.RESTClient{}, nil } - err := perform(f, tt.namespace, tt.reader, fn) + err := perform(c, tt.namespace, tt.reader, fn) if (err != nil) != tt.err { t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err) } diff --git a/pkg/kube/tunnel.go b/pkg/kube/tunnel.go new file mode 100644 index 000000000..747a7efcd --- /dev/null +++ b/pkg/kube/tunnel.go @@ -0,0 +1,94 @@ +package kube + +import ( + "bytes" + "fmt" + "net" + "strconv" + + "k8s.io/kubernetes/pkg/client/unversioned/portforward" + "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" +) + +// Tunnel describes a ssh-like tunnel to a kubernetes pod +type Tunnel struct { + Local int + Remote int + stopChan chan struct{} +} + +// Close disconnects a tunnel connection +func (t *Tunnel) Close() { + close(t.stopChan) +} + +// ForwardPort opens a tunnel to a kubernetes pod +func (c *Client) ForwardPort(namespace, podName string, remote int) (*Tunnel, error) { + client, err := c.Client() + if err != nil { + return nil, err + } + + config, err := c.ClientConfig() + if err != nil { + return nil, err + } + + // Build a url to the portforward endpoing + // example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-rc-9itlq/portforward + u := client.RESTClient.Post(). + Resource("pods"). + Namespace(namespace). + Name(podName). + SubResource("portforward").URL() + + dialer, err := remotecommand.NewExecutor(config, "POST", u) + if err != nil { + return nil, err + } + + local, err := getAvailablePort() + if err != nil { + return nil, err + } + + t := &Tunnel{ + Local: local, + Remote: remote, + stopChan: make(chan struct{}, 1), + } + + ports := []string{fmt.Sprintf("%d:%d", local, remote)} + + var b bytes.Buffer + pf, err := portforward.New(dialer, ports, t.stopChan, &b, &b) + if err != nil { + return nil, err + } + + go func() { + if err := pf.ForwardPorts(); err != nil { + fmt.Printf("Error forwarding ports: %v\n", err) + } + }() + + // wait for listeners to start + <-pf.Ready + + return t, nil +} + +func getAvailablePort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, err + } + defer l.Close() + + _, p, err := net.SplitHostPort(l.Addr().String()) + port, err := strconv.Atoi(p) + if err != nil { + return 0, err + } + return port, err +} diff --git a/pkg/kube/tunnel_test.go b/pkg/kube/tunnel_test.go new file mode 100644 index 000000000..5833b1bc8 --- /dev/null +++ b/pkg/kube/tunnel_test.go @@ -0,0 +1,15 @@ +package kube + +import ( + "testing" +) + +func TestAvailablePort(t *testing.T) { + port, err := getAvailablePort() + if err != nil { + t.Fatal(err) + } + if port < 1 { + t.Fatalf("generated port should be > 1, got %d", port) + } +} diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go index ceaa7914b..ee020f5d2 100644 --- a/pkg/proto/hapi/chart/metadata.pb.go +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -44,6 +44,8 @@ type Metadata struct { Keywords []string `protobuf:"bytes,6,rep,name=keywords" json:"keywords,omitempty"` // A list of name and URL/email address combinations for the maintainer(s) Maintainers []*Maintainer `protobuf:"bytes,7,rep,name=maintainers" json:"maintainers,omitempty"` + // The name of the template engine to use. Defaults to 'gotpl'. + Engine string `protobuf:"bytes,8,opt,name=engine" json:"engine,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -64,19 +66,20 @@ func init() { } var fileDescriptor2 = []byte{ - // 224 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x3f, 0x4f, 0xc4, 0x30, - 0x0c, 0xc5, 0x55, 0xee, 0x7a, 0x3d, 0xdc, 0xcd, 0x42, 0x28, 0x30, 0x55, 0x37, 0x31, 0xe5, 0x24, - 0x90, 0x10, 0x33, 0xfb, 0x2d, 0x37, 0xb2, 0x99, 0xd6, 0x52, 0x23, 0x48, 0x53, 0x25, 0x01, 0xc4, - 0x97, 0xe5, 0xb3, 0x90, 0xba, 0xf4, 0xcf, 0xc0, 0x60, 0xc9, 0xef, 0xfd, 0xfc, 0x2c, 0xd9, 0x70, - 0xd3, 0x52, 0x6f, 0x8e, 0x75, 0x4b, 0x3e, 0x1e, 0x2d, 0x47, 0x6a, 0x28, 0x92, 0xee, 0xbd, 0x8b, - 0x0e, 0x61, 0x40, 0x5a, 0xd0, 0xe1, 0x11, 0xe0, 0x44, 0xa6, 0x8b, 0xa9, 0xd8, 0x23, 0xc2, 0xb6, - 0x23, 0xcb, 0x2a, 0xab, 0xb2, 0xbb, 0xcb, 0xb3, 0xf4, 0x78, 0x05, 0x39, 0x5b, 0x32, 0xef, 0xea, - 0x42, 0xcc, 0x51, 0x1c, 0x7e, 0x32, 0xd8, 0x9f, 0xfe, 0xd6, 0xfe, 0x1b, 0x4b, 0x5e, 0xeb, 0x92, - 0x37, 0xa6, 0xa4, 0x47, 0x05, 0x45, 0x70, 0x1f, 0xbe, 0xe6, 0xa0, 0x36, 0xd5, 0x26, 0xd9, 0x93, - 0x1c, 0xc8, 0x27, 0xfb, 0x60, 0x5c, 0xa7, 0xb6, 0x12, 0x98, 0x24, 0x56, 0x50, 0x36, 0x1c, 0x6a, - 0x6f, 0xfa, 0x38, 0xd0, 0x5c, 0xe8, 0xda, 0xc2, 0x5b, 0xd8, 0xbf, 0xf1, 0xf7, 0x97, 0xf3, 0x4d, - 0x50, 0x3b, 0x59, 0x3b, 0x6b, 0x7c, 0x82, 0xd2, 0xce, 0xe7, 0x05, 0x55, 0x24, 0x5c, 0xde, 0x5f, - 0xeb, 0xe5, 0x01, 0x7a, 0xb9, 0xfe, 0xbc, 0x1e, 0x7d, 0x2e, 0x5e, 0x72, 0x19, 0x78, 0xdd, 0xc9, - 0xd3, 0x1e, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xaf, 0x44, 0xa7, 0x51, 0x01, 0x00, 0x00, + // 234 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xbd, 0x4f, 0xc3, 0x40, + 0x0c, 0xc5, 0x15, 0xda, 0x7c, 0xe0, 0x6c, 0x16, 0xaa, 0x0c, 0x53, 0xd4, 0x89, 0x29, 0x95, 0x40, + 0x42, 0xcc, 0xec, 0x5d, 0x3a, 0xb2, 0x99, 0xc4, 0x22, 0x27, 0x48, 0x2e, 0xba, 0x3b, 0x40, 0xfc, + 0xe3, 0xcc, 0x5c, 0xdc, 0xaf, 0x0c, 0x1d, 0x22, 0xbd, 0xf7, 0x7e, 0x79, 0x3e, 0xd9, 0x70, 0xdb, + 0xf1, 0x68, 0x36, 0x4d, 0xc7, 0x2e, 0x6c, 0x7a, 0x09, 0xdc, 0x72, 0xe0, 0x7a, 0x74, 0x36, 0x58, + 0x84, 0x09, 0xd5, 0x8a, 0xd6, 0x4f, 0x00, 0x5b, 0x36, 0x43, 0x88, 0x9f, 0x38, 0x44, 0x58, 0x0e, + 0xdc, 0x0b, 0x25, 0x55, 0x72, 0x7f, 0xbd, 0x53, 0x8d, 0x37, 0x90, 0x4a, 0xcf, 0xe6, 0x93, 0xae, + 0x34, 0xdc, 0x9b, 0xf5, 0x5f, 0x02, 0xc5, 0xf6, 0x30, 0xf6, 0x62, 0x2d, 0x66, 0x9d, 0x8d, 0xd9, + 0xbe, 0xa5, 0x1a, 0x09, 0x72, 0x6f, 0xbf, 0x5c, 0x23, 0x9e, 0x16, 0xd5, 0x22, 0xc6, 0x47, 0x3b, + 0x91, 0x6f, 0x71, 0xde, 0xd8, 0x81, 0x96, 0x5a, 0x38, 0x5a, 0xac, 0xa0, 0x6c, 0xc5, 0x37, 0xce, + 0x8c, 0x61, 0xa2, 0xa9, 0xd2, 0x79, 0x84, 0x77, 0x50, 0x7c, 0xc8, 0xef, 0x8f, 0x75, 0xad, 0xa7, + 0x4c, 0xc7, 0x9e, 0x3c, 0x3e, 0x43, 0xd9, 0x9f, 0xd6, 0xf3, 0x94, 0x47, 0x5c, 0x3e, 0xac, 0xea, + 0xf3, 0x01, 0xea, 0xf3, 0xf6, 0xbb, 0xf9, 0xaf, 0xb8, 0x82, 0x4c, 0x86, 0xf7, 0xa8, 0xa9, 0xd0, + 0x27, 0x0f, 0xee, 0x25, 0x7f, 0x4d, 0xb5, 0xf8, 0x96, 0xe9, 0x31, 0x1f, 0xff, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x6f, 0x4a, 0x7b, 0xd0, 0x69, 0x01, 0x00, 0x00, } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index f61ba57c6..99b764e34 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -28,7 +28,7 @@ type ChartRef struct { } // DownloadIndexFile uses -func DownloadIndexFile(repoName, url, indexFileName string) error { +func DownloadIndexFile(repoName, url, indexFilePath string) error { var indexURL string indexURL = strings.TrimSuffix(url, "/") + "/index.yaml" @@ -49,11 +49,7 @@ func DownloadIndexFile(repoName, url, indexFileName string) error { return err } - if err := ioutil.WriteFile(indexFileName, b, 0644); err != nil { - return err - } - - return nil + return ioutil.WriteFile(indexFilePath, b, 0644) } // UnmarshalYAML unmarshals the index file diff --git a/pkg/repo/local.go b/pkg/repo/local.go index ec6e13a07..2dbdb98e5 100644 --- a/pkg/repo/local.go +++ b/pkg/repo/local.go @@ -46,11 +46,7 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error { if err != nil { return err } - err = Reindex(ch, path+"/index.yaml") - if err != nil { - return err - } - return nil + return Reindex(ch, path+"/index.yaml") } // Reindex adds an entry to the index file at the given path diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index b5a388020..5da66911e 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -98,11 +98,7 @@ func (r *ChartRepository) saveIndexFile() error { return err } - if err = ioutil.WriteFile(filepath.Join(r.RootPath, indexPath), index, 0644); err != nil { - return err - } - - return nil + return ioutil.WriteFile(filepath.Join(r.RootPath, indexPath), index, 0644) } func (r *ChartRepository) Index() error { @@ -143,11 +139,7 @@ func (r *ChartRepository) Index() error { } - if err := r.saveIndexFile(); err != nil { - return err - } - - return nil + return r.saveIndexFile() } func generateChecksum(path string) (string, error) { diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 000000000..890c045f6 --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,10 @@ +// Package version represents the current version of the project. +package version + +// Version is the current version of the Helm. +// Update this whenever making a new release. +// The version is of the format Major.Minor.Patch +// Increment major number for new feature additions and behavioral changes. +// Increment minor number for bug fixes and performance enhancements. +// Increment patch number for critical fixes to existing releases. +var Version = "v2.0.0-alpha.1" diff --git a/versioning.mk b/versioning.mk index 8d96c8629..e04b99822 100644 --- a/versioning.mk +++ b/versioning.mk @@ -1,11 +1,28 @@ MUTABLE_VERSION ?= canary -VERSION ?= git-$(shell git rev-parse --short HEAD) -IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} +GIT_SHA := $(shell git rev-parse --short HEAD) +GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null) + +ifdef VERSION + DOCKER_VERSION = $(VERSION) + BINARY_VERSION = $(VERSION) +endif + +DOCKER_VERSION ?= git-${GIT_SHA} +BINARY_VERSION ?= ${GIT_TAG}+${GIT_SHA} + +IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${DOCKER_VERSION} MUTABLE_IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} +LDFLAGS += -X github.com/kubernetes/helm/pkg/version.Version=${BINARY_VERSION} + +DOCKER_PUSH = docker push +ifeq ($(DOCKER_REGISTRY),gcr.io) + DOCKER_PUSH = gcloud docker push +endif + info: - @echo "Build tag: ${VERSION}" + @echo "Build tag: ${DOCKER_VERSION}" @echo "Registry: ${DOCKER_REGISTRY}" @echo "Immutable tag: ${IMAGE}" @echo "Mutable tag: ${MUTABLE_IMAGE}" @@ -15,8 +32,8 @@ docker-push: docker-mutable-push docker-immutable-push .PHONY: docker-immutable-push docker-immutable-push: - docker push ${IMAGE} + ${DOCKER_PUSH} ${IMAGE} .PHONY: docker-mutable-push docker-mutable-push: - docker push ${MUTABLE_IMAGE} + ${DOCKER_PUSH} ${MUTABLE_IMAGE}