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 &errors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf("App %s not found.", appName)} } image := r.PostFormValue("image") if image == "" { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "you cannot rollback without an image name", } } w.Header().Set("Content-Type", "application/json") keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} if !regexp.MustCompile(":v[0-9]+$").MatchString(image) { img, err := getImage(appName, image) //err is not handled because it is treated in funcion app.Deploy() if err == nil { image = img } } err = app.Deploy(app.DeployOptions{ App: instance, OutputStream: writer, Image: image, User: t.GetUserName(), }) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) } return nil }
func nodeContainerUpgrade(w http.ResponseWriter, r *http.Request, t auth.Token) error { name := r.URL.Query().Get(":name") poolName := r.FormValue("pool") if poolName == "" { if !permission.Check(t, permission.PermNodecontainerUpdateUpgrade) { return permission.ErrUnauthorized } } else { if !permission.Check(t, permission.PermNodecontainerUpdateUpgrade, permission.Context(permission.CtxPool, poolName)) { return permission.ErrUnauthorized } } config, err := nodecontainer.LoadNodeContainer("", name) if err != nil { return err } err = config.ResetImage() if err != nil { return err } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = nodecontainer.RecreateContainers(mainDockerProvisioner, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) } return nil }
func moveContainersHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error { params, err := unmarshal(r.Body) if err != nil { return err } from := params["from"] to := params["to"] if from == "" || to == "" { return fmt.Errorf("Invalid params: from: %s - to: %s", from, to) } permContexts, err := moveContainersPermissionContexts(from, to) if err != nil { return err } if !permission.Check(t, permission.PermNode, permContexts...) { return permission.ErrUnauthorized } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = mainDockerProvisioner.MoveContainers(from, to, writer) if err != nil { fmt.Fprintf(writer, "Error trying to move containers: %s\n", err.Error()) } else { fmt.Fprintf(writer, "Containers moved successfully!\n") } return nil }
func moveContainerHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error { params, err := unmarshal(r.Body) if err != nil { return err } contId := r.URL.Query().Get(":id") to := params["to"] if to == "" { return fmt.Errorf("Invalid params: id: %s - to: %s", contId, to) } cont, err := mainDockerProvisioner.GetContainer(contId) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } permContexts, err := moveContainersPermissionContexts(cont.HostAddr, to) if err != nil { return err } if !permission.Check(t, permission.PermNode, permContexts...) { return permission.ErrUnauthorized } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} _, err = mainDockerProvisioner.moveContainer(contId, to, writer) if err != nil { fmt.Fprintf(writer, "Error trying to move container: %s\n", err.Error()) } else { fmt.Fprintf(writer, "Containers moved successfully!\n") } return nil }
func rebalanceContainersHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error { var dry bool var params struct { Dry string MetadataFilter map[string]string AppFilter []string } err := json.NewDecoder(r.Body).Decode(¶ms) if err == nil { dry, _ = strconv.ParseBool(params.Dry) } var permContexts []permission.PermissionContext if pool, ok := params.MetadataFilter["pool"]; ok { permContexts = append(permContexts, permission.Context(permission.CtxPool, pool)) } if !permission.Check(t, permission.PermNode, permContexts...) { return permission.ErrUnauthorized } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} _, err = mainDockerProvisioner.rebalanceContainersByFilter(writer, params.AppFilter, params.MetadataFilter, dry) if err != nil { fmt.Fprintf(writer, "Error trying to rebalance containers: %s\n", err) } else { fmt.Fprintf(writer, "Containers successfully rebalanced!\n") } return nil }
// title: logs config set // path: /docker/logs // method: POST // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized func logsConfigSetHandler(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { err = r.ParseForm() if err != nil { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: fmt.Sprintf("unable to parse form values: %s", err), } } pool := r.FormValue("pool") restart, _ := strconv.ParseBool(r.FormValue("restart")) delete(r.Form, "pool") delete(r.Form, "restart") var conf container.DockerLogConfig dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(&conf, r.Form) if err != nil { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: fmt.Sprintf("unable to parse fields in docker log config: %s", err), } } var ctxs []permission.PermissionContext if pool != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, pool)) } hasPermission := permission.Check(t, permission.PermPoolUpdateLogs, ctxs...) if !hasPermission { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: pool}, Kind: permission.PermPoolUpdateLogs, Owner: t, CustomData: event.FormToCustomData(r.Form), DisableLock: true, Allowed: event.Allowed(permission.PermPoolReadEvents, ctxs...), }) if err != nil { return err } defer func() { evt.Done(err) }() err = conf.Save(pool) if err != nil { return err } w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} fmt.Fprintln(writer, "Log config successfully updated.") if restart { filter := &app.Filter{} if pool != "" { filter.Pools = []string{pool} } return tryRestartAppsByFilter(filter, writer) } return nil }
// title: remove app // path: /apps/{name} // method: DELETE // produce: application/x-json-stream // responses: // 200: App removed // 401: Unauthorized // 404: Not found func appDelete(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() a, err := getAppFromContext(r.URL.Query().Get(":app"), r) if err != nil { return err } canDelete := permission.Check(t, permission.PermAppDelete, contextsForApp(&a)..., ) if !canDelete { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(a.Name), Kind: permission.PermAppDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} w.Header().Set("Content-Type", "application/x-json-stream") return app.Delete(&a, writer) }
// title: remove units // path: /apps/{name}/units // method: DELETE // produce: application/x-json-stream // responses: // 200: Units removed // 400: Invalid data // 401: Unauthorized // 404: App not found func removeUnits(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { n, err := numberOfUnits(r) if err != nil { return err } processName := r.FormValue("process") appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateUnitRemove, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppUpdateUnitRemove, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} return a.RemoveUnits(n, processName, writer) }
func changePlan(w http.ResponseWriter, r *http.Request, t auth.Token) error { var plan app.Plan err := json.NewDecoder(r.Body).Decode(&plan) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "unable to parse request body", } } a, err := getAppFromContext(r.URL.Query().Get(":app"), r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdatePlan, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.ChangePlan(plan.Name, writer) if err == app.ErrPlanNotFound { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return err } return err }
func unsetEnv(w http.ResponseWriter, r *http.Request, t auth.Token) error { msg := "You must provide the list of environment variables, in JSON format" if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } var variables []string defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&variables) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } if len(variables) == 0 { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } appName := r.URL.Query().Get(":app") u, err := t.User() if err != nil { return err } rec.Log(u.Email, "unset-env", "app="+appName, fmt.Sprintf("envs=%s", variables)) app, err := getApp(appName, u, r) if err != nil { return err } w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = app.UnsetEnvs(variables, true, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } return nil }
func runCommand(w http.ResponseWriter, r *http.Request, t auth.Token) error { w.Header().Set("Content-Type", "text") msg := "You must provide the command to run" if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } c, err := ioutil.ReadAll(r.Body) if err != nil { return err } if len(c) < 1 { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") once := r.URL.Query().Get("once") rec.Log(u.Email, "run-command", "app="+appName, "command="+string(c)) app, err := getAppFromContext(appName, u, r) if err != nil { return err } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = app.Run(string(c), writer, once == "true") if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return err } return nil }
func unbindServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { instanceName, appName, serviceName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app"), r.URL.Query().Get(":service") noRestart, err := strconv.ParseBool(r.URL.Query().Get("noRestart")) if err != nil { return nil } u, err := t.User() if err != nil { return err } instance, a, err := getServiceInstance(serviceName, instanceName, appName, u) if err != nil { return err } rec.Log(u.Email, "unbind-app", "instance="+instanceName, "app="+appName) w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = instance.UnbindApp(a, !noRestart, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } fmt.Fprintf(writer, "\nInstance %q is not bound to the app %q anymore.\n", instanceName, appName) return nil }
func bindServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app") u, err := t.User() if err != nil { return err } instance, a, err := getServiceInstance(instanceName, appName, u) if err != nil { return err } rec.Log(u.Email, "bind-app", "instance="+instanceName, "app="+appName) w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = instance.BindApp(a, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } fmt.Fprintf(writer, "\nInstance %q is now bound to the app %q.\n", instanceName, appName) envs := a.InstanceEnv(instanceName) if len(envs) > 0 { fmt.Fprintf(writer, "The following environment variables are available for use in your app:\n\n") for k := range envs { fmt.Fprintf(writer, "- %s\n", k) } fmt.Fprintf(writer, "- %s\n", app.TsuruServicesEnvVar) } return nil }
func removeUnits(w http.ResponseWriter, r *http.Request, t auth.Token) error { n, err := numberOfUnits(r) if err != nil { return err } u, err := t.User() if err != nil { return err } processName := r.FormValue("process") appName := r.URL.Query().Get(":app") rec.Log(u.Email, "remove-units", "app="+appName, fmt.Sprintf("units=%d", n)) app, err := getAppFromContext(appName, u, r) if err != nil { return err } w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = app.RemoveUnits(uint(n), processName, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } return nil }
// 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 }
// title: autoscale run // path: /docker/autoscale/run // method: POST // produce: application/x-json-stream // responses: // 200: Ok // 401: Unauthorized func autoScaleRunHandler(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() if !permission.Check(t, permission.PermNodeAutoscaleUpdateRun) { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool}, Kind: permission.PermNodeAutoscaleUpdateRun, Owner: t, CustomData: event.FormToCustomData(r.Form), DisableLock: true, Allowed: event.Allowed(permission.PermPoolReadEvents), }) if err != nil { return err } defer func() { evt.Done(err) }() w.Header().Set("Content-Type", "application/x-json-stream") w.WriteHeader(http.StatusOK) keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{ Encoder: json.NewEncoder(keepAliveWriter), } autoScaleConfig := mainDockerProvisioner.initAutoScaleConfig() autoScaleConfig.writer = writer return autoScaleConfig.runOnce() }
func appDelete(w http.ResponseWriter, r *http.Request, t auth.Token) error { u, err := t.User() if err != nil { return err } a, err := getAppFromContext(r.URL.Query().Get(":app"), r) if err != nil { return err } canDelete := permission.Check(t, permission.PermAppDelete, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !canDelete { return permission.ErrUnauthorized } rec.Log(u.Email, "app-delete", "app="+a.Name) keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = app.Delete(&a, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) } return nil }
func changePlan(w http.ResponseWriter, r *http.Request, t auth.Token) error { var plan app.Plan err := json.NewDecoder(r.Body).Decode(&plan) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "unable to parse request body", } } user, err := t.User() if err != nil { return err } a, err := getAppFromContext(r.URL.Query().Get(":app"), user, r) if err != nil { return err } keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.ChangePlan(plan.Name, writer) if err == app.ErrPlanNotFound { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return err } return err }
func restart(w http.ResponseWriter, r *http.Request, t auth.Token) error { process := r.URL.Query().Get("process") w.Header().Set("Content-Type", "text") u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateRestart, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(u.Email, "restart", "app="+appName) keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.Restart(process, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return err } return nil }
func platformUpdate(w http.ResponseWriter, r *http.Request, t auth.Token) error { defer r.Body.Close() name := r.URL.Query().Get(":name") file, _, _ := r.FormFile("dockerfile_content") if file != nil { defer file.Close() } args := make(map[string]string) for key, values := range r.Form { args[key] = values[0] } canUpdatePlatform := permission.Check(t, permission.PermPlatformUpdate) if !canUpdatePlatform { return permission.ErrUnauthorized } w.Header().Set("Content-Type", "text") keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err := app.PlatformUpdate(provision.PlatformOptions{ Name: name, Args: args, Input: file, Output: writer, }) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) writer.Write([]byte("Failed to update platform!\n")) return nil } writer.Write([]byte("Platform successfully updated!\n")) return nil }
func removeServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { unbindAll := r.URL.Query().Get("unbindall") serviceName := r.URL.Query().Get(":service") instanceName := r.URL.Query().Get(":instance") permissionValue := serviceName + "/" + instanceName keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} serviceInstance, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return nil } allowed := permission.Check(t, permission.PermServiceInstanceDelete, append(permission.Contexts(permission.CtxTeam, serviceInstance.Teams), permission.Context(permission.CtxServiceInstance, permissionValue), )..., ) if !allowed { writer.Encode(io.SimpleJsonMessage{Error: permission.ErrUnauthorized.Error()}) return nil } rec.Log(t.GetUserName(), "remove-service-instance", serviceName, instanceName) if unbindAll == "true" { if len(serviceInstance.Apps) > 0 { for _, appName := range serviceInstance.Apps { _, app, instErr := getServiceInstance(serviceInstance.ServiceName, serviceInstance.Name, appName) if instErr != nil { writer.Encode(io.SimpleJsonMessage{Error: instErr.Error()}) return nil } fmt.Fprintf(writer, "Unbind app %q ...\n", app.GetName()) instErr = serviceInstance.UnbindApp(app, true, writer) if instErr != nil { writer.Encode(io.SimpleJsonMessage{Error: instErr.Error()}) return nil } fmt.Fprintf(writer, "\nInstance %q is not bound to the app %q anymore.\n", serviceInstance.Name, app.GetName()) } serviceInstance, err = getServiceInstanceOrError(serviceName, instanceName) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return nil } } } err = service.DeleteInstance(serviceInstance) if err != nil { var msg string if err == service.ErrServiceInstanceBound { msg = strings.Join(serviceInstance.Apps, ",") } writer.Encode(io.SimpleJsonMessage{Message: msg, Error: err.Error()}) return nil } writer.Write([]byte("service instance successfuly removed")) return nil }
// title: app update // path: /apps/{name} // method: PUT // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: App updated // 401: Unauthorized // 404: Not found func updateApp(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { updateData := app.App{ TeamOwner: r.FormValue("teamOwner"), Plan: app.Plan{Name: r.FormValue("plan")}, Pool: r.FormValue("pool"), Description: r.FormValue("description"), } appName := r.URL.Query().Get(":appname") a, err := getAppFromContext(appName, r) if err != nil { return err } var wantedPerms []*permission.PermissionScheme if updateData.Description != "" { wantedPerms = append(wantedPerms, permission.PermAppUpdateDescription) } if updateData.Plan.Name != "" { wantedPerms = append(wantedPerms, permission.PermAppUpdatePlan) } if updateData.Pool != "" { wantedPerms = append(wantedPerms, permission.PermAppUpdatePool) } if updateData.TeamOwner != "" { wantedPerms = append(wantedPerms, permission.PermAppUpdateTeamowner) } if len(wantedPerms) == 0 { msg := "Neither the description, plan, pool or team owner were set. You must define at least one." return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } for _, perm := range wantedPerms { allowed := permission.Check(t, perm, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } } evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppUpdate, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() w.Header().Set("Content-Type", "application/x-json-stream") writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.Update(updateData, writer) if err == app.ErrPlanNotFound { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } return err }
// title: set envs // path: /apps/{app}/env // method: POST // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: Envs updated // 400: Invalid data // 401: Unauthorized // 404: App not found func setEnv(w http.ResponseWriter, r *http.Request, t auth.Token) error { err := r.ParseForm() if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } var e Envs dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(&e, r.Form) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } if len(e.Envs) == 0 { msg := "You must provide the list of environment variables" return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } extra := fmt.Sprintf("private=%t", e.Private) appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateEnvSet, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } envs := map[string]string{} variables := []bind.EnvVar{} for _, v := range e.Envs { envs[v.Name] = v.Value variables = append(variables, bind.EnvVar{Name: v.Name, Value: v.Value, Public: !e.Private}) } rec.Log(u.Email, "set-env", "app="+appName, envs, extra) w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.SetEnvs( bind.SetEnvApp{ Envs: variables, PublicOnly: true, ShouldRestart: !e.NoRestart, }, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } return nil }
// title: remove node container // path: /docker/nodecontainers/{name} // method: DELETE // responses: // 200: Ok // 401: Unauthorized // 404: Not found func nodeContainerDelete(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() name := r.URL.Query().Get(":name") poolName := r.URL.Query().Get("pool") kill, _ := strconv.ParseBool(r.URL.Query().Get("kill")) var ctxs []permission.PermissionContext if poolName != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, poolName)) } if !permission.Check(t, permission.PermNodecontainerDelete, ctxs...) { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeNodeContainer, Value: name}, Kind: permission.PermNodecontainerDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermPoolReadEvents, ctxs...), }) if err != nil { return err } defer func() { evt.Done(err) }() err = nodecontainer.RemoveContainer(poolName, name) if err == nodecontainer.ErrNodeContainerNotFound { return &tsuruErrors.HTTP{ Code: http.StatusNotFound, Message: fmt.Sprintf("node container %q not found for pool %q", name, poolName), } } if err != nil || !kill { return err } provs, err := provision.Registry() if err != nil { return err } w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} var allErrors []string for _, prov := range provs { ncProv, ok := prov.(provision.NodeContainerProvisioner) if !ok { continue } err = ncProv.RemoveNodeContainer(name, poolName, writer) if err != nil { allErrors = append(allErrors, err.Error()) } } if len(allErrors) > 0 { return errors.Errorf("multiple errors removing node container: %s", strings.Join(allErrors, "; ")) } return nil }
// title: set envs // path: /apps/{app}/env // method: POST // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: Envs updated // 400: Invalid data // 401: Unauthorized // 404: App not found func setEnv(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { err = r.ParseForm() if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } var e Envs dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(&e, r.Form) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } if len(e.Envs) == 0 { msg := "You must provide the list of environment variables" return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateEnvSet, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppUpdateEnvSet, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() envs := map[string]string{} variables := []bind.EnvVar{} for _, v := range e.Envs { envs[v.Name] = v.Value variables = append(variables, bind.EnvVar{Name: v.Name, Value: v.Value, Public: !e.Private}) } w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} return a.SetEnvs( bind.SetEnvApp{ Envs: variables, PublicOnly: true, ShouldRestart: !e.NoRestart, }, writer, ) }
func bsEnvSetHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error { var requestConfig bs.Config err := json.NewDecoder(r.Body).Decode(&requestConfig) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: fmt.Sprintf("unable to parse body as json: %s", err), } } if len(requestConfig.Envs) > 0 && !permission.Check(t, permission.PermNodeBs) { return permission.ErrUnauthorized } for _, poolEnv := range requestConfig.Pools { hasPermission := permission.Check(t, permission.PermNodeBs, permission.Context(permission.CtxPool, poolEnv.Name)) if !hasPermission { return permission.ErrUnauthorized } } currentConfig, err := bs.LoadConfig(nil) if err != nil { if err != mgo.ErrNotFound { return err } currentConfig = &bs.Config{} } envMap := bs.EnvMap{} poolEnvMap := bs.PoolEnvMap{} err = currentConfig.UpdateEnvMaps(envMap, poolEnvMap) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: err.Error(), } } err = requestConfig.UpdateEnvMaps(envMap, poolEnvMap) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: err.Error(), } } err = bs.SaveEnvs(envMap, poolEnvMap) if err != nil { return err } keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = bs.RecreateContainers(mainDockerProvisioner, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) } return nil }
func deploy(app provision.App, commands []string, w io.Writer) (string, error) { writer := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "please wait...") imageId := getImage(app) actions := []*action.Action{&insertEmptyContainerInDB, &createContainer, &startContainer, &updateContainerInDB, &followLogsAndCommit} pipeline := action.NewPipeline(actions...) err := pipeline.Execute(app, imageId, commands, []string{}, writer) if err != nil { log.Errorf("error on execute deploy pipeline for app %s - %s", app.GetName(), err) return "", err } return pipeline.Result().(string), nil }
// title: bind service instance // path: /services/{service}/instances/{instance}/{app} // method: PUT // consume: application/x-www-form-urlencoded // produce: application/x-json-stream // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized // 404: App not found func bindServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { instanceName := r.URL.Query().Get(":instance") appName := r.URL.Query().Get(":app") serviceName := r.URL.Query().Get(":service") noRestart, _ := strconv.ParseBool(r.FormValue("noRestart")) instance, a, err := getServiceInstance(serviceName, instanceName, appName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceInstanceUpdateBind, append(permission.Contexts(permission.CtxTeam, instance.Teams), permission.Context(permission.CtxServiceInstance, instance.Name), )..., ) if !allowed { return permission.ErrUnauthorized } allowed = permission.Check(t, permission.PermAppUpdateBind, contextsForApp(a)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppUpdateBind, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() w.Header().Set("Content-Type", "application/x-json-stream") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = instance.BindApp(a, !noRestart, writer) if err != nil { return err } fmt.Fprintf(writer, "\nInstance %q is now bound to the app %q.\n", instanceName, appName) envs := a.InstanceEnv(instanceName) if len(envs) > 0 { fmt.Fprintf(writer, "The following environment variables are available for use in your app:\n\n") for k := range envs { fmt.Fprintf(writer, "- %s\n", k) } fmt.Fprintf(writer, "- %s\n", app.TsuruServicesEnvVar) } return nil }
func removeServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { u, err := t.User() if err != nil { return err } unbindAll := r.URL.Query().Get("unbindall") serviceName := r.URL.Query().Get(":service") instanceName := r.URL.Query().Get(":instance") rec.Log(u.Email, "remove-service-instance", instanceName) si, err := getServiceInstanceOrError(serviceName, instanceName, u) keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return nil } if unbindAll == "true" { if len(si.Apps) > 0 { for _, appName := range si.Apps { _, app, instErr := getServiceInstance(si.ServiceName, si.Name, appName, u) if instErr != nil { writer.Encode(io.SimpleJsonMessage{Error: instErr.Error()}) return nil } fmt.Fprintf(writer, "Unbind app %q ...\n", app.GetName()) instErr = si.UnbindApp(app, writer) if instErr != nil { writer.Encode(io.SimpleJsonMessage{Error: instErr.Error()}) return nil } fmt.Fprintf(writer, "\nInstance %q is not bound to the app %q anymore.\n", si.Name, app.GetName()) } si, err = getServiceInstanceOrError(serviceName, instanceName, u) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) return nil } } } err = service.DeleteInstance(si) if err != nil { var msg string if err == service.ErrServiceInstanceBound { msg = strings.Join(si.Apps, ",") } writer.Encode(io.SimpleJsonMessage{Message: msg, Error: err.Error()}) return nil } writer.Write([]byte("service instance successfuly removed")) return nil }
func setEnv(w http.ResponseWriter, r *http.Request, t auth.Token) error { msg := "You must provide the environment variables in a JSON object" if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } var variables map[string]string err := json.NewDecoder(r.Body).Decode(&variables) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } noRestart, _ := strconv.ParseBool(r.URL.Query().Get("noRestart")) isPrivateEnv, _ := strconv.ParseBool(r.URL.Query().Get("private")) extra := fmt.Sprintf("private=%t", isPrivateEnv) appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateEnvSet, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(u.Email, "set-env", "app="+appName, variables, extra) envs := make([]bind.EnvVar, 0, len(variables)) for k, v := range variables { envs = append(envs, bind.EnvVar{Name: k, Value: v, Public: !isPrivateEnv}) } w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} err = a.SetEnvs( bind.SetEnvApp{ Envs: envs, PublicOnly: true, ShouldRestart: !noRestart, }, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } return nil }