58
type resolveCharmStoreEntityParams struct {
60
requestedSeries string
62
csParams charmrepo.NewCharmStoreParams
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
63
// repoPath holds the path to the local charm repository directory.
66
// conf holds the current model configuration.
70
func newCharmURLResolver(conf *config.Config, csClient *csclient.Client, repoPath string) *charmURLResolver {
71
r := &charmURLResolver{
72
csRepo: charmrepo.NewCharmStoreFromClient(csClient),
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.
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)
81
return nil, nil, nil, errors.Trace(err)
83
repo, err := charmrepo.InferRepository(url, args.csParams, args.repoPath)
85
return nil, nil, nil, errors.Trace(err)
87
repo = config.SpecializeCharmRepo(repo, args.conf)
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)
92
return nil, nil, nil, errors.Trace(err)
94
var repo charmrepo.Interface
100
if defaultSeries, ok := r.conf.DefaultSeries(); ok {
101
url.Series = defaultSeries
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)
110
repo, err = charmrepo.NewLocalRepository(r.repoPath)
112
return nil, nil, nil, errors.Mask(err)
115
return nil, nil, nil, errors.Errorf("unknown schema for charm reference %q", urlStr)
117
repo = config.SpecializeCharmRepo(repo, r.conf)
100
118
resultUrl, supportedSeries, err := repo.Resolve(url)
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) {
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 {
114
136
ch, err := repo.Get(curl)
118
140
stateCurl, err := client.AddLocalCharm(curl, ch)
146
repo, ok := repo.(*charmrepo.CharmStore)
148
return nil, nil, errors.Errorf("(cannot happen) cs-schema URL with unexpected repo type %T", repo)
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)
128
m, err := csclient.authorize(curl)
155
m, err := authorizeCharmStoreEntity(csClient, curl)
130
return nil, maybeTermsAgreementError(err)
157
return nil, nil, maybeTermsAgreementError(err)
132
159
if err := client.AddCharmWithAuthorization(curl, m); err != nil {
133
return nil, errors.Trace(err)
160
return nil, nil, errors.Trace(err)
137
return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
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
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 {
155
params: charmrepo.NewCharmStoreParams{
157
VisitWebPage: httpbakery.OpenWebBrowser,
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)
167
return curl, csMac, nil
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,
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) {
168
return nil, errors.New("empty charm url not allowed")
171
client := csclient.New(csclient.Params{
173
HTTPClient: c.params.HTTPClient,
174
VisitWebPage: c.params.VisitWebPage,
176
endpoint := "/delegatable-macaroon"
177
endpoint += "?id=" + url.QueryEscape(curl.String())
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)