// PutCharm uploads the given charm to provider storage, and adds a // state.Charm to the state. The charm is not uploaded if a charm with // the same URL already exists in the state. // If bumpRevision is true, the charm must be a local directory, // and the revision number will be incremented before pushing. func PutCharm(st *state.State, curl *charm.URL, repo charmrepo.Interface, bumpRevision bool) (*state.Charm, error) { if curl.Revision == -1 { rev, err := charmrepo.Latest(repo, curl) if err != nil { return nil, fmt.Errorf("cannot get latest charm revision: %v", err) } curl = curl.WithRevision(rev) } ch, err := repo.Get(curl) if err != nil { return nil, fmt.Errorf("cannot get charm: %v", err) } if bumpRevision { chd, ok := ch.(*charm.CharmDir) if !ok { return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl) } if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil { return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err) } curl = curl.WithRevision(chd.Revision()) } if sch, err := st.Charm(curl); err == nil { return sch, nil } return addCharm(st, curl, ch) }
// processGet handles a charm file GET request after authentication. // It returns the bundle path, the requested file path (if any), whether the // default charm icon has been requested and an error. func (h *charmsHandler) processGet(r *http.Request, st *state.State) ( archivePath string, fileArg string, serveIcon bool, err error, ) { errRet := func(err error) (string, string, bool, error) { return "", "", false, err } query := r.URL.Query() // Retrieve and validate query parameters. curlString := query.Get("url") if curlString == "" { return errRet(errors.Errorf("expected url=CharmURL query argument")) } curl, err := charm.ParseURL(curlString) if err != nil { return errRet(errors.Trace(err)) } fileArg = query.Get("file") if fileArg != "" { fileArg = path.Clean(fileArg) } else if query.Get("icon") == "1" { serveIcon = true fileArg = "icon.svg" } // Ensure the working directory exists. tmpDir := filepath.Join(h.dataDir, "charm-get-tmp") if err = os.MkdirAll(tmpDir, 0755); err != nil { return errRet(errors.Annotate(err, "cannot create charms tmp directory")) } // Use the storage to retrieve and save the charm archive. storage := storage.NewStorage(st.ModelUUID(), st.MongoSession()) ch, err := st.Charm(curl) if err != nil { return errRet(errors.Annotate(err, "cannot get charm from state")) } reader, _, err := storage.Get(ch.StoragePath()) if err != nil { return errRet(errors.Annotate(err, "cannot get charm from model storage")) } defer reader.Close() charmFile, err := ioutil.TempFile(tmpDir, "charm") if err != nil { return errRet(errors.Annotate(err, "cannot create charm archive file")) } if _, err = io.Copy(charmFile, reader); err != nil { cleanupFile(charmFile) return errRet(errors.Annotate(err, "error processing charm archive download")) } charmFile.Close() return charmFile.Name(), fileArg, serveIcon, nil }
// downloadCharm downloads the given charm name from the provider storage and // saves the corresponding zip archive to the given charmArchivePath. func (h *charmsHandler) downloadCharm(st *state.State, curl *charm.URL, charmArchivePath string) error { storage := storage.NewStorage(st.ModelUUID(), st.MongoSession()) ch, err := st.Charm(curl) if err != nil { return errors.Annotate(err, "cannot get charm from state") } // In order to avoid races, the archive is saved in a temporary file which // is then atomically renamed. The temporary file is created in the // charm cache directory so that we can safely assume the rename source and // target live in the same file system. cacheDir := filepath.Dir(charmArchivePath) if err = os.MkdirAll(cacheDir, 0755); err != nil { return errors.Annotate(err, "cannot create the charms cache") } tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") if err != nil { return errors.Annotate(err, "cannot create charm archive temp file") } defer tempCharmArchive.Close() // Use the storage to retrieve and save the charm archive. reader, _, err := storage.Get(ch.StoragePath()) if err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "cannot get charm from model storage") } defer reader.Close() if _, err = io.Copy(tempCharmArchive, reader); err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error processing charm archive download") } tempCharmArchive.Close() if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != nil { defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error renaming the charm archive") } return nil }
// DeployService fetches the charm from the charm store and deploys it. // The logic has been factored out into a common function which is called by // both the legacy API on the client facade, as well as the new service facade. func deployService(st *state.State, owner string, args params.ServiceDeploy) error { curl, err := charm.ParseURL(args.CharmUrl) if err != nil { return errors.Trace(err) } if curl.Revision < 0 { return errors.Errorf("charm url must include revision") } // Do a quick but not complete validation check before going any further. for _, p := range args.Placement { if p.Scope != instance.MachineScope { continue } _, err = st.Machine(p.Directive) if err != nil { return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, p.Directive) } } // Try to find the charm URL in state first. ch, err := st.Charm(curl) if errors.IsNotFound(err) { // Clients written to expect 1.16 compatibility require this next block. if curl.Schema != "cs" { return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema) } if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{ URL: args.CharmUrl, }); err == nil { ch, err = st.Charm(curl) } } if err != nil { return errors.Trace(err) } var settings charm.Settings if len(args.ConfigYAML) > 0 { settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) } else if len(args.Config) > 0 { // Parse config in a compatible way (see function comment). settings, err = parseSettingsCompatible(ch, args.Config) } if err != nil { return errors.Trace(err) } // Convert network tags to names for any given networks. requestedNetworks, err := networkTagsToNames(args.Networks) if err != nil { return errors.Trace(err) } _, err = jjj.DeployService(st, jjj.DeployServiceParams{ ServiceName: args.ServiceName, Series: args.Series, // TODO(dfc) ServiceOwner should be a tag ServiceOwner: owner, Charm: ch, NumUnits: args.NumUnits, ConfigSettings: settings, Constraints: args.Constraints, Placement: args.Placement, Networks: requestedNetworks, Storage: args.Storage, }) return errors.Trace(err) }