func (st resourceState) setResource(pendingID, applicationID, userID string, chRes charmresource.Resource, r io.Reader) (resource.Resource, error) { id := newResourceID(applicationID, chRes.Name) res := resource.Resource{ Resource: chRes, ID: id, PendingID: pendingID, ApplicationID: applicationID, } if r != nil { // TODO(ericsnow) Validate the user ID (or use a tag). res.Username = userID res.Timestamp = st.currentTimestamp() } if err := res.Validate(); err != nil { return res, errors.Annotate(err, "bad resource metadata") } if r == nil { if err := st.persist.SetResource(res); err != nil { return res, errors.Trace(err) } } else { if err := st.storeResource(res, r); err != nil { return res, errors.Trace(err) } } return res, nil }
func (p ResourcePersistence) setUnitResource(unitID string, res resource.Resource, progress *int64) error { stored, err := p.getStored(res) if err != nil { return errors.Trace(err) } // TODO(ericsnow) Ensure that stored.Resource matches res? If we do // so then the following line is unnecessary. stored.Resource = res if err := res.Validate(); err != nil { return errors.Annotate(err, "bad resource") } buildTxn := func(attempt int) ([]txn.Op, error) { // This is an "upsert". var ops []txn.Op switch attempt { case 0: ops = newInsertUnitResourceOps(unitID, stored, progress) case 1: ops = newUpdateUnitResourceOps(unitID, stored, progress) default: // Either insert or update will work so we should not get here. return nil, errors.New("setting the resource failed") } // No pending resources so we always do this here. ops = append(ops, p.base.ServiceExistsOps(res.ServiceID)...) return ops, nil } if err := p.base.Run(buildTxn); err != nil { return errors.Trace(err) } return nil }
func (ResourceSuite) TestValidateZeroValue(c *gc.C) { var res resource.Resource err := res.Validate() c.Check(errors.Cause(err), jc.Satisfies, errors.IsNotValid) c.Check(err, gc.ErrorMatches, `.*bad info.*`) }
func (ResourceSuite) TestValidateUploadNotUsed(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-service/spam", ServiceID: "a-service", } err := res.Validate() c.Check(err, jc.ErrorIsNil) }
func (ResourceSuite) TestValidateUploadNotUsed(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-application/spam", ApplicationID: "a-application", } err := res.Validate() c.Check(err, jc.ErrorIsNil) }
func (ResourceSuite) TestValidateMissingID(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ServiceID: "a-service", Username: "******", Timestamp: time.Now(), } err := res.Validate() c.Check(err, jc.ErrorIsNil) }
func (ResourceSuite) TestValidateMissingServiceID(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-service/spam", Username: "******", Timestamp: time.Now(), } err := res.Validate() c.Check(errors.Cause(err), jc.Satisfies, errors.IsNotValid) c.Check(err, gc.ErrorMatches, `.*missing service ID.*`) }
func (ResourceSuite) TestValidateUploadUsed(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-application/spam", ApplicationID: "a-application", Username: "******", Timestamp: time.Now(), } err := res.Validate() c.Check(err, jc.ErrorIsNil) }
func (ResourceSuite) TestValidateBadInfo(c *gc.C) { var charmRes charmresource.Resource c.Assert(charmRes.Validate(), gc.NotNil) res := resource.Resource{ Resource: charmRes, } err := res.Validate() c.Check(errors.Cause(err), jc.Satisfies, errors.IsNotValid) c.Check(err, gc.ErrorMatches, `.*bad info.*`) }
func (ResourceSuite) TestValidateUploadPending(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: time.Now(), } err := res.Validate() c.Check(err, jc.ErrorIsNil) }
func (ResourceSuite) TestValidateMissingTimestamp(c *gc.C) { res := resource.Resource{ Resource: newFullCharmResource(c, "spam"), ID: "a-application/spam", ApplicationID: "a-application", Username: "******", Timestamp: time.Time{}, } err := res.Validate() c.Check(errors.Cause(err), jc.Satisfies, errors.IsNotValid) c.Check(err, gc.ErrorMatches, `.*missing timestamp.*`) }
func (helpersSuite) TestAPI2Resource(c *gc.C) { now := time.Now() res, err := api.API2Resource(api.Resource{ CharmResource: api.CharmResource{ Name: "spam", Type: "file", Path: "spam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, }) c.Assert(err, jc.ErrorIsNil) fp, err := charmresource.NewFingerprint([]byte(fingerprint)) c.Assert(err, jc.ErrorIsNil) expected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "spam", Type: charmresource.TypeFile, Path: "spam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } err = expected.Validate() c.Assert(err, jc.ErrorIsNil) c.Check(res, jc.DeepEquals, expected) }
func newResource(c *gc.C, name, serviceID, username, content string) resource.Resource { var timestamp time.Time if username != "" { timestamp = time.Now().UTC() } res := resource.Resource{ Resource: NewCharmResource(c, name, content), ID: serviceID + "/" + name, PendingID: "", ServiceID: serviceID, Username: username, Timestamp: timestamp, } err := res.Validate() c.Assert(err, jc.ErrorIsNil) return res }
func (HelpersSuite) TestResource2API(c *gc.C) { fp, err := charmresource.NewFingerprint([]byte(fingerprint)) c.Assert(err, jc.ErrorIsNil) now := time.Now() res := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "spam", Type: charmresource.TypeFile, Path: "spam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, } err = res.Validate() c.Assert(err, jc.ErrorIsNil) apiRes := api.Resource2API(res) c.Check(apiRes, jc.DeepEquals, api.Resource{ CharmResource: api.CharmResource{ Name: "spam", Type: "file", Path: "spam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, }) }
func newResource(c *gc.C, name, applicationID, username, content string) resource.Resource { var timestamp time.Time if username != "" { // TODO(perrito666) 2016-05-02 lp:1558657 timestamp = time.Now().UTC() } res := resource.Resource{ Resource: NewCharmResource(c, name, content), ID: applicationID + "/" + name, PendingID: "", ApplicationID: applicationID, Username: username, Timestamp: timestamp, } err := res.Validate() c.Assert(err, jc.ErrorIsNil) return res }
func (ResourceSuite) TestRevisionStringNone(c *gc.C) { res := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "foo", Type: charmresource.TypeFile, Path: "foo.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, }, ServiceID: "svc", } err := res.Validate() c.Check(err, jc.ErrorIsNil) c.Check(res.RevisionString(), gc.Equals, "-") }
// doc2basicResource returns the resource info represented by the doc. func doc2basicResource(doc resourceDoc) (resource.Resource, error) { var res resource.Resource resType, err := charmresource.ParseType(doc.Type) if err != nil { return res, errors.Annotate(err, "got invalid data from DB") } origin, err := charmresource.ParseOrigin(doc.Origin) if err != nil { return res, errors.Annotate(err, "got invalid data from DB") } fp, err := resource.DeserializeFingerprint(doc.Fingerprint) if err != nil { return res, errors.Annotate(err, "got invalid data from DB") } res = resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: doc.Name, Type: resType, Path: doc.Path, Description: doc.Description, }, Origin: origin, Revision: doc.Revision, Fingerprint: fp, Size: doc.Size, }, ID: doc.ID, PendingID: doc.PendingID, ServiceID: doc.ServiceID, Username: doc.Username, Timestamp: doc.Timestamp, } if err := res.Validate(); err != nil { return res, errors.Annotate(err, "got invalid data from DB") } return res, nil }
func (ResourceSuite) TestRevisionStringTime(c *gc.C) { res := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "foo", Type: charmresource.TypeFile, Path: "foo.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, }, ServiceID: "svc", Username: "******", Timestamp: time.Date(2012, 7, 8, 15, 59, 5, 5, time.UTC), } err := res.Validate() c.Check(err, jc.ErrorIsNil) c.Check(res.RevisionString(), gc.Equals, "2012-07-08 15:59:05 +0000 UTC") }
func (ResourceSuite) TestRevisionStringNumber(c *gc.C) { res := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "foo", Type: charmresource.TypeFile, Path: "foo.tgz", Description: "you need it", }, Origin: charmresource.OriginStore, Revision: 7, }, ApplicationID: "svc", Username: "******", Timestamp: time.Date(2012, 7, 8, 15, 59, 5, 5, time.UTC), } err := res.Validate() c.Check(err, jc.ErrorIsNil) c.Check(res.RevisionString(), gc.Equals, "7") }
// SetResource sets the info for the resource. func (p ResourcePersistence) SetResource(res resource.Resource) error { stored, err := p.getStored(res) if errors.IsNotFound(err) { stored = storedResource{Resource: res} } else if err != nil { return errors.Trace(err) } // TODO(ericsnow) Ensure that stored.Resource matches res? If we do // so then the following line is unnecessary. stored.Resource = res if err := res.Validate(); err != nil { return errors.Annotate(err, "bad resource") } buildTxn := func(attempt int) ([]txn.Op, error) { // This is an "upsert". var ops []txn.Op switch attempt { case 0: ops = newInsertResourceOps(stored) case 1: ops = newUpdateResourceOps(stored) default: // Either insert or update will work so we should not get here. return nil, errors.New("setting the resource failed") } if stored.PendingID == "" { // Only non-pending resources must have an existing service. ops = append(ops, p.base.ServiceExistsOps(res.ServiceID)...) } return ops, nil } if err := p.base.Run(buildTxn); err != nil { return errors.Trace(err) } return nil }
// StageResource adds the resource in a separate staging area // if the resource isn't already staged. If it is then // errors.AlreadyExists is returned. A wrapper around the staged // resource is returned which supports both finalizing and removing // the staged resource. func (p ResourcePersistence) StageResource(res resource.Resource, storagePath string) (*StagedResource, error) { if storagePath == "" { return nil, errors.Errorf("missing storage path") } if err := res.Validate(); err != nil { return nil, errors.Annotate(err, "bad resource") } stored := storedResource{ Resource: res, storagePath: storagePath, } staged := &StagedResource{ base: p.base, id: res.ID, stored: stored, } if err := staged.stage(); err != nil { return nil, errors.Trace(err) } return staged, nil }
// API2Resource converts an API Resource struct into // a resource.Resource. func API2Resource(apiRes Resource) (resource.Resource, error) { var res resource.Resource charmRes, err := API2CharmResource(apiRes.CharmResource) if err != nil { return res, errors.Trace(err) } res = resource.Resource{ Resource: charmRes, ID: apiRes.ID, PendingID: apiRes.PendingID, ServiceID: apiRes.ServiceID, Username: apiRes.Username, Timestamp: apiRes.Timestamp, } if err := res.Validate(); err != nil { return res, errors.Trace(err) } return res, nil }
func (HelpersSuite) TestAPIResult2ServiceResourcesOkay(c *gc.C) { fp, err := charmresource.NewFingerprint([]byte(fingerprint)) c.Assert(err, jc.ErrorIsNil) now := time.Now() expected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "spam", Type: charmresource.TypeFile, Path: "spam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, } err = expected.Validate() c.Assert(err, jc.ErrorIsNil) unitExpected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "unitspam", Type: charmresource.TypeFile, Path: "unitspam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, } err = unitExpected.Validate() c.Assert(err, jc.ErrorIsNil) apiRes := api.Resource{ CharmResource: api.CharmResource{ Name: "spam", Type: "file", Path: "spam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, } unitRes := api.Resource{ CharmResource: api.CharmResource{ Name: "unitspam", Type: "file", Path: "unitspam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-application/spam", PendingID: "some-unique-ID", ApplicationID: "a-application", Username: "******", Timestamp: now, } fp2, err := charmresource.GenerateFingerprint(strings.NewReader("boo!")) c.Assert(err, jc.ErrorIsNil) chRes := api.CharmResource{ Name: "unitspam2", Type: "file", Path: "unitspam.tgz2", Description: "you need it2", Origin: "upload", Revision: 2, Fingerprint: fp2.Bytes(), Size: 11, } chExpected := charmresource.Resource{ Meta: charmresource.Meta{ Name: "unitspam2", Type: charmresource.TypeFile, Path: "unitspam.tgz2", Description: "you need it2", }, Origin: charmresource.OriginUpload, Revision: 2, Fingerprint: fp2, Size: 11, } resources, err := api.APIResult2ServiceResources(api.ResourcesResult{ Resources: []api.Resource{ apiRes, }, CharmStoreResources: []api.CharmResource{ chRes, }, UnitResources: []api.UnitResources{ { Entity: params.Entity{ Tag: "unit-foo-0", }, Resources: []api.Resource{ unitRes, }, DownloadProgress: map[string]int64{ unitRes.Name: 8, }, }, }, }) c.Assert(err, jc.ErrorIsNil) serviceResource := resource.ServiceResources{ Resources: []resource.Resource{ expected, }, CharmStoreResources: []charmresource.Resource{ chExpected, }, UnitResources: []resource.UnitResources{ { Tag: names.NewUnitTag("foo/0"), Resources: []resource.Resource{ unitExpected, }, DownloadProgress: map[string]int64{ unitRes.Name: 8, }, }, }, } c.Check(resources, jc.DeepEquals, serviceResource) }
func (helpersSuite) TestAPIResult2ServiceResourcesBadUnitTag(c *gc.C) { fp, err := charmresource.NewFingerprint([]byte(fingerprint)) c.Assert(err, jc.ErrorIsNil) now := time.Now() expected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "spam", Type: charmresource.TypeFile, Path: "spam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } err = expected.Validate() c.Assert(err, jc.ErrorIsNil) unitExpected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "unitspam", Type: charmresource.TypeFile, Path: "unitspam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } err = unitExpected.Validate() c.Assert(err, jc.ErrorIsNil) apiRes := api.Resource{ CharmResource: api.CharmResource{ Name: "spam", Type: "file", Path: "spam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } unitRes := api.Resource{ CharmResource: api.CharmResource{ Name: "unitspam", Type: "file", Path: "unitspam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } _, err = api.APIResult2ServiceResources(api.ResourcesResult{ Resources: []api.Resource{ apiRes, }, UnitResources: []api.UnitResources{ { Entity: params.Entity{ Tag: "THIS IS NOT A GOOD UNIT TAG", }, Resources: []api.Resource{ unitRes, }, }, }, }) c.Assert(err, gc.ErrorMatches, ".*got bad data from server.*") }
func (helpersSuite) TestAPIResult2ServiceResourcesOkay(c *gc.C) { fp, err := charmresource.NewFingerprint([]byte(fingerprint)) c.Assert(err, jc.ErrorIsNil) now := time.Now() expected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "spam", Type: charmresource.TypeFile, Path: "spam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } err = expected.Validate() c.Assert(err, jc.ErrorIsNil) unitExpected := resource.Resource{ Resource: charmresource.Resource{ Meta: charmresource.Meta{ Name: "unitspam", Type: charmresource.TypeFile, Path: "unitspam.tgz", Description: "you need it", }, Origin: charmresource.OriginUpload, Revision: 1, Fingerprint: fp, Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } err = unitExpected.Validate() c.Assert(err, jc.ErrorIsNil) apiRes := api.Resource{ CharmResource: api.CharmResource{ Name: "spam", Type: "file", Path: "spam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } unitRes := api.Resource{ CharmResource: api.CharmResource{ Name: "unitspam", Type: "file", Path: "unitspam.tgz", Description: "you need it", Origin: "upload", Revision: 1, Fingerprint: []byte(fingerprint), Size: 10, }, ID: "a-service/spam", PendingID: "some-unique-ID", ServiceID: "a-service", Username: "******", Timestamp: now, } resources, err := api.APIResult2ServiceResources(api.ResourcesResult{ Resources: []api.Resource{ apiRes, }, UnitResources: []api.UnitResources{ { Entity: params.Entity{ Tag: "unit-foo-0", }, Resources: []api.Resource{ unitRes, }, }, }, }) c.Assert(err, jc.ErrorIsNil) serviceResource := resource.ServiceResources{ Resources: []resource.Resource{ expected, }, UnitResources: []resource.UnitResources{ { Tag: names.NewUnitTag("foo/0"), Resources: []resource.Resource{ unitExpected, }, }, }, } c.Check(resources, jc.DeepEquals, serviceResource) }