// Init implements cmd.Command.Init. It will return an error satisfying // errors.BadRequest if you give it an incorrect number of arguments. func (c *ShowServiceCommand) Init(args []string) error { if len(args) == 0 { return errors.NewBadRequest(nil, "missing service name") } c.target = args[0] if err := cmd.CheckEmpty(args[1:]); err != nil { return errors.NewBadRequest(err, "") } return nil }
func (h *charmsHandler) serveGet(w http.ResponseWriter, r *http.Request) error { // TODO (bug #1499338 2015/09/24) authenticate this. st, err := h.ctxt.stateForRequestUnauthenticated(r) if err != nil { return errors.Trace(err) } // Retrieve or list charm files. // Requires "url" (charm URL) and an optional "file" (the path to the // charm file) to be included in the query. charmArchivePath, filePath, err := h.processGet(r, st) if err != nil { // An error occurred retrieving the charm bundle. if errors.IsNotFound(err) { return errors.Trace(err) } return errors.NewBadRequest(err, "") } var sender bundleContentSenderFunc switch filePath { case "": // The client requested the list of charm files. sender = h.manifestSender case "*": // The client requested the archive. sender = h.archiveSender default: // The client requested a specific file. sender = h.archiveEntrySender(filePath) } if err := h.sendBundleContent(w, r, charmArchivePath, sender); err != nil { return errors.Trace(err) } return nil }
func (h *toolsDownloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { st, err := h.ctxt.stateForRequestUnauthenticated(r) if err != nil { if err := sendError(w, err); err != nil { logger.Errorf("%v", err) } return } switch r.Method { case "GET": tarball, err := h.processGet(r, st) if err != nil { logger.Errorf("GET(%s) failed: %v", r.URL, err) if err := sendError(w, errors.NewBadRequest(err, "")); err != nil { logger.Errorf("%v", err) } return } if err := h.sendTools(w, http.StatusOK, tarball); err != nil { logger.Errorf("%v", err) } default: if err := sendError(w, errors.MethodNotAllowedf("unsupported method: %q", r.Method)); err != nil { logger.Errorf("%v", err) } } }
// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). If the // error could not be converted then false is returned. func RestoreError(err error) (error, bool) { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err, false } else if apiErr == nil { return nil, true } if params.ErrCode(err) == "" { return err, false } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton, true } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg), true case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg), true case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg), true case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg), true case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err, false case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err, false case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg), true case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err, false case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err, false case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg), true case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg), true case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg), true case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err, false default: return err, false } }
// sendTools streams the tools tarball to the client. func (h *toolsDownloadHandler) sendTools(w http.ResponseWriter, statusCode int, tarball []byte) { w.Header().Set("Content-Type", "application/x-tar-gz") w.Header().Set("Content-Length", fmt.Sprint(len(tarball))) w.WriteHeader(statusCode) if _, err := w.Write(tarball); err != nil { sendError(w, errors.NewBadRequest(errors.Annotatef(err, "failed to write tools"), "")) return } }
// 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 (h *charmsHandler) servePost(w http.ResponseWriter, r *http.Request) error { st, _, err := h.ctxt.stateForRequestAuthenticatedUser(r) if err != nil { return errors.Trace(err) } // Add a charm to the store provider. charmURL, err := h.processPost(r, st) if err != nil { return errors.NewBadRequest(err, "") } sendStatusAndJSON(w, http.StatusOK, ¶ms.CharmsResponse{CharmURL: charmURL.String()}) 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 }
func (h *charmsHandler) ServeGet(w http.ResponseWriter, r *http.Request) error { if r.Method != "GET" { return errors.Trace(emitUnsupportedMethodErr(r.Method)) } st, _, err := h.ctxt.stateForRequestAuthenticated(r) if err != nil { return errors.Trace(err) } // Retrieve or list charm files. // Requires "url" (charm URL) and an optional "file" (the path to the // charm file) to be included in the query. Optionally also receives an // "icon" query for returning the charm icon or a default one in case the // charm has no icon. charmArchivePath, fileArg, serveIcon, err := h.processGet(r, st) if err != nil { // An error occurred retrieving the charm bundle. if errors.IsNotFound(err) { return errors.Trace(err) } return errors.NewBadRequest(err, "") } defer os.Remove(charmArchivePath) var sender bundleContentSenderFunc switch fileArg { case "": // The client requested the list of charm files. sender = h.manifestSender case "*": // The client requested the archive. sender = h.archiveSender default: // The client requested a specific file. sender = h.archiveEntrySender(fileArg, serveIcon) } return errors.Trace(sendBundleContent(w, r, charmArchivePath, sender)) }
// 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 }
// 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 }
// 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 }