gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/charmdir_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "archive/zip" 8 "bytes" 9 "fmt" 10 jc "github.com/juju/testing/checkers" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 "syscall" 16 17 "github.com/juju/testing" 18 gc "gopkg.in/check.v1" 19 20 "gopkg.in/juju/charm.v6-unstable" 21 ) 22 23 type CharmDirSuite struct { 24 testing.IsolationSuite 25 } 26 27 var _ = gc.Suite(&CharmDirSuite{}) 28 29 func (s *CharmDirSuite) TestIsCharmDirGoodCharm(c *gc.C) { 30 path := charmDirPath(c, "dummy") 31 c.Assert(charm.IsCharmDir(path), jc.IsTrue) 32 } 33 34 func (s *CharmDirSuite) TestIsCharmDirBundle(c *gc.C) { 35 path := bundleDirPath(c, "wordpress-simple") 36 c.Assert(charm.IsCharmDir(path), jc.IsFalse) 37 } 38 39 func (s *CharmDirSuite) TestIsCharmDirNoMetadataYaml(c *gc.C) { 40 path := charmDirPath(c, "bad") 41 c.Assert(charm.IsCharmDir(path), jc.IsFalse) 42 } 43 44 func (s *CharmDirSuite) TestReadCharmDir(c *gc.C) { 45 path := charmDirPath(c, "dummy") 46 dir, err := charm.ReadCharmDir(path) 47 c.Assert(err, gc.IsNil) 48 checkDummy(c, dir, path) 49 } 50 51 func (s *CharmDirSuite) TestReadCharmDirWithoutConfig(c *gc.C) { 52 path := charmDirPath(c, "varnish") 53 dir, err := charm.ReadCharmDir(path) 54 c.Assert(err, gc.IsNil) 55 56 // A lacking config.yaml file still causes a proper 57 // Config value to be returned. 58 c.Assert(dir.Config().Options, gc.HasLen, 0) 59 } 60 61 func (s *CharmDirSuite) TestReadCharmDirWithoutMetrics(c *gc.C) { 62 path := charmDirPath(c, "varnish") 63 dir, err := charm.ReadCharmDir(path) 64 c.Assert(err, gc.IsNil) 65 66 // A lacking metrics.yaml file indicates the unit will not 67 // be metered. 68 c.Assert(dir.Metrics(), gc.IsNil) 69 } 70 71 func (s *CharmDirSuite) TestReadCharmDirWithEmptyMetrics(c *gc.C) { 72 path := charmDirPath(c, "metered-empty") 73 dir, err := charm.ReadCharmDir(path) 74 c.Assert(err, gc.IsNil) 75 c.Assert(Keys(dir.Metrics()), gc.HasLen, 0) 76 } 77 78 func (s *CharmDirSuite) TestReadCharmDirWithCustomMetrics(c *gc.C) { 79 path := charmDirPath(c, "metered") 80 dir, err := charm.ReadCharmDir(path) 81 c.Assert(err, gc.IsNil) 82 83 c.Assert(dir.Metrics(), gc.NotNil) 84 c.Assert(Keys(dir.Metrics()), gc.DeepEquals, []string{"juju-unit-time", "pings"}) 85 } 86 87 func (s *CharmDirSuite) TestReadCharmDirWithoutActions(c *gc.C) { 88 path := charmDirPath(c, "wordpress") 89 dir, err := charm.ReadCharmDir(path) 90 c.Assert(err, gc.IsNil) 91 92 // A lacking actions.yaml file still causes a proper 93 // Actions value to be returned. 94 c.Assert(dir.Actions().ActionSpecs, gc.HasLen, 0) 95 } 96 97 func (s *CharmDirSuite) TestArchiveTo(c *gc.C) { 98 baseDir := c.MkDir() 99 charmDir := cloneDir(c, charmDirPath(c, "dummy")) 100 s.assertArchiveTo(c, baseDir, charmDir) 101 } 102 103 func (s *CharmDirSuite) TestArchiveToWithSymlinkedRootDir(c *gc.C) { 104 path := cloneDir(c, charmDirPath(c, "dummy")) 105 baseDir := filepath.Dir(path) 106 err := os.Symlink(filepath.Join("dummy"), filepath.Join(baseDir, "newdummy")) 107 c.Assert(err, gc.IsNil) 108 charmDir := filepath.Join(baseDir, "newdummy") 109 110 s.assertArchiveTo(c, baseDir, charmDir) 111 } 112 113 func (s *CharmDirSuite) assertArchiveTo(c *gc.C, baseDir, charmDir string) { 114 haveSymlinks := true 115 if err := os.Symlink("../target", filepath.Join(charmDir, "hooks/symlink")); err != nil { 116 haveSymlinks = false 117 } 118 dir, err := charm.ReadCharmDir(charmDir) 119 c.Assert(err, gc.IsNil) 120 path := filepath.Join(baseDir, "archive.charm") 121 file, err := os.Create(path) 122 c.Assert(err, gc.IsNil) 123 err = dir.ArchiveTo(file) 124 file.Close() 125 c.Assert(err, gc.IsNil) 126 127 zipr, err := zip.OpenReader(path) 128 c.Assert(err, gc.IsNil) 129 defer zipr.Close() 130 131 var metaf, instf, emptyf, revf, symf *zip.File 132 for _, f := range zipr.File { 133 c.Logf("Archived file: %s", f.Name) 134 switch f.Name { 135 case "revision": 136 revf = f 137 case "metadata.yaml": 138 metaf = f 139 case "hooks/install": 140 instf = f 141 case "hooks/symlink": 142 symf = f 143 case "empty/": 144 emptyf = f 145 case "build/ignored": 146 c.Errorf("archive includes build/*: %s", f.Name) 147 case ".ignored", ".dir/ignored": 148 c.Errorf("archive includes .* entries: %s", f.Name) 149 } 150 } 151 152 c.Assert(revf, gc.NotNil) 153 reader, err := revf.Open() 154 c.Assert(err, gc.IsNil) 155 data, err := ioutil.ReadAll(reader) 156 reader.Close() 157 c.Assert(err, gc.IsNil) 158 c.Assert(string(data), gc.Equals, "1") 159 160 c.Assert(metaf, gc.NotNil) 161 reader, err = metaf.Open() 162 c.Assert(err, gc.IsNil) 163 meta, err := charm.ReadMeta(reader) 164 reader.Close() 165 c.Assert(err, gc.IsNil) 166 c.Assert(meta.Name, gc.Equals, "dummy") 167 168 c.Assert(instf, gc.NotNil) 169 // Despite it being 0751, we pack and unpack it as 0755. 170 c.Assert(instf.Mode()&0777, gc.Equals, os.FileMode(0755)) 171 172 if haveSymlinks { 173 c.Assert(symf, gc.NotNil) 174 c.Assert(symf.Mode()&0777, gc.Equals, os.FileMode(0777)) 175 reader, err = symf.Open() 176 c.Assert(err, gc.IsNil) 177 data, err = ioutil.ReadAll(reader) 178 reader.Close() 179 c.Assert(err, gc.IsNil) 180 c.Assert(string(data), gc.Equals, "../target") 181 } else { 182 c.Assert(symf, gc.IsNil) 183 } 184 185 c.Assert(emptyf, gc.NotNil) 186 c.Assert(emptyf.Mode()&os.ModeType, gc.Equals, os.ModeDir) 187 // Despite it being 0750, we pack and unpack it as 0755. 188 c.Assert(emptyf.Mode()&0777, gc.Equals, os.FileMode(0755)) 189 } 190 191 // Bug #864164: Must complain if charm hooks aren't executable 192 func (s *CharmDirSuite) TestArchiveToWithNonExecutableHooks(c *gc.C) { 193 hooks := []string{"install", "start", "config-changed", "upgrade-charm", "stop", "collect-metrics", "meter-status-changed"} 194 for _, relName := range []string{"foo", "bar", "self"} { 195 for _, kind := range []string{"joined", "changed", "departed", "broken"} { 196 hooks = append(hooks, relName+"-relation-"+kind) 197 } 198 } 199 200 dir := readCharmDir(c, "all-hooks") 201 path := filepath.Join(c.MkDir(), "archive.charm") 202 file, err := os.Create(path) 203 c.Assert(err, gc.IsNil) 204 err = dir.ArchiveTo(file) 205 file.Close() 206 c.Assert(err, gc.IsNil) 207 208 tlog := c.GetTestLog() 209 for _, hook := range hooks { 210 fullpath := filepath.Join(dir.Path, "hooks", hook) 211 exp := fmt.Sprintf(`^(.|\n)*WARNING juju.charm making "%s" executable in charm(.|\n)*$`, fullpath) 212 c.Assert(tlog, gc.Matches, exp, gc.Commentf("hook %q was not made executable", fullpath)) 213 } 214 215 // Expand it and check the hooks' permissions 216 // (But do not use ExpandTo(), just use the raw zip) 217 f, err := os.Open(path) 218 c.Assert(err, gc.IsNil) 219 defer f.Close() 220 fi, err := f.Stat() 221 c.Assert(err, gc.IsNil) 222 size := fi.Size() 223 zipr, err := zip.NewReader(f, size) 224 c.Assert(err, gc.IsNil) 225 allhooks := dir.Meta().Hooks() 226 for _, zfile := range zipr.File { 227 cleanName := filepath.Clean(zfile.Name) 228 if strings.HasPrefix(cleanName, "hooks") { 229 hookName := filepath.Base(cleanName) 230 if _, ok := allhooks[hookName]; ok { 231 perms := zfile.Mode() 232 c.Assert(perms&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName)) 233 } 234 } 235 } 236 } 237 238 func (s *CharmDirSuite) TestArchiveToWithBadType(c *gc.C) { 239 charmDir := cloneDir(c, charmDirPath(c, "dummy")) 240 badFile := filepath.Join(charmDir, "hooks", "badfile") 241 242 // Symlink targeting a path outside of the charm. 243 err := os.Symlink("../../target", badFile) 244 c.Assert(err, gc.IsNil) 245 246 dir, err := charm.ReadCharmDir(charmDir) 247 c.Assert(err, gc.IsNil) 248 249 err = dir.ArchiveTo(&bytes.Buffer{}) 250 c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" links out of charm: "../../target"`) 251 252 // Symlink targeting an absolute path. 253 os.Remove(badFile) 254 err = os.Symlink("/target", badFile) 255 c.Assert(err, gc.IsNil) 256 257 dir, err = charm.ReadCharmDir(charmDir) 258 c.Assert(err, gc.IsNil) 259 260 err = dir.ArchiveTo(&bytes.Buffer{}) 261 c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" is absolute: "/target"`) 262 263 // Can't archive special files either. 264 os.Remove(badFile) 265 err = syscall.Mkfifo(badFile, 0644) 266 c.Assert(err, gc.IsNil) 267 268 dir, err = charm.ReadCharmDir(charmDir) 269 c.Assert(err, gc.IsNil) 270 271 err = dir.ArchiveTo(&bytes.Buffer{}) 272 c.Assert(err, gc.ErrorMatches, `file is a named pipe: "hooks/badfile"`) 273 } 274 275 func (s *CharmDirSuite) TestDirRevisionFile(c *gc.C) { 276 charmDir := cloneDir(c, charmDirPath(c, "dummy")) 277 revPath := filepath.Join(charmDir, "revision") 278 279 // Missing revision file 280 err := os.Remove(revPath) 281 c.Assert(err, gc.IsNil) 282 283 dir, err := charm.ReadCharmDir(charmDir) 284 c.Assert(err, gc.IsNil) 285 c.Assert(dir.Revision(), gc.Equals, 0) 286 287 // Missing revision file with obsolete old revision in metadata ignores 288 // the old revision field. 289 file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0) 290 c.Assert(err, gc.IsNil) 291 _, err = file.Write([]byte("\nrevision: 1234\n")) 292 c.Assert(err, gc.IsNil) 293 294 dir, err = charm.ReadCharmDir(charmDir) 295 c.Assert(err, gc.IsNil) 296 c.Assert(dir.Revision(), gc.Equals, 0) 297 298 // Revision file with bad content 299 err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) 300 c.Assert(err, gc.IsNil) 301 302 dir, err = charm.ReadCharmDir(charmDir) 303 c.Assert(err, gc.ErrorMatches, "invalid revision file") 304 c.Assert(dir, gc.IsNil) 305 } 306 307 func (s *CharmDirSuite) TestDirSetRevision(c *gc.C) { 308 path := cloneDir(c, charmDirPath(c, "dummy")) 309 dir, err := charm.ReadCharmDir(path) 310 c.Assert(err, gc.IsNil) 311 c.Assert(dir.Revision(), gc.Equals, 1) 312 dir.SetRevision(42) 313 c.Assert(dir.Revision(), gc.Equals, 42) 314 315 var b bytes.Buffer 316 err = dir.ArchiveTo(&b) 317 c.Assert(err, gc.IsNil) 318 319 archive, err := charm.ReadCharmArchiveBytes(b.Bytes()) 320 c.Assert(archive.Revision(), gc.Equals, 42) 321 } 322 323 func (s *CharmDirSuite) TestDirSetDiskRevision(c *gc.C) { 324 charmDir := cloneDir(c, charmDirPath(c, "dummy")) 325 dir, err := charm.ReadCharmDir(charmDir) 326 c.Assert(err, gc.IsNil) 327 328 c.Assert(dir.Revision(), gc.Equals, 1) 329 dir.SetDiskRevision(42) 330 c.Assert(dir.Revision(), gc.Equals, 42) 331 332 dir, err = charm.ReadCharmDir(charmDir) 333 c.Assert(err, gc.IsNil) 334 c.Assert(dir.Revision(), gc.Equals, 42) 335 }