func serviceInstanceStatus(w http.ResponseWriter, r *http.Request, t auth.Token) error { instanceName := r.URL.Query().Get(":instance") serviceName := r.URL.Query().Get(":service") serviceInstance, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { return err } permissionValue := serviceName + "/" + instanceName allowed := permission.Check(t, permission.PermServiceInstanceReadStatus, append(permission.Contexts(permission.CtxTeam, serviceInstance.Teams), permission.Context(permission.CtxServiceInstance, permissionValue), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "service-instance-status", serviceName, instanceName) var b string if b, err = serviceInstance.Status(); err != nil { msg := fmt.Sprintf("Could not retrieve status of service instance, error: %s", err) return &errors.HTTP{Code: http.StatusInternalServerError, Message: msg} } b = fmt.Sprintf(`Service instance "%s" is %s`, instanceName, b) n, err := w.Write([]byte(b)) if n != len(b) { return &errors.HTTP{Code: http.StatusInternalServerError, Message: "Failed to write response body"} } return nil }
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 serviceDelete(w http.ResponseWriter, r *http.Request, t auth.Token) error { s, err := getService(r.URL.Query().Get(":name")) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceDelete, append(permission.Contexts(permission.CtxTeam, s.OwnerTeams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "delete-service", r.URL.Query().Get(":name")) instances, err := service.GetServiceInstancesByServices([]service.Service{s}) if err != nil { return err } if len(instances) > 0 { msg := "This service cannot be removed because it has instances.\nPlease remove these instances before removing the service." return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } err = s.Delete() if err != nil { return err } w.WriteHeader(http.StatusNoContent) return nil }
// title: regenerate token // path: /users/api-key // method: POST // produce: application/json // responses: // 200: OK // 401: Unauthorized // 404: User not found func regenerateAPIToken(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() email := r.URL.Query().Get("user") if email == "" { email = t.GetUserName() } allowed := permission.Check(t, permission.PermUserUpdateToken, permission.Context(permission.CtxUser, email), ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: userTarget(email), Kind: permission.PermUserUpdateToken, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermUserReadEvents, permission.Context(permission.CtxUser, email)), }) if err != nil { return err } defer func() { evt.Done(err) }() u, err := auth.GetUserByEmail(email) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } apiKey, err := u.RegenerateAPIKey() if err != nil { return err } w.Header().Add("Content-Type", "application/json") return json.NewEncoder(w).Encode(apiKey) }
func servicePlans(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":name") s, err := getService(serviceName) if err != nil { return err } if s.IsRestricted { allowed := permission.Check(t, permission.PermServiceReadPlans, append(permission.Contexts(permission.CtxTeam, s.Teams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } } rec.Log(t.GetUserName(), "service-plans", serviceName) plans, err := service.GetPlansByServiceName(serviceName) if err != nil { return err } b, err := json.Marshal(plans) if err != nil { return nil } w.Write(b) return nil }
func getTeam(w http.ResponseWriter, r *http.Request, t auth.Token) error { teamName := r.URL.Query().Get(":name") _, err := auth.GetTeam(teamName) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: "Team not found"} } permsForTeam := permission.PermissionRegistry.PermissionsWithContextType(permission.CtxTeam) var permissions []string for _, p := range permsForTeam { if permission.Check(t, p, permission.Context(permission.CtxTeam, teamName)) { permissions = append(permissions, p.FullName()) } } if len(permissions) == 0 { return &errors.HTTP{Code: http.StatusNotFound, Message: "Team not found"} } rec.Log(t.GetUserName(), "get-team", teamName) result := map[string]interface{}{ "name": teamName, "permissions": permissions, } w.Header().Set("Content-Type", "application/json") b, err := json.Marshal(result) if err != nil { return err } n, err := w.Write(b) if err != nil { return err } if n != len(b) { return &errors.HTTP{Code: http.StatusInternalServerError, Message: "Failed to write response body."} } return nil }
func grantServiceAccess(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":service") s, err := getService(serviceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdateGrantAccess, append(permission.Contexts(permission.CtxTeam, s.OwnerTeams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } teamName := r.URL.Query().Get(":team") team, err := auth.GetTeam(teamName) if err != nil { if err == auth.ErrTeamNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: "Team not found"} } return err } rec.Log(t.GetUserName(), "grant-service-access", "service="+serviceName, "team="+teamName) err = s.GrantAccess(team) if err != nil { return &errors.HTTP{Code: http.StatusConflict, Message: err.Error()} } return s.Update() }
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") writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(w)} 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 revokeServiceAccess(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":service") s, err := getService(serviceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdateRevokeAccess, append(permission.Contexts(permission.CtxTeam, s.OwnerTeams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } teamName := r.URL.Query().Get(":team") team, err := auth.GetTeam(teamName) if err != nil { if err == auth.ErrTeamNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: "Team not found"} } return err } if len(s.Teams) < 2 { msg := "You can not revoke the access from this team, because it is the unique team with access to this service, and a service can not be orphaned" return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } rec.Log(t.GetUserName(), "revoke-service-access", "service="+serviceName, "team="+teamName) err = s.RevokeAccess(team) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } return s.Update() }
func swap(w http.ResponseWriter, r *http.Request, t auth.Token) error { u, err := t.User() if err != nil { return err } app1Name := r.URL.Query().Get("app1") app2Name := r.URL.Query().Get("app2") locked1, err := app.AcquireApplicationLock(app1Name, t.GetUserName(), "/swap") if err != nil { return err } defer app.ReleaseApplicationLock(app1Name) locked2, err := app.AcquireApplicationLock(app2Name, t.GetUserName(), "/swap") if err != nil { return err } defer app.ReleaseApplicationLock(app2Name) app1, err := getApp(app1Name, u) if err != nil { return err } if !locked1 { return &errors.HTTP{Code: http.StatusConflict, Message: fmt.Sprintf("%s: %s", app1.Name, &app1.Lock)} } app2, err := getApp(app2Name, u) if err != nil { return err } if !locked2 { return &errors.HTTP{Code: http.StatusConflict, Message: fmt.Sprintf("%s: %s", app2.Name, &app2.Lock)} } rec.Log(u.Email, "swap", app1Name, app2Name) return app.Swap(&app1, &app2) }
func serviceInstanceInfo(w http.ResponseWriter, r *http.Request, t auth.Token) error { instanceName := r.URL.Query().Get(":instance") serviceName := r.URL.Query().Get(":service") serviceInstance, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { return err } permissionValue := serviceName + "/" + instanceName allowed := permission.Check(t, permission.PermServiceInstanceRead, append(permission.Contexts(permission.CtxTeam, serviceInstance.Teams), permission.Context(permission.CtxServiceInstance, permissionValue), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "service-instance-info", serviceName, instanceName) info, err := serviceInstance.Info() if err != nil { return err } sInfo := ServiceInstanceInfo{ Apps: serviceInstance.Apps, Teams: serviceInstance.Teams, TeamOwner: serviceInstance.TeamOwner, Description: serviceInstance.Description, CustomInfo: info, } b, err := json.Marshal(sInfo) if err != nil { return nil } w.Write(b) 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 }
func changePassword(w http.ResponseWriter, r *http.Request, t auth.Token) error { managed, ok := app.AuthScheme.(auth.ManagedScheme) if !ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: nonManagedSchemeMsg} } var body map[string]string err := json.NewDecoder(r.Body).Decode(&body) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid JSON.", } } if body["old"] == "" || body["new"] == "" { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "Both the old and the new passwords are required.", } } err = managed.ChangePassword(t, body["old"], body["new"]) if err != nil { return handleAuthError(err) } rec.Log(t.GetUserName(), "change-password") return nil }
func changePassword(w http.ResponseWriter, r *http.Request, t auth.Token) error { managed, ok := app.AuthScheme.(auth.ManagedScheme) if !ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: nonManagedSchemeMsg} } oldPassword := r.FormValue("old") newPassword := r.FormValue("new") confirmPassword := r.FormValue("confirm") if oldPassword == "" || newPassword == "" { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "Both the old and the new passwords are required.", } } if newPassword != confirmPassword { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "New password and password confirmation didn't match.", } } err := managed.ChangePassword(t, oldPassword, newPassword) if err != nil { return handleAuthError(err) } rec.Log(t.GetUserName(), "change-password") return nil }
// title: event cancel // path: /events/{uuid}/cancel // method: POST // produce: application/json // responses: // 200: OK // 400: Invalid uuid or empty reason // 404: Not found func eventCancel(w http.ResponseWriter, r *http.Request, t auth.Token) error { uuid := r.URL.Query().Get(":uuid") if !bson.IsObjectIdHex(uuid) { msg := fmt.Sprintf("uuid parameter is not ObjectId: %s", uuid) return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } objID := bson.ObjectIdHex(uuid) e, err := event.GetByID(objID) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } reason := r.FormValue("reason") if reason == "" { return &errors.HTTP{Code: http.StatusBadRequest, Message: "reason is mandatory"} } scheme, err := permission.SafeGet(e.AllowedCancel.Scheme) if err != nil { return err } allowed := permission.Check(t, scheme, e.AllowedCancel.Contexts...) if !allowed { return permission.ErrUnauthorized } err = e.TryCancel(reason, t.GetUserName()) if err != nil { if err == event.ErrNotCancelable { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } return err } w.WriteHeader(http.StatusNoContent) return nil }
// title: service update // path: /services/{name} // method: PUT // consume: application/x-www-form-urlencoded // responses: // 200: Service updated // 400: Invalid data // 401: Unauthorized // 403: Forbidden (team is not the owner) // 404: Service not found func serviceUpdate(w http.ResponseWriter, r *http.Request, t auth.Token) error { d := service.Service{ Username: r.FormValue("username"), Endpoint: map[string]string{"production": r.FormValue("endpoint")}, Password: r.FormValue("password"), Name: r.URL.Query().Get(":name"), } err := serviceValidate(d) if err != nil { return err } s, err := getService(d.Name) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdate, append(permission.Contexts(permission.CtxTeam, s.OwnerTeams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "update-service", d.Name, d.Endpoint["production"]) s.Endpoint = d.Endpoint s.Password = d.Password s.Username = d.Username if err = s.Update(); err != nil { return err } 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 }
func serviceCreate(w http.ResponseWriter, r *http.Request, t auth.Token) error { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { return err } var input serviceYaml err = yaml.Unmarshal(body, &input) if err != nil { return err } if input.Team == "" { input.Team, err = permission.TeamForPermission(t, permission.PermServiceCreate) if err == permission.ErrTooManyTeams { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "You must provide a team responsible for this service in the manifest file.", } } if err != nil { return err } } err = input.validate() if err != nil { return err } allowed := permission.Check(t, permission.PermServiceCreate, permission.Context(permission.CtxTeam, input.Team), ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "create-service", input.Id, input.Endpoint) s := service.Service{ Name: input.Id, Username: input.Username, Endpoint: input.Endpoint, Password: input.Password, OwnerTeams: []string{input.Team}, } err = s.Create() if err != nil { httpError := http.StatusInternalServerError if err == service.ErrServiceAlreadyExists { httpError = http.StatusConflict } return &errors.HTTP{Code: httpError, Message: err.Error()} } fmt.Fprint(w, "success") return nil }
func serviceInfo(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":name") _, err := getService(serviceName) if err != nil { return err } rec.Log(t.GetUserName(), "service-info", serviceName) instances, err := readableInstances(t, "", serviceName) if err != nil { return err } return json.NewEncoder(w).Encode(instances) }
func teamList(w http.ResponseWriter, r *http.Request, t auth.Token) error { rec.Log(t.GetUserName(), "list-teams") permsForTeam := permission.PermissionRegistry.PermissionsWithContextType(permission.CtxTeam) teams, err := auth.ListTeams() if err != nil { return err } teamsMap := map[string][]string{} perms, err := t.Permissions() if err != nil { return err } for _, team := range teams { teamCtx := permission.Context(permission.CtxTeam, team.Name) var parent *permission.PermissionScheme for _, p := range permsForTeam { if parent != nil && parent.IsParent(p) { continue } if permission.CheckFromPermList(perms, p, teamCtx) { parent = p teamsMap[team.Name] = append(teamsMap[team.Name], p.FullName()) } } } if len(teamsMap) == 0 { w.WriteHeader(http.StatusNoContent) return nil } var result []map[string]interface{} for name, permissions := range teamsMap { result = append(result, map[string]interface{}{ "name": name, "permissions": permissions, }) } w.Header().Set("Content-Type", "application/json") b, err := json.Marshal(result) if err != nil { return err } n, err := w.Write(b) if err != nil { return err } if n != len(b) { return &errors.HTTP{Code: http.StatusInternalServerError, Message: "Failed to write response body."} } return nil }
// title: service list // path: /services // method: GET // produce: application/json // responses: // 200: List services // 204: No content // 401: Unauthorized func serviceList(w http.ResponseWriter, r *http.Request, t auth.Token) error { rec.Log(t.GetUserName(), "list-services") teams := []string{} serviceNames := []string{} contexts := permission.ContextsForPermission(t, permission.PermServiceRead) for _, c := range contexts { if c.CtxType == permission.CtxGlobal { teams = nil serviceNames = nil break } switch c.CtxType { case permission.CtxService: serviceNames = append(serviceNames, c.Value) case permission.CtxTeam: teams = append(teams, c.Value) } } services, err := service.GetServicesByOwnerTeamsAndServices(teams, serviceNames) if err != nil { return err } sInstances, err := service.GetServiceInstancesByServices(services) if err != nil { return err } results := make([]service.ServiceModel, len(services)) for i, s := range services { results[i].Service = s.Name for _, si := range sInstances { if si.ServiceName == s.Name { results[i].Instances = append(results[i].Instances, si.Name) } } } if len(results) == 0 { w.WriteHeader(http.StatusNoContent) return nil } b, err := json.Marshal(results) if err != nil { return &errors.HTTP{Code: http.StatusInternalServerError, Message: err.Error()} } n, err := w.Write(b) if n != len(b) { return &errors.HTTP{Code: http.StatusInternalServerError, Message: "Failed to write response body"} } w.Header().Set("Content-Type", "application/json") return err }
func bindServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) 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, 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(t.GetUserName(), "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, !noRestart, 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 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", } } origin := r.URL.Query().Get("origin") if origin != "" { if !app.ValidateOrigin(origin) { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid deployment origin", } } } w.Header().Set("Content-Type", "application/json") keepAliveWriter := io.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &io.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} canRollback := permission.Check(t, permission.PermAppDeployRollback, append(permission.Contexts(permission.CtxTeam, instance.Teams), permission.Context(permission.CtxApp, instance.Name), permission.Context(permission.CtxPool, instance.Pool), )..., ) if !canRollback { return &errors.HTTP{Code: http.StatusForbidden, Message: permission.ErrUnauthorized.Error()} } err = app.Rollback(app.DeployOptions{ App: instance, OutputStream: writer, Image: image, User: t.GetUserName(), Origin: origin, }) if err != nil { writer.Encode(io.SimpleJsonMessage{Error: err.Error()}) } return nil }
// title: service instance list // path: /services/instances // method: GET // produce: application/json // responses: // 200: List services instances // 204: No content // 401: Unauthorized func serviceInstances(w http.ResponseWriter, r *http.Request, t auth.Token) error { appName := r.URL.Query().Get("app") rec.Log(t.GetUserName(), "list-service-instances", "app="+appName) instances, err := readableInstances(t, appName, "") if err != nil { return err } services, err := readableServices(t) if err != nil { return err } servicesMap := map[string]*service.ServiceModel{} for _, s := range services { if _, in := servicesMap[s.Name]; !in { servicesMap[s.Name] = &service.ServiceModel{ Service: s.Name, Instances: []string{}, } } } for _, instance := range instances { entry := servicesMap[instance.ServiceName] if entry == nil { continue } entry.Instances = append(entry.Instances, instance.Name) entry.Plans = append(entry.Plans, instance.PlanName) } result := []service.ServiceModel{} for _, entry := range servicesMap { result = append(result, *entry) } if len(result) == 0 { w.WriteHeader(http.StatusNoContent) return nil } body, err := json.Marshal(result) if err != nil { return err } n, err := w.Write(body) if n != len(body) { return &errors.HTTP{Code: http.StatusInternalServerError, Message: "Failed to write the response body."} } w.Header().Set("Content-Type", "application/json") return err }
// title: service create // path: /services // method: POST // consume: application/x-www-form-urlencoded // responses: // 201: Service created // 400: Invalid data // 401: Unauthorized // 409: Service already exists func serviceCreate(w http.ResponseWriter, r *http.Request, t auth.Token) error { s := service.Service{ Name: r.FormValue("id"), Username: r.FormValue("username"), Endpoint: map[string]string{"production": r.FormValue("endpoint")}, Password: r.FormValue("password"), } team := r.FormValue("team") if team == "" { var err error team, err = permission.TeamForPermission(t, permission.PermServiceCreate) if err == permission.ErrTooManyTeams { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "You must provide a team responsible for this service in the manifest file.", } } if err != nil { return err } } s.OwnerTeams = []string{team} err := serviceValidate(s) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceCreate, permission.Context(permission.CtxTeam, s.OwnerTeams[0]), ) if !allowed { return permission.ErrUnauthorized } rec.Log(t.GetUserName(), "create-service", s.Name, s.Endpoint["production"]) err = s.Create() if err != nil { httpError := http.StatusInternalServerError if err == service.ErrServiceAlreadyExists { httpError = http.StatusConflict } return &errors.HTTP{Code: httpError, Message: err.Error()} } w.WriteHeader(http.StatusCreated) fmt.Fprint(w, "success") return nil }
func serviceInfo(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":name") _, err := getService(serviceName) if err != nil { return err } rec.Log(t.GetUserName(), "service-info", serviceName) instances, err := readableInstances(t, "", serviceName) if err != nil { return err } b, err := json.Marshal(instances) if err != nil { return nil } w.Write(b) return nil }
// title: change service documentation // path: /services/{name}/doc // consume: application/x-www-form-urlencoded // method: PUT // responses: // 200: Documentation updated // 401: Unauthorized // 403: Forbidden (team is not the owner or service with instances) func serviceAddDoc(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":name") s, err := getService(serviceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdateDoc, append(permission.Contexts(permission.CtxTeam, s.OwnerTeams), permission.Context(permission.CtxService, s.Name), )..., ) if !allowed { return permission.ErrUnauthorized } s.Doc = r.FormValue("doc") rec.Log(t.GetUserName(), "service-add-doc", serviceName, s.Doc) return s.Update() }
func swap(w http.ResponseWriter, r *http.Request, t auth.Token) error { u, err := t.User() if err != nil { return err } app1Name := r.URL.Query().Get("app1") app2Name := r.URL.Query().Get("app2") forceSwap := r.URL.Query().Get("force") if forceSwap == "" { forceSwap = "false" } locked1, err := app.AcquireApplicationLock(app1Name, t.GetUserName(), "/swap") if err != nil { return err } defer app.ReleaseApplicationLock(app1Name) locked2, err := app.AcquireApplicationLock(app2Name, t.GetUserName(), "/swap") if err != nil { return err } defer app.ReleaseApplicationLock(app2Name) app1, err := getApp(app1Name, u) if err != nil { return err } if !locked1 { return &errors.HTTP{Code: http.StatusConflict, Message: fmt.Sprintf("%s: %s", app1.Name, &app1.Lock)} } app2, err := getApp(app2Name, u) if err != nil { return err } if !locked2 { return &errors.HTTP{Code: http.StatusConflict, Message: fmt.Sprintf("%s: %s", app2.Name, &app2.Lock)} } // compare apps by platform type and number of units if forceSwap == "false" && ((len(app1.Units()) != len(app2.Units())) || (app1.Platform != app2.Platform)) { return app.ErrAppNotEqual } rec.Log(u.Email, "swap", app1Name, app2Name) return app.Swap(&app1, &app2) }
// title: add key // path: /users/keys // method: POST // consume: application/x-www-form-urlencoded // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized // 409: Key already exists func addKeyToUser(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { key := repository.Key{ Body: r.FormValue("key"), Name: r.FormValue("name"), } var force bool if r.FormValue("force") == "true" { force = true } allowed := permission.Check(t, permission.PermUserUpdateKeyAdd, permission.Context(permission.CtxUser, t.GetUserName()), ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: userTarget(t.GetUserName()), Kind: permission.PermUserUpdateKeyAdd, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermUserReadEvents, permission.Context(permission.CtxUser, t.GetUserName())), }) if err != nil { return err } defer func() { evt.Done(err) }() if key.Body == "" { return &errors.HTTP{Code: http.StatusBadRequest, Message: "Missing key content"} } u, err := t.User() if err != nil { return err } err = u.AddKey(key, force) if err == auth.ErrKeyDisabled || err == repository.ErrUserNotFound { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } if err == repository.ErrKeyAlreadyExists { return &errors.HTTP{Code: http.StatusConflict, Message: err.Error()} } return err }
func serviceInstanceRevokeTeam(w http.ResponseWriter, r *http.Request, t auth.Token) error { instanceName := r.URL.Query().Get(":instance") serviceName := r.URL.Query().Get(":service") serviceInstance, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { return err } permissionValue := serviceName + "/" + instanceName allowed := permission.Check(t, permission.PermServiceInstanceUpdateRevoke, append(permission.Contexts(permission.CtxTeam, serviceInstance.Teams), permission.Context(permission.CtxServiceInstance, permissionValue), )..., ) if !allowed { return permission.ErrUnauthorized } teamName := r.URL.Query().Get(":team") rec.Log(t.GetUserName(), "service-revoke-team", serviceName, instanceName, teamName) return serviceInstance.Revoke(teamName) }