~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/service/store.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 
6
6
import (
7
7
        "fmt"
8
 
        "net/http"
9
8
        "net/url"
10
9
        "strings"
11
10
 
55
54
        return false
56
55
}
57
56
 
58
 
type resolveCharmStoreEntityParams struct {
59
 
        urlStr          string
60
 
        requestedSeries string
61
 
        forceSeries     bool
62
 
        csParams        charmrepo.NewCharmStoreParams
63
 
        repoPath        string
64
 
        conf            *config.Config
65
 
}
66
 
 
67
 
// resolveCharmStoreEntityURL resolves the given charm or bundle URL string
68
 
// by looking it up in the appropriate charm repository.
69
 
// If it is a charm store URL, the given csParams will
70
 
// be used to access the charm store repository.
71
 
// If it is a local charm or bundle URL, the local charm repository at
72
 
// the given repoPath will be used. The given configuration
73
 
// will be used to add any necessary attributes to the repo
74
 
// and to return the charm's supported series if possible.
 
57
// charmURLResolver holds the information necessary to
 
58
// resolve charm and bundle URLs.
 
59
type charmURLResolver struct {
 
60
        // csRepo holds the repository to use for charmstore charms.
 
61
        csRepo charmrepo.Interface
 
62
 
 
63
        // repoPath holds the path to the local charm repository directory.
 
64
        repoPath string
 
65
 
 
66
        // conf holds the current model configuration.
 
67
        conf *config.Config
 
68
}
 
69
 
 
70
func newCharmURLResolver(conf *config.Config, csClient *csclient.Client, repoPath string) *charmURLResolver {
 
71
        r := &charmURLResolver{
 
72
                csRepo:   charmrepo.NewCharmStoreFromClient(csClient),
 
73
                repoPath: repoPath,
 
74
                conf:     conf,
 
75
        }
 
76
        return r
 
77
}
 
78
 
 
79
// resolve resolves the given given charm or bundle URL
 
80
// string by looking it up in the appropriate charm repository. If it is
 
81
// a charm store URL, the given csParams will be used to access the
 
82
// charm store repository. If it is a local charm or bundle URL, the
 
83
// local charm repository at the given repoPath will be used. The given
 
84
// configuration will be used to add any necessary attributes to the
 
85
// repo and to return the charm's supported series if possible.
75
86
//
76
 
// resolveCharmStoreEntityURL also returns the charm repository holding
77
 
// the charm or bundle.
78
 
