Add lib chart update functionality

Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com>
pull/5255/head
Martin Hickey 7 years ago
parent 087d1eab52
commit 5c4af6f275

@ -22,9 +22,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
) )
const dependencyUpDesc = ` const dependencyUpDesc = `
@ -42,21 +39,9 @@ reason, an update command will not remove charts unless they are (a) present
in the Chart.yaml file, but (b) at the wrong version. in the Chart.yaml file, but (b) at the wrong version.
` `
// dependencyUpdateOptions describes a 'helm dependency update'
type dependencyUpdateOptions struct {
keyring string // --keyring
skipRefresh bool // --skip-refresh
verify bool // --verify
// args
chartpath string
helmhome helmpath.Home
}
// newDependencyUpdateCmd creates a new dependency update command. // newDependencyUpdateCmd creates a new dependency update command.
func newDependencyUpdateCmd(out io.Writer) *cobra.Command { func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
o := &dependencyUpdateOptions{ o := &refUpdateOptions{
chartpath: ".", chartpath: ".",
} }
@ -71,7 +56,7 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
o.chartpath = filepath.Clean(args[0]) o.chartpath = filepath.Clean(args[0])
} }
o.helmhome = settings.Home o.helmhome = settings.Home
return o.run(out) return o.run(out, false)
}, },
} }
@ -82,22 +67,3 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
return cmd return cmd
} }
// run runs the full dependency update process.
func (o *dependencyUpdateOptions) run(out io.Writer) error {
man := &downloader.Manager{
Out: out,
ChartPath: o.chartpath,
HelmHome: o.helmhome,
Keyring: o.keyring,
SkipUpdate: o.skipRefresh,
Getters: getter.All(settings),
}
if o.verify {
man.Verify = downloader.VerifyAlways
}
if settings.Debug {
man.Debug = true
}
return man.Update()
}

@ -165,11 +165,11 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
} }
out := bytes.NewBuffer(nil) out := bytes.NewBuffer(nil)
o := &dependencyUpdateOptions{} o := &refUpdateOptions{}
o.helmhome = hh o.helmhome = hh
o.chartpath = hh.Path(chartname) o.chartpath = hh.Path(chartname)
if err := o.run(out); err != nil { if err := o.run(out, false); err != nil {
output := out.String() output := out.String()
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
@ -178,7 +178,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
if err := o.run(out); err == nil { if err := o.run(out, false); err == nil {
output := out.String() output := out.String()
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")

@ -245,7 +245,7 @@ func (o *installOptions) run(out io.Writer) error {
SkipUpdate: false, SkipUpdate: false,
Getters: getter.All(settings), Getters: getter.All(settings),
} }
if err := man.Update(); err != nil { if err := man.Update(false); err != nil {
return err return err
} }
} else { } else {

@ -0,0 +1,83 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
)
const libraryDesc = `
Manage the libraries of a chart.
Helm charts store their libraries in 'library/'. For chart developers, it is
often easier to manage libraries in 'Chart.yaml' which declares all
libraries.
The library commands operate on that file, making it easy to synchronize
between the desired libraries and the actual libraries stored in the
'library/' directory.
For example, this Chart.yaml declares one library:
# Chart.yaml
libraries:
- name: common
version: "^2.1.0"
repository: http://another.example.com/charts
The 'name' should be the name of a chart, where that name must match the name
in that chart's 'Chart.yaml' file.
The 'version' field should contain a semantic version or version range.
The 'repository' URL should point to a Chart Repository. Helm expects that by
appending '/index.yaml' to the URL, it should be able to retrieve the chart
repository's index. Note: 'repository' can be an alias. The alias must start
with 'alias:' or '@'.
Starting from 2.2.0, repository can be defined as the path to the directory of
the library charts stored locally. The path should start with a prefix of
"file://". For example,
# Chart.yaml
libraries:
- name: common
version: "^2.1.0"
repository: "file://../library_chart/common"
If the library chart is retrieved locally, it is not required to have the
repository added to helm by "helm add repo". Version matching is also supported
for this case.
`
func newLibraryCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "library update",
Aliases: []string{"lib", "libraries"},
Short: "manage a chart's libraries",
Long: libraryDesc,
Args: require.NoArgs,
}
cmd.AddCommand(newLibraryUpdateCmd(out))
return cmd
}

