Skip to content

Commit a80b0a4

Browse files
Drop Client.AsRepo() (and add Client.WithMetadata()).
1 parent a48df4a commit a80b0a4

File tree

4 files changed

+160
-93
lines changed

4 files changed

+160
-93
lines changed

apiserver/charmrevisionupdater/updater.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,12 @@ func (api *CharmRevisionUpdaterAPI) updateLatestRevisions() error {
9191

9292
// NewCharmStoreClient instantiates a new charm store repository.
9393
// It is defined at top level for testing purposes.
94-
var NewCharmStoreClient = func() charmstore.Client {
94+
var NewCharmStoreClient = func(modelUUID string) (charmstore.Client, error) {
9595
// TODO(ericsnow) Use the Juju "HTTP context" once we have one.
96-
return charmstore.NewDefaultClient()
96+
client := charmstore.NewDefaultClient()
97+
return client.WithMetadata(charmstore.JujuMetadata{
98+
ModelUUID: modelUUID,
99+
})
97100
}
98101

99102
type latestCharmInfo struct {
@@ -130,9 +133,12 @@ func retrieveLatestCharmInfo(st *state.State) ([]latestCharmInfo, error) {
130133
resultsIndexedServices = append(resultsIndexedServices, service)
131134
}
132135

133-
client := NewCharmStoreClient()
136+
client, err := NewCharmStoreClient(modelUUID)
137+
if err != nil {
138+
return nil, err
139+
}
134140

135-
results, err := charmstore.LatestCharmInfo(client, modelUUID, curls)
141+
results, err := charmstore.LatestCharmInfo(client, curls)
136142
if err != nil {
137143
return nil, err
138144
}

charmstore/client.go

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ var logger = loggo.GetLogger("juju.charmstore")
2222
// of Juju.
2323
type Client interface {
2424
BaseClient
25-
ResourcesClient
2625
io.Closer
2726

28-
// AsRepo returns a charm repo that wraps the client. If the model's
29-
// UUID is provided then it is associated with all of the repo's
30-
// interaction with the charm store.
31-
AsRepo(modelUUID string) Repo
27+
// WithMetadata returns a copy of the the client that will use the
28+
// provided metadata during client requests.
29+
WithMetadata(meta JujuMetadata) (Client, error)
30+
31+
// Metadata returns the Juju metadata set on the client.
32+
Metadata() JujuMetadata
33+
34+
// TODO(ericsnow) Add an "AsRepo() charmrepo.Interface" method.
3235
}
3336

3437
// BaseClient exposes the functionality of the charm store, as provided
@@ -41,8 +44,14 @@ type Client interface {
4144
// - UploadCharmWithRevision(id *charm.URL, ch charm.Charm, promulgatedRevision int) error
4245
// - UploadBundleWithRevision()
4346
type BaseClient interface {
44-
// TODO(ericsnow) Embed ResourcesClient here once the charm store
45-
// supports it.
47+
ResourcesClient
48+
49+
// TODO(ericsnow) This should really be returning a type from
50+
// charmrepo/csclient/params, but we don't have one yet.
51+
52+
// LatestRevisions returns the latest revision for each of the
53+
// identified charms. The revisions in the provided URLs are ignored.
54+
LatestRevisions([]*charm.URL) ([]charmrepo.CharmRevision, error)
4655

4756
// TODO(ericsnow) Replace use of Get with use of more specific API
4857
// methods? We only use Get() for authorization on the Juju client
@@ -74,19 +83,50 @@ type ResourcesClient interface {
7483
GetResource(cURL *charm.URL, resourceName string, revision int) (io.ReadCloser, error)
7584
}
7685

77-
// client adapts csclient.Client to the needs of Juju.
78-
type client struct {
86+
type baseClient struct {
7987
*csclient.Client
8088
fakeCharmStoreClient
89+
90+
asRepo func() *charmrepo.CharmStore
91+
}
92+
93+
func newBaseClient(raw *csclient.Client) *baseClient {
94+
base := &baseClient{
95+
Client: raw,
96+
}
97+
base.asRepo = func() *charmrepo.CharmStore {
98+
return charmrepo.NewCharmStoreFromClient(base.Client)
99+
}
100+
return base
101+
}
102+
103+
// LatestRevisions implements BaseClient.
104+
func (base baseClient) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision, error) {
105+
// TODO(ericsnow) Fix this:
106+
// We must use charmrepo.CharmStore since csclient.Client does not
107+
// have the "Latest" method.
108+
repo := base.asRepo()
109+
return repo.Latest(cURLs...)
110+
}
111+
112+
// TODO(ericsnow) Factor out a metadataClient type that embeds "client",
113+
// and move the "meta" field there?
114+
115+
// client adapts csclient.Client to the needs of Juju.
116+
type client struct {
117+
BaseClient
81118
io.Closer
119+
120+
newCopy func() *client
121+
meta JujuMetadata
82122
}
83123

84124
// NewClient returns a Juju charm store client for the given client
85125
// config.
86126
func NewClient(config csclient.Params) Client {
87127
base := csclient.New(config)
88128
closer := ioutil.NopCloser(nil)
89-
return WrapBaseClient(base, closer)
129+
return newClient(base, closer)
90130
}
91131

92132
// NewDefaultClient returns a Juju charm store client using a default
@@ -100,36 +140,68 @@ func NewDefaultClient() Client {
100140
// related to the client. If no closer is needed then pass in a no-op
101141
// closer (e.g. ioutil.NopCloser).
102142
func WrapBaseClient(base *csclient.Client, closer io.Closer) Client {
103-
return &client{
104-
Client: base,
105-
Closer: closer,
143+
return newClient(base, closer)
144+
}
145+
146+
func newClient(base *csclient.Client, closer io.Closer) *client {
147+
c := &client{
148+
BaseClient: newBaseClient(base),
149+
Closer: closer,
150+
}
151+
c.newCopy = func() *client {
152+
newBase := *base
153+
copied := newClient(&newBase, closer)
154+
copied.meta = c.meta
155+
return copied
106156
}
157+
return c
107158
}
108159

109-
// AsRepo implements Client.
110-
func (client client) AsRepo(envUUID string) Repo {
111-
repo := charmrepo.NewCharmStoreFromClient(client.Client)
112-
if envUUID != "" {
113-
repo = repo.WithJujuAttrs(map[string]string{
114-
"environment_uuid": envUUID,
115-
})
160+
// WithMetadata returns a copy of the the client that will use the
161+
// provided metadata during client requests.
162+
func (c client) WithMetadata(meta JujuMetadata) (Client, error) {
163+
newClient := c.newCopy()
164+
newClient.meta = meta
165+
// Note that we don't call meta.setOnClient() at this point.
166+
// That is because not all charm store requests should include
167+
// the metadata. The following do so:
168+
// - LatestRevisions()
169+
//
170+
// If that changed then we would call meta.setOnClient() here.
171+
// TODO(ericsnow) Use the metadata for *all* requests?
172+
return newClient, nil
173+
}
174+
175+
// Metadata implements Client.
176+
func (c client) Metadata() JujuMetadata {
177+
// Note the value receiver, meaning that the returned metadata
178+
// is a copy.
179+
return c.meta
180+
}
181+
182+
// LatestRevisions returns the latest revision for each of the
183+
// identified charms. The revisions in the provided URLs are ignored.
184+
// Note that this differs from BaseClient.LatestRevisions() exclusively
185+
// due to taking into account Juju metadata (if any).
186+
func (c *client) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision, error) {
187+
if !c.meta.IsZero() {
188+
c = c.newCopy()
189+
if err := c.meta.setOnClient(c); err != nil {
190+
return nil, errors.Trace(err)
191+
}
116192
}
117-
return repo
193+
return c.BaseClient.LatestRevisions(cURLs)
118194
}
119195

120196
// LatestCharmInfo returns the most up-to-date information about each
121197
// of the identified charms at their latest revision. The revisions in
122198
// the provided URLs are ignored.
123-
func LatestCharmInfo(client Client, modelUUID string, cURLs []*charm.URL) ([]CharmInfoResult, error) {
199+
func LatestCharmInfo(client Client, cURLs []*charm.URL) ([]CharmInfoResult, error) {
124200
now := time.Now().UTC()
125201

126-
// We must use charmrepo.CharmStore since csclient.Client does not
127-
// have the "Latest" method.
128-
repo := client.AsRepo(modelUUID)
129-
130202
// Do a bulk call to get the revision info for all charms.
131203
logger.Infof("retrieving revision information for %d charms", len(cURLs))
132-
revResults, err := repo.Latest(cURLs...)
204+
revResults, err := client.LatestRevisions(cURLs)
133205
if err != nil {
134206
err = errors.Annotate(err, "while getting latest charm revision info")
135207
logger.Infof(err.Error())

charmstore/client_test.go

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -48,55 +48,77 @@ func (s *ClientSuite) TestWrapBaseClient(c *gc.C) {
4848
s.stub.CheckCallNames(c, "Close")
4949
}
5050

51-
func (s *ClientSuite) TestAsRepo(c *gc.C) {
51+
func (s *ClientSuite) TestWithMetadata(c *gc.C) {
5252
uuidVal, err := utils.NewUUID()
5353
c.Assert(err, jc.ErrorIsNil)
54-
uuid := uuidVal.String()
54+
meta := charmstore.JujuMetadata{
55+
ModelUUID: uuidVal.String(),
56+
}
5557
base := csclient.New(s.config)
56-
expected := charmrepo.NewCharmStoreFromClient(base)
5758
client := charmstore.WrapBaseClient(base, s.client)
59+
metaBefore := client.Metadata()
5860

59-
repo := client.AsRepo(uuid)
61+
newClient, err := client.WithMetadata(meta)
62+
c.Assert(err, jc.ErrorIsNil)
6063

61-
c.Check(repo.(*charmrepo.CharmStore), jc.DeepEquals, expected)
64+
s.stub.CheckNoCalls(c)
65+
c.Check(newClient, gc.Not(gc.Equals), client)
66+
c.Check(metaBefore.IsZero(), jc.IsTrue)
67+
c.Check(newClient.Metadata(), jc.DeepEquals, meta)
6268
}
6369

70+
//func (s *ClientSuite) TestLatestRevisions(c *gc.C) {
71+
// cURLs := []*charm.URL{
72+
// charm.MustParseURL("cs:quantal/spam-17"),
73+
// charm.MustParseURL("cs:quantal/eggs-2"),
74+
// charm.MustParseURL("cs:quantal/ham-1"),
75+
// }
76+
// expected := []charmrepo.CharmRevision{{
77+
// Revision: 17,
78+
// }, {
79+
// Revision: 3,
80+
// }, {
81+
// Err: errors.New("not found"),
82+
// }}
83+
// s.client.ReturnLatestRevisions = expected
84+
// client := charmstore.Client{BaseClient: s.client}
85+
//
86+
// revisions, err := client.LatestRevisions(cURLs)
87+
// c.Assert(err, jc.ErrorIsNil)
88+
//
89+
// s.stub.CheckCallNames(c, "LatestRevisions")
90+
// s.stub.CheckCall(c, 0, "LatestRevisions", cURLs)
91+
// c.Check(revisions, jc.DeepEquals, expected)
92+
//}
93+
6494
func (s *ClientSuite) TestLatestCharmInfo(c *gc.C) {
65-
uuidVal, err := utils.NewUUID()
66-
c.Assert(err, jc.ErrorIsNil)
67-
uuid := uuidVal.String()
6895
cURLs := []*charm.URL{
6996
charm.MustParseURL("cs:quantal/spam-17"),
7097
charm.MustParseURL("cs:quantal/eggs-2"),
7198
charm.MustParseURL("cs:quantal/ham-1"),
7299
}
73100
notFound := errors.New("not found")
74-
repo := &stubRepo{
75-
Stub: s.stub,
76-
ReturnLatest: []charmrepo.CharmRevision{{
77-
Revision: 17,
78-
}, {
79-
Revision: 3,
80-
}, {
81-
Err: notFound,
82-
}},
83-
}
101+
s.client.ReturnLatestRevisions = []charmrepo.CharmRevision{{
102+
Revision: 17,
103+
}, {
104+
Revision: 3,
105+
}, {
106+
Err: notFound,
107+
}}
84108
s.client.ReturnListResources = [][]charmresource.Resource{
85109
{
86110
resourcetesting.NewCharmResource(c, "spam", "<some data>"),
87111
},
88112
nil,
89113
nil,
90114
}
91-
s.client.ReturnAsRepo = repo
92115

93-
results, err := charmstore.LatestCharmInfo(s.client, uuid, cURLs)
116+
results, err := charmstore.LatestCharmInfo(s.client, cURLs)
94117
c.Assert(err, jc.ErrorIsNil)
95118

96-
s.stub.CheckCallNames(c, "AsRepo", "Latest", "ListResources")
97-
s.stub.CheckCall(c, 0, "AsRepo", uuid)
98-
s.stub.CheckCall(c, 1, "Latest", cURLs)
99-
s.stub.CheckCall(c, 2, "ListResources", cURLs)
119+
s.stub.CheckCallNames(c, "LatestRevisions", "ListResources")
120+
s.stub.CheckCall(c, 0, "LatestRevisions", cURLs)
121+
s.stub.CheckCall(c, 1, "ListResources", cURLs)
100122
timestamp := results[0].Timestamp
101123
results[2].Error = errors.Cause(results[2].Error)
102124
c.Check(results, jc.DeepEquals, []charmstore.CharmInfoResult{{
@@ -153,8 +175,8 @@ type stubClient struct {
153175
charmstore.Client
154176
*testing.Stub
155177

156-
ReturnListResources [][]charmresource.Resource
157-
ReturnAsRepo charmstore.Repo
178+
ReturnListResources [][]charmresource.Resource
179+
ReturnLatestRevisions []charmrepo.CharmRevision
158180
}
159181

160182
func (s *stubClient) ListResources(cURLs []*charm.URL) ([][]charmresource.Resource, error) {
@@ -175,25 +197,11 @@ func (s *stubClient) Close() error {
175197
return nil
176198
}
177199

178-
func (s *stubClient) AsRepo(modelUUID string) charmstore.Repo {
179-
s.AddCall("AsRepo", modelUUID)
180-
s.NextErr() // Pop one off.
181-
182-
return s.ReturnAsRepo
183-
}
184-
185-
type stubRepo struct {
186-
charmstore.Repo
187-
*testing.Stub
188-
189-
ReturnLatest []charmrepo.CharmRevision
190-
}
191-
192-
func (s *stubRepo) Latest(cURLs ...*charm.URL) ([]charmrepo.CharmRevision, error) {
193-
s.AddCall("Latest", cURLs)
200+
func (s *stubClient) LatestRevisions(cURLs []*charm.URL) ([]charmrepo.CharmRevision, error) {
201+
s.AddCall("LatestRevisions", cURLs)
194202
if err := s.NextErr(); err != nil {
195203
return nil, errors.Trace(err)
196204
}
197205

198-
return s.ReturnLatest, nil
206+
return s.ReturnLatestRevisions, nil
199207
}

charmstore/repo.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)