github.com/migueleliasweb/helm@v2.6.1+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' can be an alias. The alias must start 63 with 'alias:' or '@'. 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{out: out} 112 113 cmd := &cobra.Command{ 114 Use: "list [flags] CHART", 115 Aliases: []string{"ls"}, 116 Short: "list the dependencies for the given chart", 117 Long: dependencyListDesc, 118 RunE: func(cmd *cobra.Command, args []string) error { 119 cp := "." 120 if len(args) > 0 { 121 cp = args[0] 122 } 123 124 var err error 125 dlc.chartpath, err = filepath.Abs(cp) 126 if err != nil { 127 return err 128 } 129 return dlc.run() 130 }, 131 } 132 return cmd 133 } 134 135 func (l *dependencyListCmd) run() error { 136 c, err := chartutil.Load(l.chartpath) 137 if err != nil { 138 return err 139 } 140 141 r, err := chartutil.LoadRequirements(c) 142 if err != nil { 143 if err == chartutil.ErrRequirementsNotFound { 144 fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath) 145 return nil 146 } 147 return err 148 } 149 150 l.printRequirements(r, l.out) 151 fmt.Fprintln(l.out) 152 l.printMissing(r) 153 return nil 154 } 155 156 func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string { 157 filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") 158 archives, err := filepath.Glob(filepath.Join(l.chartpath, "charts", filename)) 159 if err != nil { 160 return "bad pattern" 161 } else if len(archives) > 1 { 162 return "too many matches" 163 } else if len(archives) == 1 { 164 archive := archives[0] 165 if _, err := os.Stat(archive); err == nil { 166 c, err := chartutil.Load(archive) 167 if err != nil { 168 return "corrupt" 169 } 170 if c.Metadata.Name != dep.Name { 171 return "misnamed" 172 } 173 174 if c.Metadata.Version != dep.Version { 175 constraint, err := semver.NewConstraint(dep.Version) 176 if err != nil { 177 return "invalid version" 178 } 179 180 v, err := semver.NewVersion(c.Metadata.Version) 181 if err != nil { 182 return "invalid version" 183 } 184 185 if constraint.Check(v) { 186 return "ok" 187 } 188 return "wrong version" 189 } 190 return "ok" 191 } 192 } 193 194 folder := filepath.Join(l.chartpath, "charts", dep.Name) 195 if fi, err := os.Stat(folder); err != nil { 196 return "missing" 197 } else if !fi.IsDir() { 198 return "mispackaged" 199 } 200 201 c, err := chartutil.Load(folder) 202 if err != nil { 203 return "corrupt" 204 } 205 206 if c.Metadata.Name != dep.Name { 207 return "misnamed" 208 } 209 210 if c.Metadata.Version != dep.Version { 211 constraint, err := semver.NewConstraint(dep.Version) 212 if err != nil { 213 return "invalid version" 214 } 215 216 v, err := semver.NewVersion(c.Metadata.Version) 217 if err != nil { 218 return "invalid version" 219 } 220 221 if constraint.Check(v) { 222 return "unpacked" 223 } 224 return "wrong version" 225 } 226 227 return "unpacked" 228 } 229 230 // printRequirements prints all of the requirements in the yaml file. 231 func (l *dependencyListCmd) printRequirements(reqs *chartutil.Requirements, out io.Writer) { 232 table := uitable.New() 233 table.MaxColWidth = 80 234 table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") 235 for _, row := range reqs.Dependencies { 236 table.AddRow(row.Name, row.Version, row.Repository, l.dependencyStatus(row)) 237 } 238 fmt.Fprintln(out, table) 239 } 240 241 // printMissing prints warnings about charts that are present on disk, but are not in the requirements. 242 func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements) { 243 folder := filepath.Join(l.chartpath, "charts/*") 244 files, err := filepath.Glob(folder) 245 if err != nil { 246 fmt.Fprintln(l.out, err) 247 return 248 } 249 250 for _, f := range files { 251 fi, err := os.Stat(f) 252 if err != nil { 253 fmt.Fprintf(l.out, "Warning: %s\n", err) 254 } 255 // Skip anything that is not a directory and not a tgz file. 256 if !fi.IsDir() && filepath.Ext(f) != ".tgz" { 257 continue 258 } 259 c, err := chartutil.Load(f) 260 if err != nil { 261 fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f) 262 continue 263 } 264 found := false 265 for _, d := range reqs.Dependencies { 266 if d.Name == c.Metadata.Name { 267 found = true 268 break 269 } 270 } 271 if !found { 272 fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f) 273 } 274 } 275 276 }