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

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/charmstore/client.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
        "io"
 
8
        "io/ioutil"
8
9
 
 
10
        "github.com/juju/errors"
 
11
        "github.com/juju/loggo"
9
12
        "gopkg.in/juju/charm.v6-unstable"
10
13
        charmresource "gopkg.in/juju/charm.v6-unstable/resource"
 
14
        "gopkg.in/juju/charmrepo.v2-unstable"
 
15
        "gopkg.in/juju/charmrepo.v2-unstable/csclient"
11
16
)
12
17
 
13
 
// Client exposes the functionality of the charm store, as provided
 
18
var logger = loggo.GetLogger("juju.charmstore")
 
19
 
 
20
// TODO(ericsnow) Build around charmrepo.CharmStore instead of csclient.Client.
 
21
 
 
22
// BaseClient exposes the functionality of the charm store, as provided
14
23
// by github.com/juju/charmrepo/csclient.Client.
15
24
//
16
25
// Note that the following csclient.Client methods are used as well,
19
28
//  - UploadCharm(id *charm.URL, ch charm.Charm) (*charm.URL, error)
20
29
//  - UploadCharmWithRevision(id *charm.URL, ch charm.Charm, promulgatedRevision int) error
21
30
//  - UploadBundleWithRevision()
22
 
type Client interface {
23
 
        // TODO(ericsnow) Replace use of Get with use of more specific API methods?
 
31
type BaseClient interface {
 
32
        ResourcesClient
 
33
 
 
34
        // TODO(ericsnow) This should really be returning a type from
 
35
        // charmrepo/csclient/params, but we don't have one yet.
 
36
 
 
37
        // LatestRevisions returns the latest revision for each of the
 
38
        // identified charms. The revisions in the provided URLs are ignored.
 
39
        LatestRevisions([]*charm.URL) ([]charmrepo.CharmRevision, error)
 
40
 
 
41
        // TODO(ericsnow) Replace use of Get with use of more specific API
 
42
        // methods? We only use Get() for authorization on the Juju client
 
43
        // side.
24
44
 
25
45
        // Get makes a GET request to the given path in the charm store. The
26
46
        // path must have a leading slash, but must not include the host
28
48
        // given result value, which should be a pointer to the expected
29
49
        // data, but may be nil if no result is desired.
30
50
        Get(path string, result interface{}) error
 
51
}
31
52
 
32
 
        // TODO(ericsnow) Just embed resource/charmstore.BaseClient?
 
53
// ResourcesClient exposes the charm store client functionality for
 
54
// dealing with resources.
 
55
type ResourcesClient interface {
 
56
        // TODO(ericsnow) Just embed resource/charmstore.BaseClient (or vice-versa)?
33
57
 
34
58
        // ListResources composes, for each of the identified charms, the
35
59
        // list of details for each of the charm's resources. Those details
41
65
        // is streamed from the charm store. The charm's revision, if any,
42
66
        // is ignored. If the identified resource is not in the charm store
43
67
        // then errors.NotFound is returned.
44
 
        GetResource(cURL *charm.URL, resourceName string, revision int) (io.ReadCloser, error)
 
68
        GetResource(cURL *charm.URL, resourceName string, revision int) (charmresource.Resource, io.ReadCloser, error)
 
69
}
 
70
 
 
71
type baseClient struct {
 
72
        *csclient.Client
 
73
 
 
74
        asRepo func() *charmrepo.CharmStore
 
75
}
 
76
 
 
77
// TODO(ericsnow) Remove the fake methods once the charm store adds support.
 
78
 
 
79
// ListResources implements ResourcesClient.ListResources as a noop.
 
80
func (baseClient) ListResources(charmURLs []*charm.URL) ([][]charmresource.Resource, error) {
 
81
        res := make([][]charmresource.Resource, len(charmURLs))
 
82
        return res, nil
 
83
}
 
84
 
 
85
// GetResource implements ResourcesClient.GetResource as a noop.
 
86
func (baseClient) GetResource(cURL *charm.URL, resourceName string, revision int) (charmresource.Resource, io.ReadCloser, error) {
 
87
        return charmresource.Resource{}, nil, errors.NotFoundf("resource %q", resourceName)
 
88
}
 
89
 
 
90
// TODO(ericsnow) We must make the Juju metadata available here since
 
91
// we must use charmrepo.NewCharmStore(), which doesn't give us an
 
92
// alternative.
 
93
 
 
94
func newBaseClient(raw *csclient.Client, config ClientConfig, meta JujuMetadata) *baseClient {
 
95
        base := &baseClient{
 
96
                Client: raw,
 
97
        }
 
98
        base.asRepo = func() *charmrepo.CharmStore {
 
99
                // TODO(ericsnow) Use charmrepo.NewCharmStoreFromClient(), when available?
 
100
                repo := charmrepo.NewCharmStore(config.NewCharmStoreParams)
 
101
                return repo.WithJujuAttrs(meta.asAttrs())
 
102
        }
 
103
        return base
 
104
}
 
105
 
 
106
// LatestRevisions implements BaseClient.
 
107
func (base baseClient) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision, error) {
 
108
        // TODO(ericsnow) Fix this:
 
109
        // We must use charmrepo.CharmStore since csclient.Client does not
 
110
        // have the "Latest" method.
 
111
        repo := base.asRepo()
 
112
        return repo.Latest(cURLs...)
 
113
}
 
