Skip to content
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

Merged
merged 8 commits into from Mar 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
181 changes: 180 additions & 1 deletion charmstore.go
Expand Up @@ -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"
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 := &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 {
Expand Down Expand Up @@ -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)
}