Add library chart list capability

Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com>
pull/5263/head
Martin Hickey 7 years ago
parent 4356999b5f
commit 3b3362d701

@ -16,18 +16,12 @@ limitations under the License.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/Masterminds/semver"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
)
const dependencyDesc = `
@ -103,12 +97,8 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
return cmd
}
type dependencyLisOptions struct {
chartpath string
}
func newDependencyListCmd(out io.Writer) *cobra.Command {
o := &dependencyLisOptions{
o := &refListOptions{
chartpath: ".",
}
@ -122,148 +112,8 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
if len(args) > 0 {
o.chartpath = filepath.Clean(args[0])
}
return o.run(out)
return o.run(out, false)
},
}
return cmd
}
func (o *dependencyLisOptions) run(out io.Writer) error {
c, err := loader.Load(o.chartpath)
if err != nil {
return err
}
if c.Metadata.Dependencies == nil {
fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(o.chartpath, "charts"))
return nil
}
o.printDependencies(out, c.Metadata.Dependencies)
fmt.Fprintln(out)
o.printMissing(out, c.Metadata.Dependencies)
return nil
}
func (o *dependencyLisOptions) dependencyStatus(dep *chart.Dependency) string {
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
archives, err := filepath.Glob(filepath.Join(o.chartpath, "charts", filename))
if err != nil {
return "bad pattern"
} else if len(archives) > 1 {
return "too many matches"
} else if len(archives) == 1 {
archive := archives[0]
if _, err := os.Stat(archive); err == nil {
c, err := loader.Load(archive)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "ok"
}
return "wrong version"
}
return "ok"
}
}
folder := filepath.Join(o.chartpath, "charts", dep.Name)
if fi, err := os.Stat(folder); err != nil {
return "missing"
} else if !fi.IsDir() {
return "mispackaged"
}
c, err := loader.Load(folder)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "unpacked"
}
return "wrong version"
}
return "unpacked"
}
// printDependencies prints all of the dependencies in the yaml file.
func (o *dependencyLisOptions) printDependencies(out io.Writer, reqs []*chart.Dependency) {
table := uitable.New()
table.MaxColWidth = 80
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
for _, row := range reqs {
table.AddRow(row.Name, row.Version, row.Repository, o.dependencyStatus(row))
}
fmt.Fprintln(out, table)
}
// printMissing prints warnings about charts that are present on disk, but are
// not in Charts.yaml.
func (o *dependencyLisOptions) printMissing(out io.Writer, reqs []*chart.Dependency) {
folder := filepath.Join(o.chartpath, "charts/*")
files, err := filepath.Glob(folder)
if err != nil {
fmt.Fprintln(out, err)
return
}
for _, f := range files {
fi, err := os.Stat(f)
if err != nil {
fmt.Fprintf(out, "Warning: %s\n", err)
}
// Skip anything that is not a directory and not a tgz file.
if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
continue
}
c, err := loader.Load(f)
if err != nil {
fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
continue
}
found := false
for _, d := range reqs {
if d.Name == c.Name() {
found = true
break
}
}
if !found {
fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f)
}
}
}

@ -17,6 +17,7 @@ package main
import (
"io"
"path/filepath"
"github.com/spf13/cobra"
@ -67,17 +68,47 @@ 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.
`
const libraryListDesc = `
List all of the libraries declared in a chart.
This can take chart archives and chart directories as input. It will not alter
the contents of a chart.
This will produce an error if the chart cannot be loaded.
`
func newLibraryCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "library update",
Use: "library update|list",
Aliases: []string{"lib", "libraries"},
Short: "manage a chart's libraries",
Long: libraryDesc,
Args: require.NoArgs,
}
cmd.AddCommand(newLibraryListCmd(out))
cmd.AddCommand(newLibraryUpdateCmd(out))
return cmd
}
func newLibraryListCmd(out io.Writer) *cobra.Command {
o := &refListOptions{
chartpath: ".",
}
cmd := &cobra.Command{
Use: "list CHART",
Aliases: []string{"ls"},
Short: "list the libraries for the given chart",
Long: libraryListDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
o.chartpath = filepath.Clean(args[0])
}
return o.run(out, true)
},
}
return cmd
}

