create resource funcs #65

Merged
merged 8 commits into from Mar 11, 2016
View
@@ -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)
@kat-co

kat-co Mar 9, 2016

Contributor

Do we already have this function defined in core?

@natefinch

natefinch Mar 11, 2016

Contributor

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.

+ 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 {
@kat-co

kat-co Mar 9, 2016

Contributor

Should we be reusing the existing type defined in juju/charm?

@natefinch

natefinch Mar 11, 2016

Contributor

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 := &params.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)
+}
Oops, something went wrong.