// title: rollback // path: /apps/{appname}/deploy/rollback // method: POST // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: OK // 400: Invalid data // 403: Forbidden // 404: Not found func deployRollback(w http.ResponseWriter, r *http.Request, t auth.Token) error { appName := r.URL.Query().Get(":appname") instance, err := app.GetByName(appName) if err != nil { return &tsuruErrors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf("App %s not found.", appName)} } image := r.FormValue("image") if image == "" { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: "you cannot rollback without an image name", } } origin := r.FormValue("origin") if origin != "" { if !app.ValidateOrigin(origin) { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid deployment origin", } } } w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} opts := app.DeployOptions{ App: instance, OutputStream: writer, Image: image, User: t.GetUserName(), Origin: origin, Rollback: true, } opts.GetKind() canRollback := permission.Check(t, permSchemeForDeploy(opts), contextsForApp(instance)...) if !canRollback { return &tsuruErrors.HTTP{Code: http.StatusForbidden, Message: permission.ErrUnauthorized.Error()} } var imageID string evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppDeploy, Owner: t, CustomData: opts, Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(instance)...), AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, contextsForApp(instance)...), Cancelable: true, }) if err != nil { return err } defer func() { evt.DoneCustomData(err, map[string]string{"image": imageID}) }() opts.Event = evt imageID, err = app.Deploy(opts) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) } return nil }
func permSchemeForDeploy(opts app.DeployOptions) *permission.PermissionScheme { switch opts.GetKind() { case app.DeployGit: return permission.PermAppDeployGit case app.DeployImage: return permission.PermAppDeployImage case app.DeployUpload: return permission.PermAppDeployUpload case app.DeployUploadBuild: return permission.PermAppDeployBuild case app.DeployArchiveURL: return permission.PermAppDeployArchiveUrl case app.DeployRollback: return permission.PermAppDeployRollback default: return permission.PermAppDeploy } }
// title: app deploy // path: /apps/{appname}/deploy // method: POST // consume: application/x-www-form-urlencoded // responses: // 200: OK // 400: Invalid data // 403: Forbidden // 404: Not found func deploy(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { var file multipart.File var fileSize int64 if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/") { file, _, err = r.FormFile("file") if err != nil { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: err.Error(), } } fileSize, err = file.Seek(0, os.SEEK_END) if err != nil { return errors.Wrap(err, "unable to find uploaded file size") } file.Seek(0, os.SEEK_SET) } archiveURL := r.FormValue("archive-url") image := r.FormValue("image") if image == "" && archiveURL == "" && file == nil { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: "you must specify either the archive-url, a image url or upload a file.", } } commit := r.FormValue("commit") w.Header().Set("Content-Type", "text") appName := r.URL.Query().Get(":appname") origin := r.FormValue("origin") if image != "" { origin = "image" } if origin != "" { if !app.ValidateOrigin(origin) { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid deployment origin", } } } var userName string if t.IsAppToken() { if t.GetAppName() != appName && t.GetAppName() != app.InternalAppName { return &tsuruErrors.HTTP{Code: http.StatusUnauthorized, Message: "invalid app token"} } userName = r.FormValue("user") } else { commit = "" userName = t.GetUserName() } instance, err := app.GetByName(appName) if err != nil { return &tsuruErrors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } var build bool buildString := r.FormValue("build") if buildString != "" { build, err = strconv.ParseBool(buildString) if err != nil { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: err.Error(), } } } message := r.FormValue("message") if commit != "" && message == "" { var messages []string messages, err = repository.Manager().CommitMessages(instance.Name, commit, 1) if err != nil { return err } if len(messages) > 0 { message = messages[0] } } if origin == "" && commit != "" { origin = "git" } opts := app.DeployOptions{ App: instance, Commit: commit, FileSize: fileSize, File: file, ArchiveURL: archiveURL, User: userName, Image: image, Origin: origin, Build: build, Message: message, } opts.GetKind() if t.GetAppName() != app.InternalAppName { canDeploy := permission.Check(t, permSchemeForDeploy(opts), contextsForApp(instance)...) if !canDeploy { return &tsuruErrors.HTTP{Code: http.StatusForbidden, Message: "User does not have permission to do this action in this app"} } } var imageID string evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppDeploy, RawOwner: event.Owner{Type: event.OwnerTypeUser, Name: userName}, CustomData: opts, Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(instance)...), AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, contextsForApp(instance)...), Cancelable: true, }) if err != nil { return err } defer func() { evt.DoneCustomData(err, map[string]string{"image": imageID}) }() opts.Event = evt writer := io.NewKeepAliveWriter(w, 30*time.Second, "please wait...") defer writer.Stop() opts.OutputStream = writer imageID, err = app.Deploy(opts) if err == nil { fmt.Fprintln(w, "\nOK") } return err }