@ -0,0 +1,53 @@
/*
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 (
"runtime"
"testing"
)
func TestLibraryListCmd(t *testing.T) {
noSuchChart := cmdTestCase{
name: "No such chart",
cmd: "library list /no/such/chart",
golden: "output/dependency-list-no-chart-linux.txt",
wantError: true,
}
noDependencies := cmdTestCase{
name: "No libraries",
cmd: "library list testdata/testcharts/alpine",
golden: "output/library-list-no-requirements-linux.txt",
}
if runtime.GOOS == "windows" {
noSuchChart.golden = "output/dependency-list-no-chart-windows.txt"
noDependencies.golden = "output/library-list-no-requirements-windows.txt"
}
tests := []cmdTestCase{noSuchChart,
noDependencies, {
name: "Libraries in library dir",
cmd: "library list testdata/testcharts/libcharttest",
golden: "output/dependency-list.txt",
}, {
name: "Libraries in chart archive",
cmd: "library list testdata/testcharts/libcharttest-0.1.0.tgz",
golden: "output/dependency-list-archive.txt",
}}
runTestCmd(t, tests)
}

@ -0,0 +1,187 @@
/*
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 (
"fmt"
"io"
"os"
"path/filepath"
"github.com/Masterminds/semver"
"github.com/gosuri/uitable"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
)
type refListOptions struct {
chartpath string
}
func (o *refListOptions) run(out io.Writer, isLib bool) error {
c, err := loader.Load(o.chartpath)
if err != nil {
return err
}
var reqs []*chart.Dependency
var dirName string
if isLib {
reqs = c.Metadata.Libraries
dirName = "library"
} else {
reqs = c.Metadata.Dependencies
dirName = "charts"
}
if reqs == nil {
if isLib {
fmt.Fprintf(out, "WARNING: no libraries at %s\n", filepath.Join(o.chartpath, dirName))
} else {
fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(o.chartpath, dirName))
}
return nil
}
o.printDependencies(out, reqs, dirName)
fmt.Fprintln(out)
o.printMissing(out, reqs, dirName)
return nil
}
func (o *refListOptions) dependencyStatus(dep *chart.Dependency, dirName string) string {
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
archives, err := filepath.Glob(filepath.Join(o.chartpath, dirName, filename))
if err != nil {
return "bad pattern"
} else if len(archives) > 1 {
return "too many matches"
} else if len(archives) == 1 {
archive := archives[0]
if _, err := os.Stat(archive); err == nil {
c, err := loader.Load(archive)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "ok"
}
return "wrong version"
}
return "ok"
}
}
folder := filepath.Join(o.chartpath, dirName, dep.Name)
if fi, err := os.Stat(folder); err != nil {
return "missing"
} else if !fi.IsDir() {
return "mispackaged"
}
c, err := loader.Load(folder)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "unpacked"
}
return "wrong version"
}
return "unpacked"
}
// printDependencies prints all of the dependencies/libraries in the yaml file.
func (o *refListOptions) printDependencies(out io.Writer, reqs []*chart.Dependency, dirName string) {
table := uitable.New()
table.MaxColWidth = 80
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
for _, row := range reqs {
table.AddRow(row.Name, row.Version, row.Repository, o.dependencyStatus(row, dirName))
}
fmt.Fprintln(out, table)
}
// printMissing prints warnings about charts that are present on disk, but are
// not in Charts.yaml.
func (o *refListOptions) printMissing(out io.Writer, reqs []*chart.Dependency, dirName string) {
folder := filepath.Join(o.chartpath, dirName+"/*")
files, err := filepath.Glob(folder)
if err != nil {
fmt.Fprintln(out, err)
return
}
for _, f := range files {
fi, err := os.Stat(f)
if err != nil {
fmt.Fprintf(out, "Warning: %s\n", err)
}
// Skip anything that is not a directory and not a tgz file.
if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
continue
}
c, err := loader.Load(f)
if err != nil {
fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
continue
}
found := false
for _, d := range reqs {
if d.Name == c.Name() {
found = true
break
}
}
if !found {
fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f)
}
}
}

@ -0,0 +1 @@
WARNING: no libraries at testdata/testcharts/alpine/library

@ -0,0 +1 @@
WARNING: no libraries at testdata\testcharts\alpine\library

@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

@ -0,0 +1,13 @@
description: A Helm chart for Kubernetes
name: libcharttest
version: 0.1.0
libraries:
- name: reqsubchart
version: 0.1.0
repository: "https://example.com/charts"
- name: reqsubchart2
version: 0.2.0
repository: "https://example.com/charts"
- name: reqsubchart3
version: ">=0.1.0"
repository: "https://example.com/charts"

@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

@ -0,0 +1,3 @@
description: A Helm chart for Kubernetes
name: reqsubchart
version: 0.1.0

@ -0,0 +1,4 @@
# Default values for reqsubchart.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value

@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

@ -0,0 +1,3 @@
description: A Helm chart for Kubernetes
name: reqsubchart2
version: 0.2.0

@ -0,0 +1,4 @@
# Default values for reqsubchart.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value

@ -0,0 +1,4 @@
# Default values for libcharttest.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value
Loading…
Cancel
Save