all: use v5 of the charmstore API #4807

Merged
merged 1 commit into from Mar 21, 2016
Jump to file or symbol
Failed to load files and symbols.
+250 −358
Split
@@ -45,7 +45,7 @@ func (s *CharmSuite) SetUpTest(c *gc.C) {
AuthUsername: "test-user",
AuthPassword: "test-password",
}
- handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4)
+ handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5)
c.Assert(err, jc.ErrorIsNil)
s.Handler = handler
s.Server = httptest.NewServer(handler)
@@ -56,7 +56,7 @@ func (s *CharmStoreSuite) SetUpTest(c *gc.C) {
IdentityLocation: s.discharger.Location(),
PublicKeyLocator: s.discharger,
}
- handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4)
+ handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5)
c.Assert(err, jc.ErrorIsNil)
s.handler = handler
s.Srv = httptest.NewServer(handler)
View
@@ -70,11 +70,23 @@ type ResourcesClient interface {
type baseClient struct {
*csclient.Client
- fakeCharmStoreClient
asRepo func() *charmrepo.CharmStore
}
+// TODO(ericsnow) Remove the fake methods once the charm store adds support.
+
+// ListResources implements ResourcesClient.ListResources as a noop.
+func (baseClient) ListResources(charmURLs []*charm.URL) ([][]charmresource.Resource, error) {
+ res := make([][]charmresource.Resource, len(charmURLs))
+ return res, nil
+}
+
+// GetResource implements ResourcesClient.GetResource as a noop.
+func (baseClient) GetResource(cURL *charm.URL, resourceName string, revision int) (charmresource.Resource, io.ReadCloser, error) {
+ return charmresource.Resource{}, nil, errors.NotFoundf("resource %q", resourceName)
+}
+
// TODO(ericsnow) We must make the Juju metadata available here since
// we must use charmrepo.NewCharmStore(), which doesn't give us an
// alternative.
@@ -193,20 +205,3 @@ func (c *Client) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision,
}
return c.BaseClient.LatestRevisions(cURLs)
}
-
-// TODO(ericsnow) Add an "AsRepo() charmrepo.Interface" method.
-
-// TODO(ericsnow) Remove the fake once the charm store adds support.
-
-type fakeCharmStoreClient struct{}
-
-// ListResources implements BaseClient as a noop.
-func (fakeCharmStoreClient) ListResources(charmURLs []*charm.URL) ([][]charmresource.Resource, error) {
- res := make([][]charmresource.Resource, len(charmURLs))
- return res, nil
-}
-
-// GetResource implements BaseClient as a noop.
-func (fakeCharmStoreClient) GetResource(cURL *charm.URL, resourceName string, revision int) (charmresource.Resource, io.ReadCloser, error) {
- return charmresource.Resource{}, nil, errors.NotFoundf("resource %q", resourceName)
-}
@@ -4,26 +4,14 @@
package charmcmd
import (
- "net/http"
- "net/url"
- "os"
-
"github.com/juju/cmd"
"github.com/juju/errors"
- "github.com/juju/idmclient/ussologin"
- "github.com/juju/persistent-cookiejar"
"gopkg.in/juju/charmrepo.v2-unstable"
- "gopkg.in/juju/environschema.v1/form"
- "gopkg.in/macaroon-bakery.v1/httpbakery"
"github.com/juju/juju/charmstore"
- "github.com/juju/juju/jujuclient"
+ "github.com/juju/juju/cmd/modelcmd"
)
-// TODO(ericsnow) Factor out code from cmd/juju/commands/common.go and
-// cmd/envcmd/base.go into cmd/charmstore.go and cmd/apicontext.go. Then
-// use those here instead of copy-and-pasting here.
-
///////////////////
// The charmstoreSpec code is based loosely on code in cmd/juju/commands/deploy.go.
@@ -44,86 +32,20 @@ func newCharmstoreSpec() CharmstoreSpec {
// Connect implements CharmstoreSpec.
func (cs charmstoreSpec) Connect(ctx *cmd.Context) (*charmstore.Client, error) {
- visitWebPage := httpbakery.OpenWebBrowser
- if ctx != nil {
- filler := &form.IOFiller{
- In: ctx.Stdin,
- Out: ctx.Stderr,
- }
- visitWebPage = ussologin.VisitWebPage(
- filler,
- &http.Client{},
- jujuclient.NewTokenStore(),
- )
- }
- apiContext, err := newAPIContext(visitWebPage)
+ // Note that creating the API context in Connect is technically
+ // wrong, as it means we'll be creating the bakery context
+ // (and reading/writing the cookies) each time it's called.
+ // TODO(ericsnow) Use modelcmd.ModelCommandBase instead.
+ apiContext, err := modelcmd.NewAPIContext(ctx)
if err != nil {
return nil, errors.Trace(err)
}
-
client := charmstore.NewClient(charmstore.ClientConfig{
charmrepo.NewCharmStoreParams{
- HTTPClient: apiContext.HTTPClient(),
- VisitWebPage: visitWebPage,
+ BakeryClient: apiContext.BakeryClient,
},
})
client.Closer = apiContext
return client, nil
}
-
-///////////////////
-// For the most part, apiContext is copied directly from cmd/envcmd/base.go.
-
-// newAPIContext returns a new api context, which should be closed
-// when done with.
-func newAPIContext(visitWebPage func(*url.URL) error) (*apiContext, error) {
- jar, err := cookiejar.New(&cookiejar.Options{
- Filename: cookieFile(),
- })
- if err != nil {
- return nil, errors.Trace(err)
- }
- client := httpbakery.NewClient()
- client.Jar = jar
- client.VisitWebPage = visitWebPage
-
- return &apiContext{
- jar: jar,
- client: client,
- }, nil
-}
-
-// apiContext is a convenience type that can be embedded wherever
-// we need an API connection.
-// It also stores a bakery bakery client allowing the API
-// to be used using macaroons to authenticate. It stores
-// obtained macaroons and discharges in a cookie jar file.
-type apiContext struct {
- jar *cookiejar.Jar
- client *httpbakery.Client
-}
-
-// Close saves the embedded cookie jar.
-func (c *apiContext) Close() error {
- if err := c.jar.Save(); err != nil {
- return errors.Annotatef(err, "cannot save cookie jar")
- }
- return nil
-}
-
-// HTTPClient returns an http.Client that contains the loaded
-// persistent cookie jar.
-func (ctx *apiContext) HTTPClient() *http.Client {
- return ctx.client.Client
-}
-
-// cookieFile returns the path to the cookie used to store authorization
-// macaroons. The returned value can be overridden by setting the
-// JUJU_COOKIEFILE or GO_COOKIEFILE environment variables.
-func cookieFile() string {
- if file := os.Getenv("JUJU_COOKIEFILE"); file != "" {
- return file
- }
- return cookiejar.DefaultCookieFile()
-}
@@ -28,6 +28,7 @@ func NewCommandBase(spec CharmstoreSpec) *CommandBase {
// CommandBase is the type that should be embedded in "juju charm"
// sub-commands.
type CommandBase struct {
+ // TODO(ericsnow) This should be a modelcmd.ModelCommandBase.
cmd.CommandBase
spec CharmstoreSpec
}
@@ -5,16 +5,13 @@ package service
import (
"fmt"
- "net/http"
"regexp"
"strings"
"github.com/juju/cmd"
"github.com/juju/errors"
- "github.com/juju/idmclient/ussologin"
"github.com/juju/romulus/api/budget"
wireformat "github.com/juju/romulus/wireformat/budget"
- "gopkg.in/juju/environschema.v1/form"
"gopkg.in/macaroon-bakery.v1/httpbakery"
"launchpad.net/gnuflag"
@@ -41,7 +38,7 @@ func (a *AllocateBudget) SetFlags(f *gnuflag.FlagSet) {
}
// RunPre is part of the DeployStep interface.
-func (a *AllocateBudget) RunPre(state api.Connection, client *http.Client, ctx *cmd.Context, deployInfo DeploymentInfo) error {
+func (a *AllocateBudget) RunPre(state api.Connection, bakeryClient *httpbakery.Client, ctx *cmd.Context, deployInfo DeploymentInfo) error {
if deployInfo.CharmURL.Schema == "local" {
return nil
}
@@ -64,7 +61,7 @@ func (a *AllocateBudget) RunPre(state api.Connection, client *http.Client, ctx *
return errors.Trace(err)
}
a.Budget, a.Limit = allocBudget, allocLimit
- a.APIClient, err = getApiClient(ctx, client)
+ a.APIClient, err = getApiClient(bakeryClient)
if err != nil {
return errors.Annotate(err, "could not create API client")
}
@@ -81,13 +78,13 @@ func (a *AllocateBudget) RunPre(state api.Connection, client *http.Client, ctx *
return nil
}
-func (a *AllocateBudget) RunPost(_ api.Connection, client *http.Client, ctx *cmd.Context, deployInfo DeploymentInfo, prevErr error) error {
+func (a *AllocateBudget) RunPost(_ api.Connection, bclient *httpbakery.Client, ctx *cmd.Context, deployInfo DeploymentInfo, prevErr error) error {
if prevErr == nil || !a.allocated {
return nil
}
var err error
if a.APIClient == nil {
- a.APIClient, err = getApiClient(ctx, client)
+ a.APIClient, err = getApiClient(bclient)
if err != nil {
return errors.Trace(err)
}
@@ -112,14 +109,8 @@ var getApiClient = getApiClientImpl
var tokenStore = jujuclient.NewTokenStore
-func getApiClientImpl(ctx *cmd.Context, client *http.Client) (apiClient, error) {
- filler := &form.IOFiller{
- In: ctx.Stdin,
- Out: ctx.Stderr,
- }
- bakeryClient := &httpbakery.Client{Client: client, VisitWebPage: ussologin.VisitWebPage(filler, client, tokenStore())}
- c := budget.NewClient(bakeryClient)
- return c, nil
+func getApiClientImpl(bclient *httpbakery.Client) (apiClient, error) {
+ return budget.NewClient(bclient), nil
}
type apiClient interface {
@@ -3,8 +3,6 @@
package service
import (
- "net/http"
-
"github.com/juju/cmd"
"github.com/juju/errors"
"github.com/juju/romulus/wireformat/budget"
@@ -33,12 +31,12 @@ func (s *allocationSuite) SetUpTest(c *gc.C) {
s.stub = &testing.Stub{}
s.apiClient = &mockBudgetAPIClient{Stub: s.stub}
s.allocate = &AllocateBudget{AllocationSpec: "personal:100"}
- s.PatchValue(&getApiClient, func(*cmd.Context, *http.Client) (apiClient, error) { return s.apiClient, nil })
+ s.PatchValue(&getApiClient, func(*httpbakery.Client) (apiClient, error) { return s.apiClient, nil })
s.ctx = coretesting.Context(c)
}
func (s *allocationSuite) TestMeteredCharm(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("cs:quantal/metered-1"),
ServiceName: "service name",
@@ -57,7 +55,7 @@ func (s *allocationSuite) TestMeteredCharm(c *gc.C) {
}
func (s *allocationSuite) TestLocalCharm(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("local:quantal/metered-1"),
ServiceName: "service name",
@@ -72,7 +70,7 @@ func (s *allocationSuite) TestLocalCharm(c *gc.C) {
}
func (s *allocationSuite) TestMeteredCharmInvalidAllocation(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("cs:quantal/metered-1"),
ServiceName: "service name",
@@ -91,7 +89,7 @@ func (s *allocationSuite) TestMeteredCharmInvalidAllocation(c *gc.C) {
}
func (s *allocationSuite) TestMeteredCharmServiceUnavail(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("cs:quantal/metered-1"),
ServiceName: "service name",
@@ -109,7 +107,7 @@ func (s *allocationSuite) TestMeteredCharmServiceUnavail(c *gc.C) {
}
func (s *allocationSuite) TestMeteredCharmRemoveAllocation(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("cs:quantal/metered-1"),
ServiceName: "service name",
@@ -129,7 +127,7 @@ func (s *allocationSuite) TestMeteredCharmRemoveAllocation(c *gc.C) {
}
func (s *allocationSuite) TestUnmeteredCharm(c *gc.C) {
- client := httpbakery.NewClient().Client
+ client := httpbakery.NewClient()
d := DeploymentInfo{
CharmURL: charm.MustParseURL("cs:quantal/unmetered-1"),
ServiceName: "service name",
@@ -814,25 +814,29 @@ func (s *deployRepoCharmStoreSuite) TestDeployBundleServiceUpgradeFailure(c *gc.
`)
c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:trusty/incompatible-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
- // Try upgrading to a different user.
- testcharms.UploadCharm(c, s.client, "~who/trusty/wordpress-42", "wordpress")
+ // Try upgrading to a different series.
+ // Note that this test comes before the next one because
+ // otherwise we can't resolve the charm URL because the charm's
+ // "base entity" is not marked as promulgated so the query by
+ // promulgated will find it.
+ testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress")
_, err = s.DeployBundleYAML(c, `
services:
wordpress:
- charm: cs:~who/trusty/wordpress-42
+ charm: vivid/wordpress
num_units: 1
`)
- c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:~who/trusty/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
+ c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:vivid/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
- // Try upgrading to a different series.
- testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress")
+ // Try upgrading to a different user.
+ testcharms.UploadCharm(c, s.client, "~who/trusty/wordpress-42", "wordpress")
_, err = s.DeployBundleYAML(c, `
services:
wordpress:
- charm: vivid/wordpress
+ charm: cs:~who/trusty/wordpress-42
num_units: 1
`)
- c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:vivid/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
+ c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:~who/trusty/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
}
func (s *deployRepoCharmStoreSuite) TestDeployBundleMultipleRelations(c *gc.C) {
Oops, something went wrong.