@ -0,0 +1,69 @@
/*
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 (
"io"
"path/filepath"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
)
const libraryUpDesc = `
Update the on-disk libraries to mirror Chart.yaml.
This command verifies that the required charts, as expressed in 'Chart.yaml',
are present in 'library/' and are at an acceptable version. It will pull down
the latest charts that satisfy the libraries, and clean up old libraries.
On successful update, this will generate a lock file that can be used to
rebuild the libraries to an exact version.
Libraries are not required to be represented in 'Chart.yaml'. For that
reason, an update command will not remove charts unless they are (a) present
in the Chart.yaml file, but (b) at the wrong version.
`
// newLibraryUpdateCmd creates a new library update command.
func newLibraryUpdateCmd(out io.Writer) *cobra.Command {
o := &refUpdateOptions{
chartpath: ".",
}
cmd := &cobra.Command{
Use: "update CHART",
Aliases: []string{"up"},
Short: "update library/ based on the contents of Chart.yaml",
Long: libraryUpDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
o.chartpath = filepath.Clean(args[0])
}
o.helmhome = settings.Home
return o.run(out, true)
},
}
f := cmd.Flags()
f.BoolVar(&o.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd
}

@ -52,6 +52,7 @@ Versioned chart archives are used by Helm package repositories.
type packageOptions struct { type packageOptions struct {
appVersion string // --app-version appVersion string // --app-version
dependencyUpdate bool // --dependency-update dependencyUpdate bool // --dependency-update
libraryUpdate bool // --library-update
destination string // --destination destination string // --destination
key string // --key key string // --key
keyring string // --keyring keyring string // --keyring
@ -103,6 +104,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.appVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVar(&o.appVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.") f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&o.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) f.BoolVarP(&o.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
f.BoolVarP(&o.libraryUpdate, "library-update", "l", false, `update libraries from "Chart.yaml" to dir "library/" before packaging`)
o.valuesOptions.addFlags(f) o.valuesOptions.addFlags(f)
return cmd return cmd
@ -124,7 +126,22 @@ func (o *packageOptions) run(out io.Writer) error {
Debug: settings.Debug, Debug: settings.Debug,
} }
if err := downloadManager.Update(); err != nil { if err := downloadManager.Update(false); err != nil {
return err
}
}
if o.libraryUpdate {
downloadManager := &downloader.Manager{
Out: out,
ChartPath: path,
HelmHome: settings.Home,
Keyring: o.keyring,
Getters: getter.All(settings),
Debug: settings.Debug,
}
if err := downloadManager.Update(true); err != nil {
return err return err
} }
} }

@ -0,0 +1,55 @@
/*
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 (
"io"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
)
// refUpdateOptions describes a 'helm library/dependency update'
type refUpdateOptions struct {
keyring string // --keyring
skipRefresh bool // --skip-refresh
verify bool // --verify
// args
chartpath string
helmhome helmpath.Home
}
// run runs the full library update process.
func (o *refUpdateOptions) run(out io.Writer, lib bool) error {
man := &downloader.Manager{
Out: out,
ChartPath: o.chartpath,
HelmHome: o.helmhome,
Keyring: o.keyring,
SkipUpdate: o.skipRefresh,
Getters: getter.All(settings),
}
if o.verify {
man.Verify = downloader.VerifyAlways
}
if settings.Debug {
man.Debug = true
}
return man.Update(lib)
}

@ -65,6 +65,7 @@ func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Wri
// chart commands // chart commands
newCreateCmd(out), newCreateCmd(out),
newDependencyCmd(out), newDependencyCmd(out),
newLibraryCmd(out),
newPullCmd(out), newPullCmd(out),
newShowCmd(out), newShowCmd(out),
newLintCmd(out), newLintCmd(out),

@ -19,7 +19,8 @@ package chart
const APIVersionv1 = "v1" const APIVersionv1 = "v1"
// Chart is a helm package that contains metadata, a default config, zero or more // Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies). // optionally parameterizable templates, and zero or more
// charts (dependencies/libraries).
type Chart struct { type Chart struct {
// Metadata is the contents of the Chartfile. // Metadata is the contents of the Chartfile.
Metadata *Metadata Metadata *Metadata
@ -37,6 +38,7 @@ type Chart struct {
parent *Chart parent *Chart
dependencies []*Chart dependencies []*Chart
libraries []*Chart
} }
// SetDependencies replaces the chart dependencies. // SetDependencies replaces the chart dependencies.
@ -72,6 +74,9 @@ func (ch *Chart) Root() *Chart {
// Dependencies are the charts that this chart depends on. // Dependencies are the charts that this chart depends on.
func (ch *Chart) Dependencies() []*Chart { return ch.dependencies } func (ch *Chart) Dependencies() []*Chart { return ch.dependencies }
// Libraries are the common charts that this chart uses.
func (ch *Chart) Libraries() []*Chart { return ch.libraries }
// IsRoot determines if the chart is the root chart. // IsRoot determines if the chart is the root chart.
func (ch *Chart) IsRoot() bool { return ch.parent == nil } func (ch *Chart) IsRoot() bool { return ch.parent == nil }

@ -19,7 +19,7 @@ import "time"
// Dependency describes a chart upon which another chart depends. // Dependency describes a chart upon which another chart depends.
// //
// Dependencies can be used to express developer intent, or to capture the state // Dependencies/libraries can be used to express developer intent, or to capture the state
// of a chart. // of a chart.
type Dependency struct { type Dependency struct {
// Name is the name of the dependency. // Name is the name of the dependency.
@ -49,9 +49,9 @@ type Dependency struct {
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
} }
// Lock is a lock file for dependencies. // Lock is a lock file for dependencies/libraries.
// //
// It represents the state that the dependencies should be in. // It represents the state that the dependencies/libraries should be in.
type Lock struct { type Lock struct {
// Genderated is the date the lock file was last generated. // Genderated is the date the lock file was last generated.
Generated time.Time `json:"generated"` Generated time.Time `json:"generated"`
@ -59,4 +59,6 @@ type Lock struct {
Digest string `json:"digest"` Digest string `json:"digest"`
// Dependencies is the list of dependencies that this lock file has locked. // Dependencies is the list of dependencies that this lock file has locked.
Dependencies []*Dependency `json:"dependencies"` Dependencies []*Dependency `json:"dependencies"`
// Libraries is the list of libraries that this lock file has locked.
Libraries []*Dependency `json:"libraries"`
} }

@ -64,4 +64,6 @@ type Metadata struct {
KubeVersion string `json:"kubeVersion,omitempty"` KubeVersion string `json:"kubeVersion,omitempty"`
// Dependencies are a list of dependencies for a chart. // Dependencies are a list of dependencies for a chart.
Dependencies []*Dependency `json:"dependencies,omitempty"` Dependencies []*Dependency `json:"dependencies,omitempty"`
// Libraries are a list of libraries for a chart.
Libraries []*Dependency `json:"libraries,omitempty"`
} }

@ -40,7 +40,7 @@ import (
"k8s.io/helm/pkg/urlutil" "k8s.io/helm/pkg/urlutil"
) )
// Manager handles the lifecycle of fetching, resolving, and storing dependencies. // Manager handles the lifecycle of fetching, resolving, and storing dependencies/libraries.
type Manager struct { type Manager struct {
// Out is used to print warnings and notifications. // Out is used to print warnings and notifications.
Out io.Writer Out io.Writer
@ -75,7 +75,7 @@ func (m *Manager) Build() error {
// an update. // an update.
lock := c.Lock lock := c.Lock
if lock == nil { if lock == nil {
return m.Update() return m.Update(false)
} }
req := c.Metadata.Dependencies req := c.Metadata.Dependencies
@ -96,7 +96,7 @@ func (m *Manager) Build() error {
} }
// Now we need to fetch every package here into charts/ // Now we need to fetch every package here into charts/
if err := m.downloadAll(lock.Dependencies); err != nil { if err := m.downloadAll(lock.Dependencies, false); err != nil {
return err return err
} }
@ -108,20 +108,25 @@ func (m *Manager) Build() error {
// It first reads the Chart.yaml file, and then attempts to // It first reads the Chart.yaml file, and then attempts to
// negotiate versions based on that. It will download the versions // negotiate versions based on that. It will download the versions
// from remote chart repositories unless SkipUpdate is true. // from remote chart repositories unless SkipUpdate is true.
func (m *Manager) Update() error { func (m *Manager) Update(isLib bool) error {
c, err := m.loadChartDir() c, err := m.loadChartDir()
if err != nil { if err != nil {
return err return err
} }
// If no dependencies are found, we consider this a successful // If no dependencies/libraries are found, we consider this a successful
// completion. // completion.
req := c.Metadata.Dependencies var req []*chart.Dependency
if isLib {
req = c.Metadata.Libraries
} else {
req = c.Metadata.Dependencies
}
if req == nil { if req == nil {
return nil return nil
} }
// Hash dependencies // Hash dependencies/libraries
// FIXME should this hash all of Chart.yaml // FIXME should this hash all of Chart.yaml
hash, err := resolver.HashReq(req) hash, err := resolver.HashReq(req)
if err != nil { if err != nil {
@ -143,14 +148,20 @@ func (m *Manager) Update() error {
} }
// Now we need to find out which version of a chart best satisfies the // Now we need to find out which version of a chart best satisfies the
// dependencies in the Chart.yaml // dependencies/libraries in the Chart.yaml
lock, err := m.resolve(req, repoNames, hash) lock, err := m.resolve(req, repoNames, hash, isLib)
if err != nil { if err != nil {
return err return err
} }
// Now we need to fetch every package here into charts/ // Now we need to fetch every package here into charts/
if err := m.downloadAll(lock.Dependencies); err != nil { var lockReq []*chart.Dependency
if isLib {
lockReq = lock.Libraries
} else {
lockReq = lock.Dependencies
}
if err := m.downloadAll(lockReq, isLib); err != nil {
return err return err
} }
@ -173,26 +184,35 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
return loader.LoadDir(m.ChartPath) return loader.LoadDir(m.ChartPath)
} }
// resolve takes a list of dependencies and translates them into an exact version to download. // resolve takes a list of dependencies/libraries and translates them into an exact version to download.
// //
// This returns a lock file, which has all of the dependencies normalized to a specific version. // This returns a lock file, which has all of the dependencies/libraries normalized to a specific version.
func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string, hash string) (*chart.Lock, error) { func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string, hash string, isLib bool) (*chart.Lock, error) {
res := resolver.New(m.ChartPath, m.HelmHome) res := resolver.New(m.ChartPath, m.HelmHome)
return res.Resolve(req, repoNames, hash) return res.Resolve(req, repoNames, hash, isLib)
} }
// downloadAll takes a list of dependencies and downloads them into charts/ // downloadAll takes a list of dependencies/libraries and downloads them into charts/
// //
// It will delete versions of the chart that exist on disk and might cause // It will delete versions of the chart that exist on disk and might cause
// a conflict. // a conflict.
func (m *Manager) downloadAll(deps []*chart.Dependency) error { func (m *Manager) downloadAll(deps []*chart.Dependency, isLib bool) error {
repos, err := m.loadChartRepositories() repos, err := m.loadChartRepositories()
if err != nil { if err != nil {
return err return err
} }
destPath := filepath.Join(m.ChartPath, "charts") var dirName string
tmpPath := filepath.Join(m.ChartPath, "tmpcharts") var tmpDirName string
if isLib {
dirName = "library"
tmpDirName = "tmplibrary"
} else {
dirName = "charts"
tmpDirName = "tmpcharts"
}
destPath := filepath.Join(m.ChartPath, dirName)
tmpPath := filepath.Join(m.ChartPath, tmpDirName)
// Create 'charts' directory if it doesn't already exist. // Create 'charts' directory if it doesn't already exist.
if fi, err := os.Stat(destPath); err != nil { if fi, err := os.Stat(destPath); err != nil {

@ -32,7 +32,7 @@ import (
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )
// Resolver resolves dependencies from semantic version ranges to a particular version. // Resolver resolves libraries/dependencies from semantic version ranges to a particular version.
type Resolver struct { type Resolver struct {
chartpath string chartpath string
helmhome helmpath.Home helmhome helmpath.Home
@ -46,10 +46,10 @@ func New(chartpath string, helmhome helmpath.Home) *Resolver {
} }
} }
// Resolve resolves dependencies and returns a lock file with the resolution. // Resolve resolves libraries/dependencies and returns a lock file with the resolution.
func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string, d string) (*chart.Lock, error) { func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string, d string, isLib bool) (*chart.Lock, error) {
// Now we clone the dependencies, locking as we go. // Now we clone the libraries/dependencies, locking as we go.
locked := make([]*chart.Dependency, len(reqs)) locked := make([]*chart.Dependency, len(reqs))
missing := []string{} missing := []string{}
for i, d := range reqs { for i, d := range reqs {
@ -68,7 +68,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
} }
constraint, err := semver.NewConstraint(d.Version) constraint, err := semver.NewConstraint(d.Version)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) return nil, errors.Wrapf(err, "dependency/library %q has an invalid version/constraint format", d.Name)
} }
repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name])) repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name]))
@ -107,6 +107,15 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
if len(missing) > 0 { if len(missing) > 0 {
return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", ")) return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", "))
} }
if isLib {
return &chart.Lock{
Generated: time.Now(),
Digest: d,
Libraries: locked,
}, nil
}
return &chart.Lock{ return &chart.Lock{
Generated: time.Now(), Generated: time.Now(),
Digest: d, Digest: d,
@ -114,7 +123,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
}, nil }, nil
} }
// HashReq generates a hash of the dependencies. // HashReq generates a hash of the libraries/dependencies.
// //
// This should be used only to compare against another hash generated by this // This should be used only to compare against another hash generated by this
// function. // function.
@ -128,7 +137,7 @@ func HashReq(req []*chart.Dependency) (string, error) {
} }
// GetLocalPath generates absolute local path when use // GetLocalPath generates absolute local path when use
// "file://" in repository of dependencies // "file://" in repository of libraries/dependencies
func GetLocalPath(repo, chartpath string) (string, error) { func GetLocalPath(repo, chartpath string) (string, error) {
var depPath string var depPath string
var err error var err error

@ -95,7 +95,7 @@ func TestResolve(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
l, err := r.Resolve(tt.req, repoNames, hash) l, err := r.Resolve(tt.req, repoNames, hash, false)
if err != nil { if err != nil {
if tt.err { if tt.err {
continue continue

Loading…
Cancel
Save