New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
create resource funcs #65
Changes from all commits
8fdf2ff
68ecf4a
1afe0e6
ceddbd7
4851b9d
1acd190
4dce499
b211153
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import ( | |
"github.com/juju/utils" | ||
"gopkg.in/errgo.v1" | ||
"gopkg.in/juju/charm.v6-unstable" | ||
"gopkg.in/juju/charm.v6-unstable/resource" | ||
|
||
"gopkg.in/juju/charmrepo.v2-unstable/csclient" | ||
"gopkg.in/juju/charmrepo.v2-unstable/csclient/params" | ||
|
@@ -27,7 +28,7 @@ var CacheDir string | |
// CharmStore is a repository Interface that provides access to the public Juju | ||
// charm store. | ||
type CharmStore struct { | ||
client *csclient.Client | ||
client apiClient | ||
} | ||
|
||
var _ Interface = (*CharmStore)(nil) | ||
|
@@ -49,6 +50,14 @@ type NewCharmStoreParams struct { | |
// the user visits a web page to authenticate themselves. | ||
// If nil, a default function that returns an error will be used. | ||
VisitWebPage func(url *url.URL) error | ||
|
||
// User holds the name to authenticate as for the client. If User is empty, | ||
// no credentials will be sent. | ||
User string | ||
|
||
// Password holds the password for the given user, for authenticating the | ||
// client. | ||
Password string | ||
} | ||
|
||
// NewCharmStore creates and returns a charm store repository. | ||
|
@@ -62,6 +71,8 @@ func NewCharmStore(p NewCharmStoreParams) *CharmStore { | |
URL: p.URL, | ||
HTTPClient: p.HTTPClient, | ||
VisitWebPage: p.VisitWebPage, | ||
User: p.User, | ||
Password: p.Password, | ||
}) | ||
return NewCharmStoreFromClient(client) | ||
} | ||
|
@@ -239,6 +250,148 @@ func (s *CharmStore) Latest(curls ...*charm.URL) ([]CharmRevision, error) { | |
return responses, nil | ||
} | ||
|
||
// ListResources returns metadata for all the resources defined on the given charms. | ||
func (s *CharmStore) ListResources(ids []*charm.URL) ([]ResourceResult, error) { | ||
if len(ids) == 0 { | ||
return nil, nil | ||
} | ||
|
||
results, err := s.client.ListResources(ids) | ||
if err != nil { | ||
return nil, errgo.NoteMask(err, "cannot get resource metadata from the charm store", errgo.Any) | ||
} | ||
|
||
result := make([]ResourceResult, len(ids)) | ||
for i, id := range ids { | ||
resources, ok := results[id.String()] | ||
if !ok { | ||
result[i].Err = CharmNotFound(id.String()) | ||
continue | ||
} | ||
list := make([]resource.Resource, len(resources)) | ||
for j, res := range resources { | ||
resource, err := apiResource2Resource(res) | ||
if err != nil { | ||
return nil, errgo.Notef(err, "got bad data from server for resource %q", res.Name) | ||
} | ||
list[j] = resource | ||
} | ||
result[i].Resources = list | ||
} | ||
return result, nil | ||
} | ||
|
||
func apiResource2Resource(res params.Resource) (resource.Resource, error) { | ||
var result resource.Resource | ||
resType, err := apiResourceType2ResourceType(res.Type) | ||
if err != nil { | ||
return result, errgo.Mask(err, errgo.Any) | ||
} | ||
origin, err := apiOrigin2Origin(res.Origin) | ||
if err != nil { | ||
return result, errgo.Mask(err, errgo.Any) | ||
} | ||
fp, err := resource.NewFingerprint(res.Fingerprint) | ||
if err != nil { | ||
return result, errgo.Mask(err, errgo.Any) | ||
} | ||
return resource.Resource{ | ||
Meta: resource.Meta{ | ||
Name: res.Name, | ||
Type: resType, | ||
Path: res.Path, | ||
Description: res.Description, | ||
}, | ||
Origin: origin, | ||
Revision: res.Revision, | ||
Fingerprint: fp, | ||
Size: res.Size, | ||
}, nil | ||
} | ||
|
||
// UploadResource uploads the bytes from the given file as a resource with the given name for the charm. | ||
func (s *CharmStore) UploadResource(id *charm.URL, name, filename string) (revision int, err error) { | ||
f, err := os.Open(filename) | ||
if err != nil { | ||
return -1, errgo.Mask(err, errgo.Any) | ||
} | ||
defer f.Close() | ||
rev, err := s.client.UploadResource(id, name, filename, f) | ||
if err != nil { | ||
return rev, errgo.Mask(err, errgo.Any) | ||
} | ||
return rev, nil | ||
} | ||
|
||
// ResourceData holds the information about the bytes of a resource. | ||
type ResourceData struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be reusing the existing type defined in juju/charm? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The one we have defined is in juju/juju ... so I don't think we can/should reference that from here. If it were in juju/charm, then maybe, though we need to return the revision, which is not included in that other type. Really, it's just a dumb data bag to avoid returning 4 values, and it has no functionality, so I don't think we need to worry about duplicating code here. |
||
io.ReadCloser | ||
Size int64 | ||
Revision int | ||
Fingerprint resource.Fingerprint | ||
} | ||
|
||
// GetLatestResource returns the bytes for the latest revision of the given resource. | ||
func (s *CharmStore) GetLatestResource(id *charm.URL, name string) (result ResourceData, err error) { | ||
return s.GetResource(id, -1, name) | ||
} | ||
|
||
// GetResource returns the bytes for the specified revision of the given resource. | ||
func (s *CharmStore) GetResource(id *charm.URL, revision int, name string) (result ResourceData, err error) { | ||
data, err := s.client.GetResource(id, revision, name) | ||
if err != nil { | ||
return result, errgo.Mask(err, errgo.Any) | ||
} | ||
defer func() { | ||
if err != nil { | ||
data.Close() | ||
} | ||
}() | ||
fp, err := resource.ParseFingerprint(data.Hash) | ||
if err != nil { | ||
return result, errgo.NoteMask(err, "invalid fingerprint returned from server", errgo.Any) | ||
} | ||
return ResourceData{ | ||
ReadCloser: data.ReadCloser, | ||
Size: data.Size, | ||
Revision: data.Revision, | ||
Fingerprint: fp, | ||
}, nil | ||
} | ||
|
||
// Publish tells the charmstore to mark the given charm as published with the given resource revisions. | ||
func (s *CharmStore) Publish(id *charm.URL, resources map[string]int) (*charm.URL, error) { | ||
val := ¶ms.PublishRequest{ | ||
Published: true, | ||
Resources: resources, | ||
} | ||
var result params.PublishResponse | ||
if err := s.client.PutWithResponse("/"+id.Path()+"/publish", val, &result); err != nil { | ||
return nil, errgo.Mask(err) | ||
} | ||
return result.Id, nil | ||
} | ||
|
||
func apiResourceType2ResourceType(t string) (resource.Type, error) { | ||
switch t { | ||
case "file": | ||
return resource.TypeFile, nil | ||
default: | ||
return 0, errgo.Newf("unknown resource type: %v", t) | ||
} | ||
} | ||
|
||
func apiOrigin2Origin(origin string) (resource.Origin, error) { | ||
switch origin { | ||
case "store": | ||
return resource.OriginStore, nil | ||
case "ulpoad": | ||
return resource.OriginUpload, nil | ||
default: | ||
return 0, errgo.Newf("unknown origin: %v", origin) | ||
} | ||
} | ||
|
||
// Resolve implements Interface.Resolve. | ||
func (s *CharmStore) Resolve(ref *charm.URL) (*charm.URL, []string, error) { | ||
var result struct { | ||
|
@@ -291,3 +444,29 @@ func (s *CharmStore) WithJujuAttrs(attrs map[string]string) *CharmStore { | |
newRepo.client.SetHTTPHeader(header) | ||
return &newRepo | ||
} | ||
|
||
type apiClient interface { | ||
DisableStats() | ||
Do(req *http.Request, path string) (*http.Response, error) | ||
DoWithBody(req *http.Request, path string, body io.ReadSeeker) (*http.Response, error) | ||
Get(path string, result interface{}) error | ||
GetArchive(id *charm.URL) (r io.ReadCloser, eid *charm.URL, hash string, size int64, err error) | ||
GetResource(id *charm.URL, revision int, name string) (csclient.ResourceData, error) | ||
ListResources(ids []*charm.URL) (map[string][]params.Resource, error) | ||
Log(typ params.LogType, level params.LogLevel, message string, urls ...*charm.URL) error | ||
Login() error | ||
Meta(id *charm.URL, result interface{}) (*charm.URL, error) | ||
Put(path string, val interface{}) error | ||
PutCommonInfo(id *charm.URL, info map[string]interface{}) error | ||
PutExtraInfo(id *charm.URL, info map[string]interface{}) error | ||
PutWithResponse(path string, val, result interface{}) error | ||
ServerURL() string | ||
SetHTTPHeader(header http.Header) | ||
StatsUpdate(req params.StatsUpdateRequest) error | ||
UploadBundle(id *charm.URL, b charm.Bundle) (*charm.URL, error) | ||
UploadBundleWithRevision(id *charm.URL, b charm.Bundle, promulgatedRevision int) error | ||
UploadCharm(id *charm.URL, ch charm.Charm) (*charm.URL, error) | ||
UploadCharmWithRevision(id *charm.URL, ch charm.Charm, promulgatedRevision int) error | ||
UploadResource(id *charm.URL, name, path string, file io.ReadSeeker) (revision int, err error) | ||
WhoAmI() (*params.WhoAmIResponse, error) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we already have this function defined in core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Different API... that's the controller API. In theory, we could make them look mostly the same, but I think there's always going to be differences, so I'm not sure it's worth trying to shoehorn them into the same mold.