github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/cmd/helm/dependency.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package main 17 18 import ( 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 24 "github.com/Masterminds/semver" 25 "github.com/gosuri/uitable" 26 "github.com/spf13/cobra" 27 28 "k8s.io/helm/pkg/chartutil" 29 ) 30 31 const dependencyDesc = ` 32 Manage the dependencies of a chart. 33 34 Helm charts store their dependencies in 'charts/'. For chart developers, it is 35 often easier to manage a single dependency file ('requirements.yaml') 36 which declares all dependencies. 37 38 The dependency commands operate on that file, making it easy to synchronize 39 between the desired dependencies and the actual dependencies stored in the 40 'charts/' directory. 41 42 A 'requirements.yaml' file is a YAML file in which developers can declare chart 43 dependencies, along with the location of the chart and the desired version. 44 For example, this requirements file declares two dependencies: 45 46 # requirements.yaml 47 dependencies: 48 - name: nginx 49 version: "1.2.3" 50 repository: "https://example.com/charts" 51 - name: memcached 52 version: "3.2.1" 53 repository: "https://another.example.com/charts" 54 55 The 'name' should be the name of a chart, where that name must match the name 56 in that chart's 'Chart.yaml' file. 57 58 The 'version' field should contain a semantic version or version range. 59 60 The 'repository' URL should point to a Chart Repository. Helm expects that by 61 appending '/index.yaml' to the URL, it should be able to retrieve the chart 62 repository's index. Note: 'repository' cannot be a repository alias. It must be 63 a URL. 64 65 Starting from 2.2.0, repository can be defined as the path to the directory of 66 the dependency charts stored locally. The path should start with a prefix of 67 "file://". For example, 68 69 # requirements.yaml 70 dependencies: 71 - name: nginx 72 version: "1.2.3" 73 repository: "file://../dependency_chart/nginx" 74 75 If the dependency chart is retrieved locally, it is not required to have the 76 repository added to helm by "helm add repo". Version matching is also supported 77 for this case. 78 ` 79 80 const dependencyListDesc = ` 81 List all of the dependencies declared in a chart. 82 83 This can take chart archives and chart directories as input. It will not alter 84 the contents of a chart. 85 86 This will produce an error if the chart cannot be loaded. It will emit a warning 87 if it cannot find a requirements.yaml. 88 ` 89 90 func newDependencyCmd(out io.Writer) *cobra.Command { 91 cmd := &cobra.Command{ 92 Use: "dependency update|build|list", 93 Aliases: []string{"dep", "dependencies"}, 94 Short: "manage a chart's dependencies", 95 Long: dependencyDesc, 96 } 97 98 cmd.AddCommand(newDependencyListCmd(out)) 99 cmd.AddCommand(newDependencyUpdateCmd(out)) 100 cmd.AddCommand(newDependencyBuildCmd(out)) 101 102 return cmd 103 } 104 105 type dependencyListCmd struct { 106 out io.Writer 107 chartpath string 108 } 109 110 func newDependencyListCmd(out io.Writer) *cobra.Command { 111 dlc := &dependencyListCmd{ 112 out: out, 113 } 114 cmd := &cobra.Command{ 115 Use: "list [flags] CHART", 116 Aliases: []string{"ls"}, 117 Short: "list the dependencies for the given chart", 118 Long: dependencyListDesc, 119 RunE: func(cmd *cobra.Command, args []string) error { 120 cp := "." 121 if len(args) > 0 { 122 cp = args[0] 123 } 124 125 var err error 126 dlc.chartpath, err = filepath.Abs(cp) 127 if err != nil { 128 return err 129 } 130 return dlc.run() 131 }, 132 } 133 return cmd 134 } 135 136 func (l *dependencyListCmd) run() error { 137 c, err := chartutil.Load(l.chartpath) 138 if err != nil { 139 return err 140 } 141 142 r, err := chartutil.LoadRequirements(c) 143 if err != nil { 144 if err == chartutil.ErrRequirementsNotFound { 145 fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath) 146 return nil 147 } 148 return err 149 } 150 151 l.printRequirements(r, l.out) 152 fmt.Fprintln(l.out) 153 l.printMissing(r, l.out) 154 return nil 155 } 156 157 func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string { 158 filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") 159 archives, err := filepath.Glob(filepath.Join(l.chartpath, "charts", filename)) 160 if err != nil { 161 return "bad pattern" 162 } else if len(archives) > 1 { 163 return "too many matches" 164 } else if len(archives) == 1 { 165 archive := archives[0] 166 if _, err := os.Stat(archive); err == nil { 167 c, err := chartutil.Load(archive) 168 if err != nil { 169 return "corrupt" 170 } 171 if c.Metadata.Name != dep.Name { 172 return "misnamed" 173 } 174 175 if c.Metadata.Version != dep.Version { 176 constraint, err := semver.NewConstraint(dep.Version) 177 if err != nil { 178 return "invalid version" 179 } 180 181 v, err := semver.NewVersion(c.Metadata.Version) 182 if err != nil { 183 return "invalid version" 184 } 185 186 if constraint.Check(v) { 187 return "ok" 188 } 189 return "wrong version" 190 } 191 return "ok" 192 } 193 } 194 195 folder := filepath.Join(l.chartpath, "charts", dep.Name) 196 if fi, err := os.Stat(folder); err != nil { 197 return "missing" 198 } else if !fi.IsDir() { 199 return "mispackaged" 200 } 201 202 c, err := chartutil.Load(folder) 203 if err != nil { 204 return "corrupt" 205 } 206 207 if c.Metadata.Name != dep.Name { 208 return "misnamed" 209 } 210 211 if c.Metadata.Version != dep.Version { 212 constraint, err := semver.NewConstraint(dep.Version) 213 if err != nil { 214 return "invalid version" 215 } 216 217 v, err := semver.NewVersion(c.Metadata.Version) 218 if err != nil { 219 return "invalid version" 220 } 221 222 if constraint.Check(v) { 223 return "unpacked" 224 } 225 return "wrong version" 226 } 227 228 return "unpacked" 229 } 230 231 // printRequirements prints all of the requirements in the yaml file. 232 func (l *dependencyListCmd) printRequirements(reqs *chartutil.Requirements, out io.Writer) { 233 table := uitable.New() 234 table.MaxColWidth = 80 235 table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") 236 for _, row := range reqs.Dependencies { 237 table.AddRow(row.Name, row.Version, row.Repository, l.dependencyStatus(row)) 238 } 239 fmt.Fprintln(out, table) 240 } 241 242 // printMissing prints warnings about charts that are present on disk, but are not in the requirements. 243 func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements, out io.Writer) { 244 folder := filepath.Join(l.chartpath, "charts/*") 245 files, err := filepath.Glob(folder) 246 if err != nil { 247 fmt.Fprintln(l.out, err) 248 return 249 } 250 251 for _, f := range files { 252 fi, err := os.Stat(f) 253 if err != nil { 254 fmt.Fprintf(l.out, "Warning: %s\n", err) 255 } 256 // Skip anything that is not a directory and not a tgz file. 257 if !fi.IsDir() && filepath.Ext(f) != ".tgz" { 258 continue 259 } 260 c, err := chartutil.Load(f) 261 if err != nil { 262 fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f) 263 continue 264 } 265 found := false 266 for _, d := range reqs.Dependencies { 267 if d.Name == c.Metadata.Name { 268 found = true 269 break 270 } 271 } 272 if !found { 273 fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f) 274 } 275 } 276 277 }