func addCharm(st *state.State, curl *charm.URL, ch charm.Charm) (*state.Charm, error) { var f *os.File name := charm.Quote(curl.String()) switch ch := ch.(type) { case *charm.Dir: var err error if f, err = ioutil.TempFile("", name); err != nil { return nil, err } defer os.Remove(f.Name()) defer f.Close() err = ch.BundleTo(f) if err != nil { return nil, fmt.Errorf("cannot bundle charm: %v", err) } if _, err := f.Seek(0, 0); err != nil { return nil, err } case *charm.Bundle: var err error if f, err = os.Open(ch.Path); err != nil { return nil, fmt.Errorf("cannot read charm bundle: %v", err) } defer f.Close() default: return nil, fmt.Errorf("unknown charm type %T", ch) } digest, size, err := utils.ReadSHA256(f) if err != nil { return nil, err } if _, err := f.Seek(0, 0); err != nil { return nil, err } cfg, err := st.EnvironConfig() if err != nil { return nil, err } env, err := environs.New(cfg) if err != nil { return nil, err } stor := env.Storage() if err := stor.Put(name, f, size); err != nil { return nil, fmt.Errorf("cannot put charm: %v", err) } ustr, err := stor.URL(name) if err != nil { return nil, fmt.Errorf("cannot get storage URL for charm: %v", err) } u, err := url.Parse(ustr) if err != nil { return nil, fmt.Errorf("cannot parse storage URL: %v", err) } sch, err := st.AddCharm(ch, curl, u, digest) if err != nil { return nil, fmt.Errorf("cannot add charm: %v", err) } return sch, nil }
func addCharm(c *gc.C, st *State, series string, ch charm.Charm) *Charm { ident := fmt.Sprintf("%s-%s-%d", series, ch.Meta().Name, ch.Revision()) curl := charm.MustParseURL("local:" + series + "/" + ident) bundleURL, err := url.Parse("http://bundles.testing.invalid/" + ident) c.Assert(err, gc.IsNil) sch, err := st.AddCharm(ch, curl, bundleURL, ident+"-sha256") c.Assert(err, gc.IsNil) return sch }
// AddCharm adds the ch charm with curl to the state. bundleURL must // be set to a URL where the bundle for ch may be downloaded from. On // success the newly added charm state is returned. func (st *State) AddCharm(ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string) (stch *Charm, err error) { // The charm may already exist in state as a placeholder, so we // check for that situation and update the existing charm record // if necessary, otherwise add a new record. var existing charmDoc err = st.charms.Find(bson.D{{"_id", curl.String()}, {"placeholder", true}}).One(&existing) if err == mgo.ErrNotFound { cdoc := &charmDoc{ URL: curl, Meta: ch.Meta(), Config: ch.Config(), Actions: ch.Actions(), BundleURL: bundleURL, BundleSha256: bundleSha256, } err = st.charms.Insert(cdoc) if err != nil { return nil, fmt.Errorf("cannot add charm %q: %v", curl, err) } return newCharm(st, cdoc) } else if err != nil { return nil, err } return st.updateCharmDoc(ch, curl, bundleURL, bundleSha256, stillPlaceholder) }
func checkDummy(c *gc.C, f charm.Charm, path string) { c.Assert(f.Revision(), gc.Equals, 1) c.Assert(f.Meta().Name, gc.Equals, "dummy") c.Assert(f.Config().Options["title"].Default, gc.Equals, "My Title") c.Assert(f.Actions(), gc.DeepEquals, &charm.Actions{ map[string]charm.ActionSpec{ "snapshot": charm.ActionSpec{ Description: "Take a snapshot of the database.", Params: map[string]interface{}{ "outfile": map[string]interface{}{ "description": "The file to write out to.", "type": "string", "default": "foo.bz2", }}}}}) switch f := f.(type) { case *charm.Bundle: c.Assert(f.Path, gc.Equals, path) case *charm.Dir: c.Assert(f.Path, gc.Equals, path) } }
// updateCharmDoc updates the charm with specified URL with the given // data, and resets the placeholder and pendingupdate flags. If the // charm is no longer a placeholder or pending (depending on preReq), // it returns ErrCharmRevisionAlreadyModified. func (st *State) updateCharmDoc( ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string, preReq interface{}) (*Charm, error) { updateFields := bson.D{{"$set", bson.D{ {"meta", ch.Meta()}, {"config", ch.Config()}, {"actions", ch.Actions()}, {"bundleurl", bundleURL}, {"bundlesha256", bundleSha256}, {"pendingupload", false}, {"placeholder", false}, }}} ops := []txn.Op{{ C: st.charms.Name, Id: curl, Assert: preReq, Update: updateFields, }} if err := st.runTransaction(ops); err != nil { return nil, onAbort(err, ErrCharmRevisionAlreadyModified) } return st.Charm(curl) }
// AddLocalCharm prepares the given charm with a local: schema in its // URL, and uploads it via the API server, returning the assigned // charm URL. If the API server does not support charm uploads, an // error satisfying params.IsCodeNotImplemented() is returned. func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) { if curl.Schema != "local" { return nil, fmt.Errorf("expected charm URL with local: schema, got %q", curl.String()) } // Package the charm for uploading. var archive *os.File switch ch := ch.(type) { case *charm.Dir: var err error if archive, err = ioutil.TempFile("", "charm"); err != nil { return nil, fmt.Errorf("cannot create temp file: %v", err) } defer os.Remove(archive.Name()) defer archive.Close() if err := ch.BundleTo(archive); err != nil { return nil, fmt.Errorf("cannot repackage charm: %v", err) } if _, err := archive.Seek(0, 0); err != nil { return nil, fmt.Errorf("cannot rewind packaged charm: %v", err) } case *charm.Bundle: var err error if archive, err = os.Open(ch.Path); err != nil { return nil, fmt.Errorf("cannot read charm archive: %v", err) } defer archive.Close() default: return nil, fmt.Errorf("unknown charm type %T", ch) } // Prepare the upload request. url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series) req, err := http.NewRequest("POST", url, archive) if err != nil { return nil, fmt.Errorf("cannot create upload request: %v", err) } req.SetBasicAuth(c.st.tag, c.st.password) req.Header.Set("Content-Type", "application/zip") // Send the request. // BUG(dimitern) 2013-12-17 bug #1261780 // Due to issues with go 1.1.2, fixed later, we cannot use a // regular TLS client with the CACert here, because we get "x509: // cannot validate certificate for 127.0.0.1 because it doesn't // contain any IP SANs". Once we use a later go version, this // should be changed to connect to the API server with a regular // HTTP+TLS enabled client, using the CACert (possily cached, like // the tag and password) passed in api.Open()'s info argument. resp, err := utils.GetNonValidatingHTTPClient().Do(req) if err != nil { return nil, fmt.Errorf("cannot upload charm: %v", err) } if resp.StatusCode == http.StatusMethodNotAllowed { // API server is 1.16 or older, so charm upload // is not supported; notify the client. return nil, ¶ms.Error{ Message: "charm upload is not supported by the API server", Code: params.CodeNotImplemented, } } // Now parse the response & return. body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("cannot read charm upload response: %v", err) } defer resp.Body.Close() var jsonResponse params.CharmsResponse if err := json.Unmarshal(body, &jsonResponse); err != nil { return nil, fmt.Errorf("cannot unmarshal upload response: %v", err) } if jsonResponse.Error != "" { return nil, fmt.Errorf("error uploading charm: %v", jsonResponse.Error) } return charm.MustParseURL(jsonResponse.CharmURL), nil }