11
8
"github.com/juju/errors"
12
"github.com/juju/persistent-cookiejar"
13
9
"gopkg.in/juju/charmrepo.v2-unstable"
14
"gopkg.in/juju/charmrepo.v2-unstable/csclient"
15
"gopkg.in/macaroon-bakery.v1/httpbakery"
11
"github.com/juju/juju/charmstore"
12
"github.com/juju/juju/cmd/modelcmd"
18
// TODO(ericsnow) Factor out code from cmd/juju/commands/common.go and
19
// cmd/envcmd/base.go into cmd/charmstore.go and cmd/apicontext.go. Then
20
// use those here instead of copy-and-pasting here.
22
// CharmstoreClient exposes the functionality of the charm store client.
23
type CharmstoreClient interface {
24
// TODO(ericsnow) Embed github.com/juju/juju/charmstore.Client.
28
15
///////////////////
29
16
// The charmstoreSpec code is based loosely on code in cmd/juju/commands/deploy.go.
33
20
type CharmstoreSpec interface {
34
21
// Connect connects to the specified charm store.
35
Connect() (CharmstoreClient, error)
22
Connect(ctx *cmd.Context) (*charmstore.Client, error)
38
type charmstoreSpec struct {
39
params charmrepo.NewCharmStoreParams
25
type charmstoreSpec struct{}
42
27
// newCharmstoreSpec creates a new charm store spec with default
44
29
func newCharmstoreSpec() CharmstoreSpec {
45
return &charmstoreSpec{
46
params: charmrepo.NewCharmStoreParams{
47
//URL: We use the default.
48
//HTTPClient: We set it later.
49
VisitWebPage: httpbakery.OpenWebBrowser,
30
return &charmstoreSpec{}
33
// Connect implements CharmstoreSpec.
34
func (cs charmstoreSpec) Connect(ctx *cmd.Context) (*charmstore.Client, error) {
35
// Note that creating the API context in Connect is technically
36
// wrong, as it means we'll be creating the bakery context
37
// (and reading/writing the cookies) each time it's called.
38
// TODO(ericsnow) Use modelcmd.ModelCommandBase instead.
39
apiContext, err := modelcmd.NewAPIContext(ctx)
41
return nil, errors.Trace(err)
43
client := charmstore.NewClient(charmstore.ClientConfig{
44
charmrepo.NewCharmStoreParams{
45
BakeryClient: apiContext.BakeryClient,
54
// Connect implements CharmstoreSpec.
55
func (cs charmstoreSpec) Connect() (CharmstoreClient, error) {
56
params, apiContext, err := cs.connect()
58
return nil, errors.Trace(err)
61
baseClient := csclient.New(csclient.Params{
63
HTTPClient: params.HTTPClient,
64
VisitWebPage: params.VisitWebPage,
67
csClient := &charmstoreClient{
69
apiContext: apiContext,
74
// TODO(ericsnow) Also add charmstoreSpec.Repo() -> charmrepo.Interface?
76
func (cs charmstoreSpec) connect() (charmrepo.NewCharmStoreParams, *apiContext, error) {
77
apiContext, err := newAPIContext()
79
return charmrepo.NewCharmStoreParams{}, nil, errors.Trace(err)
82
params := cs.params // a copy
83
params.HTTPClient = apiContext.HTTPClient()
84
return params, apiContext, nil
88
// charmstoreClient is based loosely on cmd/juju/commands/common.go.
90
type charmstoreClient struct {
95
// Close implements io.Closer.
96
func (cs *charmstoreClient) Close() error {
97
return cs.apiContext.Close()
101
// For the most part, apiContext is copied directly from cmd/envcmd/base.go.
103
// newAPIContext returns a new api context, which should be closed
105
func newAPIContext() (*apiContext, error) {
106
jar, err := cookiejar.New(&cookiejar.Options{
107
Filename: cookieFile(),
110
return nil, errors.Trace(err)
112
client := httpbakery.NewClient()
114
client.VisitWebPage = httpbakery.OpenWebBrowser
122
// apiContext is a convenience type that can be embedded wherever
123
// we need an API connection.
124
// It also stores a bakery bakery client allowing the API
125
// to be used using macaroons to authenticate. It stores
126
// obtained macaroons and discharges in a cookie jar file.
127
type apiContext struct {
129
client *httpbakery.Client
132
// Close saves the embedded cookie jar.
133
func (c *apiContext) Close() error {
134
if err := c.jar.Save(); err != nil {
135
return errors.Annotatef(err, "cannot save cookie jar")
140
// HTTPClient returns an http.Client that contains the loaded
141
// persistent cookie jar.
142
func (ctx *apiContext) HTTPClient() *http.Client {
143
return ctx.client.Client
146
// cookieFile returns the path to the cookie used to store authorization
147
// macaroons. The returned value can be overridden by setting the
148
// JUJU_COOKIEFILE or GO_COOKIEFILE environment variables.
149
func cookieFile() string {
150
if file := os.Getenv("JUJU_COOKIEFILE"); file != "" {
153
return cookiejar.DefaultCookieFile()
48
client.Closer = apiContext