func (u *UniterAPIV3) charmModifiedVersion(tagStr string, canAccess func(names.Tag) bool) (int, error) { tag, err := names.ParseTag(tagStr) if err != nil { return -1, common.ErrPerm } if !canAccess(tag) { return -1, common.ErrPerm } unitOrService, err := u.st.FindEntity(tag) if err != nil { return -1, err } var service *state.Application switch entity := unitOrService.(type) { case *state.Application: service = entity case *state.Unit: service, err = entity.Application() if err != nil { return -1, err } default: return -1, errors.BadRequestf("type %T does not have a CharmModifiedVersion", entity) } return service.CharmModifiedVersion(), nil }
// processPost handles a tools upload POST request after authentication. func (h *toolsUploadHandler) processPost(r *http.Request, st *state.State) (*tools.Tools, error) { query := r.URL.Query() binaryVersionParam := query.Get("binaryVersion") if binaryVersionParam == "" { return nil, errors.BadRequestf("expected binaryVersion argument") } toolsVersion, err := version.ParseBinary(binaryVersionParam) if err != nil { return nil, errors.NewBadRequest(err, fmt.Sprintf("invalid tools version %q", binaryVersionParam)) } // Make sure the content type is x-tar-gz. contentType := r.Header.Get("Content-Type") if contentType != "application/x-tar-gz" { return nil, errors.BadRequestf("expected Content-Type: application/x-tar-gz, got: %v", contentType) } // Get the server root, so we know how to form the URL in the Tools returned. serverRoot, err := h.getServerRoot(r, query, st) if err != nil { return nil, errors.NewBadRequest(err, "cannot to determine server root") } // We'll clone the tools for each additional series specified. var cloneSeries []string if seriesParam := query.Get("series"); seriesParam != "" { cloneSeries = strings.Split(seriesParam, ",") } logger.Debugf("request to upload tools: %s", toolsVersion) logger.Debugf("additional series: %s", cloneSeries) toolsVersions := []version.Binary{toolsVersion} for _, series := range cloneSeries { if series != toolsVersion.Series { v := toolsVersion v.Series = series toolsVersions = append(toolsVersions, v) } } return h.handleUpload(r.Body, toolsVersions, serverRoot, st) }
func (s *apiclientSuite) TestAPICallError(c *gc.C) { clock := &fakeClock{} conn := api.NewTestingState(api.TestingStateParams{ RPCConnection: newRPCConnection(errors.BadRequestf("boom")), Clock: clock, }) err := conn.APICall("facade", 1, "id", "method", nil, nil) c.Check(err.Error(), gc.Equals, "boom") c.Check(err, jc.Satisfies, errors.IsBadRequest) c.Check(clock.waits, gc.HasLen, 0) }
// Init implements cmd.Command.Init. It will return an error satisfying // errors.BadRequest if you give it an incorrect number of arguments. func (c *UploadCommand) Init(args []string) error { switch len(args) { case 0: return errors.BadRequestf("missing application name") case 1: return errors.BadRequestf("no resource specified") } service := args[0] if service == "" { // TODO(ericsnow) names.IsValidApplication return errors.NewNotValid(nil, "missing application name") } c.service = service if err := c.addResourceFile(args[1]); err != nil { return errors.Trace(err) } if err := cmd.CheckEmpty(args[2:]); err != nil { return errors.NewBadRequest(err, "") } return nil }
func getGUIComboPath(rootDir, query string) (string, error) { k := strings.SplitN(query, "=", 2)[0] fname, err := url.QueryUnescape(k) if err != nil { return "", errors.NewBadRequest(err, fmt.Sprintf("invalid file name %q", k)) } // Ignore pat injected queries. if strings.HasPrefix(fname, ":") { return "", nil } // The Juju GUI references its combined files starting from the // "static/gui/build" directory. fname = filepath.Clean(fname) if fname == ".." || strings.HasPrefix(fname, "../") { return "", errors.BadRequestf("forbidden file path %q", k) } return filepath.Join(rootDir, "static", "gui", "build", fname), nil }
// handleUpload uploads the tools data from the reader to env storage as the specified version. func (h *toolsUploadHandler) handleUpload(r io.Reader, toolsVersions []version.Binary, serverRoot string, st *state.State) (*tools.Tools, error) { // Check if changes are allowed and the command may proceed. blockChecker := common.NewBlockChecker(st) if err := blockChecker.ChangeAllowed(); err != nil { return nil, errors.Trace(err) } storage, err := st.ToolsStorage() if err != nil { return nil, err } defer storage.Close() // Read the tools tarball from the request, calculating the sha256 along the way. data, sha256, err := readAndHash(r) if err != nil { return nil, err } if len(data) == 0 { return nil, errors.BadRequestf("no tools uploaded") } // TODO(wallyworld): check integrity of tools tarball. // Store tools and metadata in tools storage. for _, v := range toolsVersions { metadata := binarystorage.Metadata{ Version: v.String(), Size: int64(len(data)), SHA256: sha256, } logger.Debugf("uploading tools %+v to storage", metadata) if err := storage.Add(bytes.NewReader(data), metadata); err != nil { return nil, err } } tools := &tools.Tools{ Version: toolsVersions[0], Size: int64(len(data)), SHA256: sha256, URL: common.ToolsURL(serverRoot, toolsVersions[0]), } return tools, nil }
// handlePut is used to switch to a specific Juju GUI version. func (h *guiVersionHandler) handlePut(w http.ResponseWriter, req *http.Request) error { // Validate the request. if ctype := req.Header.Get("Content-Type"); ctype != params.ContentTypeJSON { return errors.BadRequestf("invalid content type %q: expected %q", ctype, params.ContentTypeJSON) } // Authenticate the request and retrieve the Juju state. st, _, err := h.ctxt.stateForRequestAuthenticatedUser(req) if err != nil { return errors.Annotate(err, "cannot open state") } var selected params.GUIVersionRequest decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&selected); err != nil { return errors.NewBadRequest(err, "invalid request body") } // Switch to the provided GUI version. if err = st.GUISetVersion(selected.Version); err != nil { return errors.Trace(err) } return nil }
// handlePost is used to upload new Juju GUI archives to the controller. func (h *guiArchiveHandler) handlePost(w http.ResponseWriter, req *http.Request) error { // Validate the request. if ctype := req.Header.Get("Content-Type"); ctype != bzMimeType { return errors.BadRequestf("invalid content type %q: expected %q", ctype, bzMimeType) } if err := req.ParseForm(); err != nil { return errors.Annotate(err, "cannot parse form") } versParam := req.Form.Get("version") if versParam == "" { return errors.BadRequestf("version parameter not provided") } vers, err := version.Parse(versParam) if err != nil { return errors.BadRequestf("invalid version parameter %q", versParam) } hashParam := req.Form.Get("hash") if hashParam == "" { return errors.BadRequestf("hash parameter not provided") } if req.ContentLength == -1 { return errors.BadRequestf("content length not provided") } // Open the GUI archive storage. st, _, err := h.ctxt.stateForRequestAuthenticatedUser(req) if err != nil { return errors.Annotate(err, "cannot open state") } storage, err := st.GUIStorage() if err != nil { return errors.Annotate(err, "cannot open GUI storage") } defer storage.Close() // Read and validate the archive data. data, hash, err := readAndHash(req.Body) size := int64(len(data)) if size != req.ContentLength { return errors.BadRequestf("archive does not match provided content length") } if hash != hashParam { return errors.BadRequestf("archive does not match provided hash") } // Add the archive to the GUI storage. metadata := binarystorage.Metadata{ Version: vers.String(), Size: size, SHA256: hash, } if err := storage.Add(bytes.NewReader(data), metadata); err != nil { return errors.Annotate(err, "cannot add GUI archive to storage") } // Prepare and return the response. resp := params.GUIArchiveVersion{ Version: vers, SHA256: hash, } if currentVers, err := st.GUIVersion(); err == nil { if currentVers == vers { resp.Current = true } } else if !errors.IsNotFound(err) { return errors.Annotate(err, "cannot retrieve current GUI version") } sendStatusAndJSON(w, http.StatusOK, resp) return nil }
err: lease.ErrClaimDenied, code: params.CodeLeaseClaimDenied, status: http.StatusInternalServerError, helperFunc: params.IsCodeLeaseClaimDenied, }, { err: common.OperationBlockedError("test"), code: params.CodeOperationBlocked, status: http.StatusBadRequest, helperFunc: params.IsCodeOperationBlocked, }, { err: errors.NotSupportedf("needed feature"), code: params.CodeNotSupported, status: http.StatusInternalServerError, helperFunc: params.IsCodeNotSupported, }, { err: errors.BadRequestf("something"), code: params.CodeBadRequest, status: http.StatusBadRequest, helperFunc: params.IsBadRequest, }, { err: errors.MethodNotAllowedf("something"), code: params.CodeMethodNotAllowed, status: http.StatusMethodNotAllowed, helperFunc: params.IsMethodNotAllowed, }, { err: stderrors.New("an error"), status: http.StatusInternalServerError, code: "", }, { err: &common.DischargeRequiredError{ Cause: errors.New("something"),
// 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 }