github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/juju/publish_test.go (about)

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