func resolveCharmStoreEntityURL(args resolveCharmStoreEntityParams) (*charm.URL, []string, charmrepo.Interface, error) {
79
 
        url, err := charm.ParseURL(args.urlStr)
80
 
        if err != nil {
81
 
                return nil, nil, nil, errors.Trace(err)
82
 
        }
83
 
        repo, err := charmrepo.InferRepository(url, args.csParams, args.repoPath)
84
 
        if err != nil {
85
 
                return nil, nil, nil, errors.Trace(err)
86
 
        }
87
 
        repo = config.SpecializeCharmRepo(repo, args.conf)
88
 
 
89
 
        if url.Schema == "local" && url.Series == "" {
90
 
                if defaultSeries, ok := args.conf.DefaultSeries(); ok {
91
 
                        url.Series = defaultSeries
 
87
// It returns the fully resolved URL, any series supported by the entity,
 
88
// and the repository that holds it.
 
89
func (r *charmURLResolver) resolve(urlStr string) (*charm.URL, []string, charmrepo.Interface, error) {
 
90
        url, err := charm.ParseURL(urlStr)
 
91
        if err != nil {
 
92
                return nil, nil, nil, errors.Trace(err)
 
93
        }
 
94
        var repo charmrepo.Interface
 
95
        switch url.Schema {
 
96
        case "cs":
 
97
                repo = r.csRepo
 
98
        case "local":
 
99
                if url.Series == "" {
 
100
                        if defaultSeries, ok := r.conf.DefaultSeries(); ok {
 
101
                                url.Series = defaultSeries
 
102
                        }
92
103
                }
93
104
                if url.Series == "" {
94
105
                        possibleURL := *url
96
107
                        logger.Errorf("The series is not specified in the model (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
97
108
                        return nil, nil, nil, errors.Errorf("cannot resolve series for charm: %q", url)
98
109
                }
 
110
                repo, err = charmrepo.NewLocalRepository(r.repoPath)
 
111
                if err != nil {
 
112
                        return nil, nil, nil, errors.Mask(err)
 
113
                }
 
114
        default:
 
115
                return nil, nil, nil, errors.Errorf("unknown schema for charm reference %q", urlStr)
99
116
        }
 
117
        repo = config.SpecializeCharmRepo(repo, r.conf)
100
118
        resultUrl, supportedSeries, err := repo.Resolve(url)
101
119
        if err != nil {
102
120
                return nil, nil, nil, errors.Trace(err)
108
126
// given charm URL to state. For non-public charm URLs, this function also
109
127
// handles the macaroon authorization process using the given csClient.
110
128
// The resulting charm URL of the added charm is displayed on stdout.
111
 
func addCharmFromURL(client *api.Client, curl *charm.URL, repo charmrepo.Interface, csclient *csClient) (*charm.URL, error) {
 
129
//
 
130
// The repo holds the charm repository associated with with the URL
 
131
// by resolveCharmStoreEntityURL.
 
132
func addCharmFromURL(client *api.Client, curl *charm.URL, repo charmrepo.Interface) (*charm.URL, *macaroon.Macaroon, error) {
 
133
        var csMac *macaroon.Macaroon
112
134
        switch curl.Schema {
113
135
        case "local":
114
136
                ch, err := repo.Get(curl)
115
137
                if err != nil {
116
 
                        return nil, err
 
138
                        return nil, nil, err
117
139
                }
118
140
                stateCurl, err := client.AddLocalCharm(curl, ch)
119
141
                if err != nil {
120
 
                        return nil, err
 
142
                        return nil, nil, err
121
143
                }
122
144
                curl = stateCurl
123
145
        case "cs":
 
146
                repo, ok := repo.(*charmrepo.CharmStore)
 
147
                if !ok {
 
148
                        return nil, nil, errors.Errorf("(cannot happen) cs-schema URL with unexpected repo type %T", repo)
 
149
                }
 
150
                csClient := repo.Client()
124
151
                if err := client.AddCharm(curl); err != nil {
125
152
                        if !params.IsCodeUnauthorized(err) {
126
 
                                return nil, errors.Trace(err)
 
153
                                return nil, nil, errors.Trace(err)
127
154
                        }
128
 
                        m, err := csclient.authorize(curl)
 
155
                        m, err := authorizeCharmStoreEntity(csClient, curl)
129
156
                        if err != nil {
130
 
                                return nil, maybeTermsAgreementError(err)
 
157
                                return nil, nil, maybeTermsAgreementError(err)
131
158
                        }
132
159
                        if err := client.AddCharmWithAuthorization(curl, m); err != nil {
133
 
                                return nil, errors.Trace(err)
 
160
                                return nil, nil, errors.Trace(err)
134
161
                        }
 
162
                        csMac = m
135
163
                }
136
164
        default:
137
 
                return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
138
 
        }
139
 
        return curl, nil
140
 
}
141
 
 
142
 
// csClient gives access to the charm store server and provides parameters
143
 
// for connecting to the charm store.
144
 
type csClient struct {
145
 
        params charmrepo.NewCharmStoreParams
146
 
}
147
 
 
148
 
// newCharmStoreClient is called to obtain a charm store client
149
 
// including the parameters for connecting to the charm store, and
150
 
// helpers to save the local authorization cookies and to authorize
151
 
// non-public charm deployments. It is defined as a variable so it can
152
 
// be changed for testing purposes.
153
 
var newCharmStoreClient = func(client *http.Client) *csClient {
154
 
        return &csClient{
155
 
                params: charmrepo.NewCharmStoreParams{
156
 
                        HTTPClient:   client,
157
 
                        VisitWebPage: httpbakery.OpenWebBrowser,
158
 
                },
159
 
        }
160
 
}
161
 
 
162
 
// authorize acquires and return the charm store delegatable macaroon to be
 
165
                return nil, nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
 
166
        }
 
167
        return curl, csMac, nil
 
168
}
 
169
 
 
170
// newCharmStoreClient is called to obtain a charm store client.
 
171
// It is defined as a variable so it can be changed for testing purposes.
 
172
var newCharmStoreClient = func(client *httpbakery.Client) *csclient.Client {
 
173
        return csclient.New(csclient.Params{
 
174
                BakeryClient: client,
 
175
        })
 
176
}
 
177
 
 
178
// authorizeCharmStoreEntity acquires and return the charm store delegatable macaroon to be
163
179
// used to add the charm corresponding to the given URL.
164
180
// The macaroon is properly attenuated so that it can only be used to deploy
165
181
// the given charm URL.
166
 
func (c *csClient) authorize(curl *charm.URL) (*macaroon.Macaroon, error) {
167
 
        if curl == nil {
168
 
                return nil, errors.New("empty charm url not allowed")
169
 
        }
170
 
 
171
 
        client := csclient.New(csclient.Params{
172
 
                URL:          c.params.URL,
173
 
                HTTPClient:   c.params.HTTPClient,
174
 
                VisitWebPage: c.params.VisitWebPage,
175
 
        })
176
 
        endpoint := "/delegatable-macaroon"
177
 
        endpoint += "?id=" + url.QueryEscape(curl.String())
178
 
 
 
182
func authorizeCharmStoreEntity(csClient *csclient.Client, curl *charm.URL) (*macaroon.Macaroon, error) {
 
183
        endpoint := "/delegatable-macaroon?id=" + url.QueryEscape(curl.String())
179
184
        var m *macaroon.Macaroon
180
 
        if err := client.Get(endpoint, &m); err != nil {
 
185
        if err := csClient.Get(endpoint, &m); err != nil {
181
186
                return nil, errors.Trace(err)
182
187
        }
183
 
 
184
188
        return m, nil
185
189
}