github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/commands/publish_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/cmd"
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/charm.v5/charmrepo"
    16  
    17  	"github.com/juju/juju/bzr"
    18  	"github.com/juju/juju/cmd/envcmd"
    19  	"github.com/juju/juju/testing"
    20  )
    21  
    22  // Sadly, this is a very slow test suite, heavily dominated by calls to bzr.
    23  
    24  type PublishSuite struct {
    25  	testing.FakeJujuHomeSuite
    26  	gitjujutesting.HTTPSuite
    27  
    28  	dir        string
    29  	oldBaseURL string
    30  	branch     *bzr.Branch
    31  }
    32  
    33  var _ = gc.Suite(&PublishSuite{})
    34  
    35  func touch(c *gc.C, filename string) {
    36  	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
    37  	c.Assert(err, jc.ErrorIsNil)
    38  	f.Close()
    39  }
    40  
    41  func addMeta(c *gc.C, branch *bzr.Branch, meta string) {
    42  	if meta == "" {
    43  		meta = "name: wordpress\nsummary: Some summary\ndescription: Some description.\n"
    44  	}
    45  	f, err := os.Create(branch.Join("metadata.yaml"))
    46  	c.Assert(err, jc.ErrorIsNil)
    47  	_, err = f.Write([]byte(meta))
    48  	f.Close()
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	err = branch.Add("metadata.yaml")
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	err = branch.Commit("Added metadata.yaml.")
    53  	c.Assert(err, jc.ErrorIsNil)
    54  }
    55  
    56  func (s *PublishSuite) runPublish(c *gc.C, args ...string) (*cmd.Context, error) {
    57  	return testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), args, s.dir)
    58  }
    59  
    60  const pollDelay = testing.ShortWait
    61  
    62  func (s *PublishSuite) SetUpSuite(c *gc.C) {
    63  	s.FakeJujuHomeSuite.SetUpSuite(c)
    64  	s.HTTPSuite.SetUpSuite(c)
    65  
    66  	s.oldBaseURL = charmrepo.LegacyStore.BaseURL
    67  	charmrepo.LegacyStore.BaseURL = s.URL("")
    68  }
    69  
    70  func (s *PublishSuite) TearDownSuite(c *gc.C) {
    71  	s.FakeJujuHomeSuite.TearDownSuite(c)
    72  	s.HTTPSuite.TearDownSuite(c)
    73  
    74  	charmrepo.LegacyStore.BaseURL = s.oldBaseURL
    75  }
    76  
    77  func (s *PublishSuite) SetUpTest(c *gc.C) {
    78  	s.FakeJujuHomeSuite.SetUpTest(c)
    79  	s.HTTPSuite.SetUpTest(c)
    80  	s.PatchEnvironment("BZR_HOME", utils.Home())
    81  	s.FakeJujuHomeSuite.Home.AddFiles(c, gitjujutesting.TestFile{
    82  		Name: bzrHomeFile,
    83  		Data: "[DEFAULT]\nemail = Test <testing@testing.invalid>\n",
    84  	})
    85  
    86  	s.dir = c.MkDir()
    87  	s.branch = bzr.New(s.dir)
    88  	err := s.branch.Init()
    89  	c.Assert(err, jc.ErrorIsNil)
    90  }
    91  
    92  func (s *PublishSuite) TearDownTest(c *gc.C) {
    93  	s.HTTPSuite.TearDownTest(c)
    94  	s.FakeJujuHomeSuite.TearDownTest(c)
    95  }
    96  
    97  func (s *PublishSuite) TestNoBranch(c *gc.C) {
    98  	dir := c.MkDir()
    99  	_, err := testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), []string{"cs:precise/wordpress"}, dir)
   100  	// We need to do this here because \U is outputed on windows
   101  	// and it's an invalid regex escape sequence
   102  	c.Assert(err.Error(), gc.Equals, fmt.Sprintf("not a charm branch: %s", dir))
   103  }
   104  
   105  func (s *PublishSuite) TestEmpty(c *gc.C) {
   106  	_, err := s.runPublish(c, "cs:precise/wordpress")
   107  	c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`)
   108  }
   109  
   110  func (s *PublishSuite) TestFrom(c *gc.C) {
   111  	_, err := testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), []string{"--from", s.dir, "cs:precise/wordpress"}, c.MkDir())
   112  	c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`)
   113  }
   114  
   115  func (s *PublishSuite) TestMissingSeries(c *gc.C) {
   116  	_, err := s.runPublish(c, "cs:wordpress")
   117  	c.Assert(err, gc.ErrorMatches, `cannot infer charm URL for "cs:wordpress": charm url series is not resolved`)
   118  }
   119  
   120  func (s *PublishSuite) TestNotClean(c *gc.C) {
   121  	touch(c, s.branch.Join("file"))
   122  	_, err := s.runPublish(c, "cs:precise/wordpress")
   123  	c.Assert(err, gc.ErrorMatches, `branch is not clean \(bzr status\)`)
   124  }
   125  
   126  func (s *PublishSuite) TestNoPushLocation(c *gc.C) {
   127  	addMeta(c, s.branch, "")
   128  	_, err := s.runPublish(c)
   129  	c.Assert(err, gc.ErrorMatches, `no charm URL provided and cannot infer from current directory \(no push location\)`)
   130  }
   131  
   132  func (s *PublishSuite) TestUnknownPushLocation(c *gc.C) {
   133  	addMeta(c, s.branch, "")
   134  	err := s.branch.Push(&bzr.PushAttr{Location: c.MkDir() + "/foo", Remember: true})
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	_, err = s.runPublish(c)
   137  	c.Assert(err, gc.ErrorMatches, `cannot infer charm URL from branch location: ".*/foo"`)
   138  }
   139  
   140  func (s *PublishSuite) TestWrongRepository(c *gc.C) {
   141  	addMeta(c, s.branch, "")
   142  	_, err := s.runPublish(c, "local:precise/wordpress")
   143  	c.Assert(err, gc.ErrorMatches, "charm URL must reference the juju charm store")
   144  }
   145  
   146  func (s *PublishSuite) TestInferURL(c *gc.C) {
   147  	addMeta(c, s.branch, "")
   148  
   149  	cmd := &PublishCommand{}
   150  	cmd.ChangePushLocation(func(location string) string {
   151  		c.Assert(location, gc.Equals, "lp:charms/precise/wordpress")
   152  		c.SucceedNow()
   153  		panic("unreachable")
   154  	})
   155  
   156  	_, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"precise/wordpress"}, s.dir)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	c.Fatal("shouldn't get here; location closure didn't run?")
   159  }
   160  
   161  func (s *PublishSuite) TestBrokenCharm(c *gc.C) {
   162  	addMeta(c, s.branch, "name: wordpress\nsummary: Some summary\n")
   163  	_, err := s.runPublish(c, "cs:precise/wordpress")
   164  	c.Assert(err, gc.ErrorMatches, "metadata: description: expected string, got nothing")
   165  }
   166  
   167  func (s *PublishSuite) TestWrongName(c *gc.C) {
   168  	addMeta(c, s.branch, "")
   169  	_, err := s.runPublish(c, "cs:precise/mysql")
   170  	c.Assert(err, gc.ErrorMatches, `charm name in metadata must match name in URL: "wordpress" != "mysql"`)
   171  }
   172  
   173  func (s *PublishSuite) TestPreExistingPublished(c *gc.C) {
   174  	addMeta(c, s.branch, "")
   175  
   176  	// Pretend the store has seen the digest before, and it has succeeded.
   177  	digest, err := s.branch.RevisionId()
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	body := `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   180  	gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   181  
   182  	ctx, err := s.runPublish(c, "cs:precise/wordpress")
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n")
   185  
   186  	req := gitjujutesting.Server.WaitRequest()
   187  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   188  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   189  }
   190  
   191  func (s *PublishSuite) TestPreExistingPublishedEdge(c *gc.C) {
   192  	addMeta(c, s.branch, "")
   193  
   194  	// If it doesn't find the right digest on the first try, it asks again for
   195  	// any digest at all to keep the tip in mind. There's a small chance that
   196  	// on the second request the tip has changed and matches the digest we're
   197  	// looking for, in which case we have the answer already.
   198  	digest, err := s.branch.RevisionId()
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	var body string
   201  	body = `{"cs:precise/wordpress": {"errors": ["entry not found"]}}`
   202  	gitjujutesting.Server.Response(200, nil, []byte(body))
   203  	body = `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   204  	gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   205  
   206  	ctx, err := s.runPublish(c, "cs:precise/wordpress")
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n")
   209  
   210  	req := gitjujutesting.Server.WaitRequest()
   211  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   212  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   213  
   214  	req = gitjujutesting.Server.WaitRequest()
   215  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   216  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress")
   217  }
   218  
   219  func (s *PublishSuite) TestPreExistingPublishError(c *gc.C) {
   220  	addMeta(c, s.branch, "")
   221  
   222  	// Pretend the store has seen the digest before, and it has failed.
   223  	digest, err := s.branch.RevisionId()
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	body := `{"cs:precise/wordpress": {"kind": "publish-error", "digest": %q, "errors": ["an error"]}}`
   226  	gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   227  
   228  	_, err = s.runPublish(c, "cs:precise/wordpress")
   229  	c.Assert(err, gc.ErrorMatches, "charm could not be published: an error")
   230  
   231  	req := gitjujutesting.Server.WaitRequest()
   232  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   233  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   234  }
   235  
   236  func (s *PublishSuite) TestFullPublish(c *gc.C) {
   237  	addMeta(c, s.branch, "")
   238  
   239  	digest, err := s.branch.RevisionId()
   240  	c.Assert(err, jc.ErrorIsNil)
   241  
   242  	pushBranch := bzr.New(c.MkDir())
   243  	err = pushBranch.Init()
   244  	c.Assert(err, jc.ErrorIsNil)
   245  
   246  	cmd := &PublishCommand{}
   247  	cmd.ChangePushLocation(func(location string) string {
   248  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   249  		return pushBranch.Location()
   250  	})
   251  	cmd.SetPollDelay(testing.ShortWait)
   252  
   253  	var body string
   254  
   255  	// The local digest isn't found.
   256  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   257  	gitjujutesting.Server.Response(200, nil, []byte(body))
   258  
   259  	// But the charm exists with an arbitrary non-matching digest.
   260  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}`
   261  	gitjujutesting.Server.Response(200, nil, []byte(body))
   262  
   263  	// After the branch is pushed we fake the publishing delay.
   264  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}`
   265  	gitjujutesting.Server.Response(200, nil, []byte(body))
   266  
   267  	// And finally report success.
   268  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   269  	gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   270  
   271  	ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir)
   272  	c.Assert(err, jc.ErrorIsNil)
   273  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n")
   274  
   275  	// Ensure the branch was actually pushed.
   276  	pushDigest, err := pushBranch.RevisionId()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	c.Assert(pushDigest, gc.Equals, digest)
   279  
   280  	// And that all the requests were sent with the proper data.
   281  	req := gitjujutesting.Server.WaitRequest()
   282  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   283  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   284  
   285  	for i := 0; i < 3; i++ {
   286  		// The second request grabs tip to see the current state, and the
   287  		// following requests are done after pushing to see when it changes.
   288  		req = gitjujutesting.Server.WaitRequest()
   289  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   290  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   291  	}
   292  }
   293  
   294  func (s *PublishSuite) TestFullPublishError(c *gc.C) {
   295  	addMeta(c, s.branch, "")
   296  
   297  	digest, err := s.branch.RevisionId()
   298  	c.Assert(err, jc.ErrorIsNil)
   299  
   300  	pushBranch := bzr.New(c.MkDir())
   301  	err = pushBranch.Init()
   302  	c.Assert(err, jc.ErrorIsNil)
   303  
   304  	cmd := &PublishCommand{}
   305  	cmd.ChangePushLocation(func(location string) string {
   306  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   307  		return pushBranch.Location()
   308  	})
   309  	cmd.SetPollDelay(pollDelay)
   310  
   311  	var body string
   312  
   313  	// The local digest isn't found.
   314  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   315  	gitjujutesting.Server.Response(200, nil, []byte(body))
   316  
   317  	// And tip isn't found either, meaning the charm was never published.
   318  	gitjujutesting.Server.Response(200, nil, []byte(body))
   319  
   320  	// After the branch is pushed we fake the publishing delay.
   321  	gitjujutesting.Server.Response(200, nil, []byte(body))
   322  
   323  	// And finally report success.
   324  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   325  	gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   326  
   327  	ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n")
   330  
   331  	// Ensure the branch was actually pushed.
   332  	pushDigest, err := pushBranch.RevisionId()
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	c.Assert(pushDigest, gc.Equals, digest)
   335  
   336  	// And that all the requests were sent with the proper data.
   337  	req := gitjujutesting.Server.WaitRequest()
   338  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   339  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   340  
   341  	for i := 0; i < 3; i++ {
   342  		// The second request grabs tip to see the current state, and the
   343  		// following requests are done after pushing to see when it changes.
   344  		req = gitjujutesting.Server.WaitRequest()
   345  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   346  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   347  	}
   348  }
   349  
   350  func (s *PublishSuite) TestFullPublishRace(c *gc.C) {
   351  	addMeta(c, s.branch, "")
   352  
   353  	digest, err := s.branch.RevisionId()
   354  	c.Assert(err, jc.ErrorIsNil)
   355  
   356  	pushBranch := bzr.New(c.MkDir())
   357  	err = pushBranch.Init()
   358  	c.Assert(err, jc.ErrorIsNil)
   359  
   360  	cmd := &PublishCommand{}
   361  	cmd.ChangePushLocation(func(location string) string {
   362  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   363  		return pushBranch.Location()
   364  	})
   365  	cmd.SetPollDelay(pollDelay)
   366  
   367  	var body string
   368  
   369  	// The local digest isn't found.
   370  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   371  	gitjujutesting.Server.Response(200, nil, []byte(body))
   372  
   373  	// And tip isn't found either, meaning the charm was never published.
   374  	gitjujutesting.Server.Response(200, nil, []byte(body))
   375  
   376  	// After the branch is pushed we fake the publishing delay.
   377  	gitjujutesting.Server.Response(200, nil, []byte(body))
   378  
   379  	// But, surprisingly, the digest changed to something else entirely.
   380  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "surprising-digest", "revision": 42}}`
   381  	gitjujutesting.Server.Response(200, nil, []byte(body))
   382  
   383  	_, err = testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir)
   384  	c.Assert(err, gc.ErrorMatches, `charm changed but not to local charm digest; publishing race\?`)
   385  
   386  	// Ensure the branch was actually pushed.
   387  	pushDigest, err := pushBranch.RevisionId()
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	c.Assert(pushDigest, gc.Equals, digest)
   390  
   391  	// And that all the requests were sent with the proper data.
   392  	req := gitjujutesting.Server.WaitRequest()
   393  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   394  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   395  
   396  	for i := 0; i < 3; i++ {
   397  		// The second request grabs tip to see the current state, and the
   398  		// following requests are done after pushing to see when it changes.
   399  		req = gitjujutesting.Server.WaitRequest()
   400  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   401  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   402  	}
   403  }