launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/charm/dir_test.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package charm_test
     5  
     6  import (
     7  	"archive/zip"
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  
    16  	gc "launchpad.net/gocheck"
    17  
    18  	"launchpad.net/juju-core/charm"
    19  	"launchpad.net/juju-core/testing"
    20  	"launchpad.net/juju-core/testing/testbase"
    21  )
    22  
    23  type DirSuite struct {
    24  	testbase.LoggingSuite
    25  }
    26  
    27  var _ = gc.Suite(&DirSuite{})
    28  
    29  func (s *DirSuite) TestReadDir(c *gc.C) {
    30  	path := testing.Charms.DirPath("dummy")
    31  	dir, err := charm.ReadDir(path)
    32  	c.Assert(err, gc.IsNil)
    33  	checkDummy(c, dir, path)
    34  }
    35  
    36  func (s *DirSuite) TestReadDirWithoutConfig(c *gc.C) {
    37  	path := testing.Charms.DirPath("varnish")
    38  	dir, err := charm.ReadDir(path)
    39  	c.Assert(err, gc.IsNil)
    40  
    41  	// A lacking config.yaml file still causes a proper
    42  	// Config value to be returned.
    43  	c.Assert(dir.Config().Options, gc.HasLen, 0)
    44  }
    45  
    46  func (s *DirSuite) TestBundleTo(c *gc.C) {
    47  	baseDir := c.MkDir()
    48  	charmDir := testing.Charms.ClonedDirPath(baseDir, "dummy")
    49  	var haveSymlinks = true
    50  	if err := os.Symlink("../target", filepath.Join(charmDir, "hooks/symlink")); err != nil {
    51  		haveSymlinks = false
    52  	}
    53  	dir, err := charm.ReadDir(charmDir)
    54  	c.Assert(err, gc.IsNil)
    55  	path := filepath.Join(baseDir, "bundle.charm")
    56  	file, err := os.Create(path)
    57  	c.Assert(err, gc.IsNil)
    58  	err = dir.BundleTo(file)
    59  	file.Close()
    60  	c.Assert(err, gc.IsNil)
    61  
    62  	zipr, err := zip.OpenReader(path)
    63  	c.Assert(err, gc.IsNil)
    64  	defer zipr.Close()
    65  
    66  	var metaf, instf, emptyf, revf, symf *zip.File
    67  	for _, f := range zipr.File {
    68  		c.Logf("Bundled file: %s", f.Name)
    69  		switch f.Name {
    70  		case "revision":
    71  			revf = f
    72  		case "metadata.yaml":
    73  			metaf = f
    74  		case "hooks/install":
    75  			instf = f
    76  		case "hooks/symlink":
    77  			symf = f
    78  		case "empty/":
    79  			emptyf = f
    80  		case "build/ignored":
    81  			c.Errorf("bundle includes build/*: %s", f.Name)
    82  		case ".ignored", ".dir/ignored":
    83  			c.Errorf("bundle includes .* entries: %s", f.Name)
    84  		}
    85  	}
    86  
    87  	c.Assert(revf, gc.NotNil)
    88  	reader, err := revf.Open()
    89  	c.Assert(err, gc.IsNil)
    90  	data, err := ioutil.ReadAll(reader)
    91  	reader.Close()
    92  	c.Assert(err, gc.IsNil)
    93  	c.Assert(string(data), gc.Equals, "1")
    94  
    95  	c.Assert(metaf, gc.NotNil)
    96  	reader, err = metaf.Open()
    97  	c.Assert(err, gc.IsNil)
    98  	meta, err := charm.ReadMeta(reader)
    99  	reader.Close()
   100  	c.Assert(err, gc.IsNil)
   101  	c.Assert(meta.Name, gc.Equals, "dummy")
   102  
   103  	c.Assert(instf, gc.NotNil)
   104  	// Despite it being 0751, we pack and unpack it as 0755.
   105  	c.Assert(instf.Mode()&0777, gc.Equals, os.FileMode(0755))
   106  
   107  	if haveSymlinks {
   108  		c.Assert(symf, gc.NotNil)
   109  		c.Assert(symf.Mode()&0777, gc.Equals, os.FileMode(0777))
   110  		reader, err = symf.Open()
   111  		c.Assert(err, gc.IsNil)
   112  		data, err = ioutil.ReadAll(reader)
   113  		reader.Close()
   114  		c.Assert(err, gc.IsNil)
   115  		c.Assert(string(data), gc.Equals, "../target")
   116  	} else {
   117  		c.Assert(symf, gc.IsNil)
   118  	}
   119  
   120  	c.Assert(emptyf, gc.NotNil)
   121  	c.Assert(emptyf.Mode()&os.ModeType, gc.Equals, os.ModeDir)
   122  	// Despite it being 0750, we pack and unpack it as 0755.
   123  	c.Assert(emptyf.Mode()&0777, gc.Equals, os.FileMode(0755))
   124  }
   125  
   126  // Bug #864164: Must complain if charm hooks aren't executable
   127  func (s *DirSuite) TestBundleToWithNonExecutableHooks(c *gc.C) {
   128  	hooks := []string{"install", "start", "config-changed", "upgrade-charm", "stop"}
   129  	for _, relName := range []string{"foo", "bar", "self"} {
   130  		for _, kind := range []string{"joined", "changed", "departed", "broken"} {
   131  			hooks = append(hooks, relName+"-relation-"+kind)
   132  		}
   133  	}
   134  
   135  	dir := testing.Charms.Dir("all-hooks")
   136  	path := filepath.Join(c.MkDir(), "bundle.charm")
   137  	file, err := os.Create(path)
   138  	c.Assert(err, gc.IsNil)
   139  	err = dir.BundleTo(file)
   140  	file.Close()
   141  	c.Assert(err, gc.IsNil)
   142  
   143  	tlog := c.GetTestLog()
   144  	for _, hook := range hooks {
   145  		fullpath := filepath.Join(dir.Path, "hooks", hook)
   146  		exp := fmt.Sprintf(`^(.|\n)*WARNING juju charm: making "%s" executable in charm(.|\n)*$`, fullpath)
   147  		c.Assert(tlog, gc.Matches, exp, gc.Commentf("hook %q was not made executable", fullpath))
   148  	}
   149  
   150  	// Expand it and check the hooks' permissions
   151  	// (But do not use ExpandTo(), just use the raw zip)
   152  	f, err := os.Open(path)
   153  	c.Assert(err, gc.IsNil)
   154  	defer f.Close()
   155  	fi, err := f.Stat()
   156  	c.Assert(err, gc.IsNil)
   157  	size := fi.Size()
   158  	zipr, err := zip.NewReader(f, size)
   159  	c.Assert(err, gc.IsNil)
   160  	allhooks := dir.Meta().Hooks()
   161  	for _, zfile := range zipr.File {
   162  		cleanName := filepath.Clean(zfile.Name)
   163  		if strings.HasPrefix(cleanName, "hooks") {
   164  			hookName := filepath.Base(cleanName)
   165  			if _, ok := allhooks[hookName]; ok {
   166  				perms := zfile.Mode()
   167  				c.Assert(perms&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName))
   168  			}
   169  		}
   170  	}
   171  }
   172  
   173  func (s *DirSuite) TestBundleToWithBadType(c *gc.C) {
   174  	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
   175  	badFile := filepath.Join(charmDir, "hooks", "badfile")
   176  
   177  	// Symlink targeting a path outside of the charm.
   178  	err := os.Symlink("../../target", badFile)
   179  	c.Assert(err, gc.IsNil)
   180  
   181  	dir, err := charm.ReadDir(charmDir)
   182  	c.Assert(err, gc.IsNil)
   183  
   184  	err = dir.BundleTo(&bytes.Buffer{})
   185  	c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" links out of charm: "../../target"`)
   186  
   187  	// Symlink targeting an absolute path.
   188  	os.Remove(badFile)
   189  	err = os.Symlink("/target", badFile)
   190  	c.Assert(err, gc.IsNil)
   191  
   192  	dir, err = charm.ReadDir(charmDir)
   193  	c.Assert(err, gc.IsNil)
   194  
   195  	err = dir.BundleTo(&bytes.Buffer{})
   196  	c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" is absolute: "/target"`)
   197  
   198  	// Can't bundle special files either.
   199  	os.Remove(badFile)
   200  	err = syscall.Mkfifo(badFile, 0644)
   201  	c.Assert(err, gc.IsNil)
   202  
   203  	dir, err = charm.ReadDir(charmDir)
   204  	c.Assert(err, gc.IsNil)
   205  
   206  	err = dir.BundleTo(&bytes.Buffer{})
   207  	c.Assert(err, gc.ErrorMatches, `file is a named pipe: "hooks/badfile"`)
   208  }
   209  
   210  func (s *DirSuite) TestDirRevisionFile(c *gc.C) {
   211  	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
   212  	revPath := filepath.Join(charmDir, "revision")
   213  
   214  	// Missing revision file
   215  	err := os.Remove(revPath)
   216  	c.Assert(err, gc.IsNil)
   217  
   218  	dir, err := charm.ReadDir(charmDir)
   219  	c.Assert(err, gc.IsNil)
   220  	c.Assert(dir.Revision(), gc.Equals, 0)
   221  
   222  	// Missing revision file with old revision in metadata
   223  	file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0)
   224  	c.Assert(err, gc.IsNil)
   225  	_, err = file.Write([]byte("\nrevision: 1234\n"))
   226  	c.Assert(err, gc.IsNil)
   227  
   228  	dir, err = charm.ReadDir(charmDir)
   229  	c.Assert(err, gc.IsNil)
   230  	c.Assert(dir.Revision(), gc.Equals, 1234)
   231  
   232  	// Revision file with bad content
   233  	err = ioutil.WriteFile(revPath, []byte("garbage"), 0666)
   234  	c.Assert(err, gc.IsNil)
   235  
   236  	dir, err = charm.ReadDir(charmDir)
   237  	c.Assert(err, gc.ErrorMatches, "invalid revision file")
   238  	c.Assert(dir, gc.IsNil)
   239  }
   240  
   241  func (s *DirSuite) TestDirSetRevision(c *gc.C) {
   242  	dir := testing.Charms.ClonedDir(c.MkDir(), "dummy")
   243  	c.Assert(dir.Revision(), gc.Equals, 1)
   244  	dir.SetRevision(42)
   245  	c.Assert(dir.Revision(), gc.Equals, 42)
   246  
   247  	var b bytes.Buffer
   248  	err := dir.BundleTo(&b)
   249  	c.Assert(err, gc.IsNil)
   250  
   251  	bundle, err := charm.ReadBundleBytes(b.Bytes())
   252  	c.Assert(bundle.Revision(), gc.Equals, 42)
   253  }
   254  
   255  func (s *DirSuite) TestDirSetDiskRevision(c *gc.C) {
   256  	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
   257  	dir, err := charm.ReadDir(charmDir)
   258  	c.Assert(err, gc.IsNil)
   259  
   260  	c.Assert(dir.Revision(), gc.Equals, 1)
   261  	dir.SetDiskRevision(42)
   262  	c.Assert(dir.Revision(), gc.Equals, 42)
   263  
   264  	dir, err = charm.ReadDir(charmDir)
   265  	c.Assert(err, gc.IsNil)
   266  	c.Assert(dir.Revision(), gc.Equals, 42)
   267  }