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  }