114
 
 
115
// ClientConfig holds the configuration of a charm store client.
 
116
type ClientConfig struct {
 
117
        charmrepo.NewCharmStoreParams
 
118
}
 
119
 
 
120
func (config ClientConfig) newCSClient() *csclient.Client {
 
121
        return csclient.New(csclient.Params{
 
122
                URL:          config.URL,
 
123
                HTTPClient:   config.HTTPClient,
 
124
                VisitWebPage: config.VisitWebPage,
 
125
        })
 
126
}
 
127
 
 
128
func (config ClientConfig) newCSRepo() *charmrepo.CharmStore {
 
129
        return charmrepo.NewCharmStore(config.NewCharmStoreParams)
 
130
}
 
131
 
 
132
// TODO(ericsnow) Factor out a metadataClient type that embeds "client",
 
133
// and move the "meta" field there?
 
134
 
 
135
// Client adapts csclient.Client to the needs of Juju.
 
136
type Client struct {
 
137
        BaseClient
 
138
        io.Closer
 
139
 
 
140
        newCopy func() *Client
 
141
        meta    JujuMetadata
 
142
}
 
143
 
 
144
// NewClient returns a Juju charm store client for the given client
 
145
// config.
 
146
func NewClient(config ClientConfig) *Client {
 
147
        base := config.newCSClient()
 
148
        closer := ioutil.NopCloser(nil)
 
149
        var meta JujuMetadata
 
150
        return newClient(base, config, meta, closer)
 
151
}
 
152
 
 
153
func newClient(base *csclient.Client, config ClientConfig, meta JujuMetadata, closer io.Closer) *Client {
 
154
        c := &Client{
 
155
                BaseClient: newBaseClient(base, config, meta),
 
156
                Closer:     closer,
 
157
                meta:       meta,
 
158
        }
 
159
        c.newCopy = func() *Client {
 
160
                newBase := *base // a copy
 
161
                copied := newClient(&newBase, config, c.meta, closer)
 
162
                return copied
 
163
        }
 
164
        return c
 
165
}
 
166
 
 
167
// NewDefaultClient returns a Juju charm store client using a default
 
168
// client config.
 
169
func NewDefaultClient() *Client {
 
170
        return NewClient(ClientConfig{})
 
171
}
 
172
 
 
173
// WithMetadata returns a copy of the the client that will use the
 
174
// provided metadata during client requests.
 
175
func (c Client) WithMetadata(meta JujuMetadata) (*Client, error) {
 
176
        newClient := c.newCopy()
 
177
        newClient.meta = meta
 
178
        // Note that we don't call meta.setOnClient() at this point.
 
179
        // That is because not all charm store requests should include
 
180
        // the metadata. The following do so:
 
181
        //  - LatestRevisions()
 
182
        //
 
183
        // If that changed then we would call meta.setOnClient() here.
 
184
        // TODO(ericsnow) Use the metadata for *all* requests?
 
185
        return newClient, nil
 
186
}
 
187
 
 
188
// Metadata returns a copy of the Juju metadata set on the client.
 
189
func (c Client) Metadata() JujuMetadata {
 
190
        // Note the value receiver, meaning that the returned metadata
 
191
        // is a copy.
 
192
        return c.meta
 
193
}
 
194
 
 
195
// LatestRevisions returns the latest revision for each of the
 
196
// identified charms. The revisions in the provided URLs are ignored.
 
197
// Note that this differs from BaseClient.LatestRevisions() exclusively
 
198
// due to taking into account Juju metadata (if any).
 
199
func (c *Client) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision, error) {
 
200
        if !c.meta.IsZero() {
 
201
                c = c.newCopy()
 
202
                if err := c.meta.setOnClient(c.BaseClient); err != nil {
 
203
                        return nil, errors.Trace(err)
 
204
                }
 
205
        }
 
206
        return c.BaseClient.LatestRevisions(cURLs)
45
207
}