// processPost handles a charm upload POST request after authentication. func (h *charmsHandler) processPost(r *http.Request, st *state.State) (*charm.URL, error) { query := r.URL.Query() series := query.Get("series") if series == "" { return nil, fmt.Errorf("expected series=URL argument") } // Make sure the content type is zip. contentType := r.Header.Get("Content-Type") if contentType != "application/zip" { return nil, fmt.Errorf("expected Content-Type: application/zip, got: %v", contentType) } tempFile, err := ioutil.TempFile("", "charm") if err != nil { return nil, fmt.Errorf("cannot create temp file: %v", err) } defer tempFile.Close() defer os.Remove(tempFile.Name()) if _, err := io.Copy(tempFile, r.Body); err != nil { return nil, fmt.Errorf("error processing file upload: %v", err) } err = h.processUploadedArchive(tempFile.Name()) if err != nil { return nil, err } archive, err := charm.ReadCharmArchive(tempFile.Name()) if err != nil { return nil, fmt.Errorf("invalid charm archive: %v", err) } // We got it, now let's reserve a charm URL for it in state. archiveURL := &charm.URL{ Schema: "local", Name: archive.Meta().Name, Revision: archive.Revision(), Series: series, } preparedURL, err := st.PrepareLocalCharmUpload(archiveURL) if err != nil { return nil, err } // Now we need to repackage it with the reserved URL, upload it to // provider storage and update the state. err = h.repackageAndUploadCharm(st, archive, preparedURL) if err != nil { return nil, err } // All done. return preparedURL, nil }
// processPost handles a charm upload POST request after authentication. func (h *charmsHandler) processPost(r *http.Request, st *state.State) (*charm.URL, error) { query := r.URL.Query() schema := query.Get("schema") if schema == "" { schema = "local" } series := query.Get("series") if series != "" { if err := charm.ValidateSeries(series); err != nil { return nil, errors.NewBadRequest(err, "") } } // Make sure the content type is zip. contentType := r.Header.Get("Content-Type") if contentType != "application/zip" { return nil, errors.BadRequestf("expected Content-Type: application/zip, got: %v", contentType) } charmFileName, err := writeCharmToTempFile(r.Body) if err != nil { return nil, errors.Trace(err) } defer os.Remove(charmFileName) err = h.processUploadedArchive(charmFileName) if err != nil { return nil, err } archive, err := charm.ReadCharmArchive(charmFileName) if err != nil { return nil, errors.BadRequestf("invalid charm archive: %v", err) } name := archive.Meta().Name if err := charm.ValidateName(name); err != nil { return nil, errors.NewBadRequest(err, "") } // We got it, now let's reserve a charm URL for it in state. curl := &charm.URL{ Schema: schema, Name: archive.Meta().Name, Revision: archive.Revision(), Series: series, } if schema == "local" { curl, err = st.PrepareLocalCharmUpload(curl) if err != nil { return nil, errors.Trace(err) } } else { // "cs:" charms may only be uploaded into models which are // being imported during model migrations. There's currently // no other time where it makes sense to accept charm store // charms through this endpoint. if isImporting, err := modelIsImporting(st); err != nil { return nil, errors.Trace(err) } else if !isImporting { return nil, errors.New("cs charms may only be uploaded during model migration import") } // If a revision argument is provided, it takes precedence // over the revision in the charm archive. This is required to // handle the revision differences between unpublished and // published charms in the charm store. revisionStr := query.Get("revision") if revisionStr != "" { curl.Revision, err = strconv.Atoi(revisionStr) if err != nil { return nil, errors.NewBadRequest(errors.NewNotValid(err, "revision"), "") } } if _, err := st.PrepareStoreCharmUpload(curl); err != nil { return nil, errors.Trace(err) } } // Now we need to repackage it with the reserved URL, upload it to // provider storage and update the state. err = h.repackageAndUploadCharm(st, archive, curl) if err != nil { return nil, errors.Trace(err) } return curl, nil }