// title: revoke access to service instance // path: /services/{service}/instances/permission/{instance}/{team} // method: DELETE // responses: // 200: Access revoked // 401: Unauthorized // 404: Service instance not found func serviceInstanceRevokeTeam(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() instanceName := r.URL.Query().Get(":instance") serviceName := r.URL.Query().Get(":service") serviceInstance, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceInstanceUpdateRevoke, contextsForServiceInstance(serviceInstance, serviceName)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: serviceInstanceTarget(serviceName, instanceName), Kind: permission.PermServiceInstanceUpdateRevoke, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermServiceInstanceReadEvents, contextsForServiceInstance(serviceInstance, serviceName)...), }) if err != nil { return err } defer func() { evt.Done(err) }() teamName := r.URL.Query().Get(":team") return serviceInstance.Revoke(teamName) }
// title: remove plan // path: /plans/{name} // method: DELETE // responses: // 200: Plan removed // 401: Unauthorized // 404: Plan not found func removePlan(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() allowed := permission.Check(t, permission.PermPlanDelete) if !allowed { return permission.ErrUnauthorized } planName := r.URL.Query().Get(":planname") evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePlan, Value: planName}, Kind: permission.PermPlanDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermPlanReadEvents), }) if err != nil { return err } defer func() { evt.Done(err) }() err = app.PlanRemove(planName) if err == app.ErrPlanNotFound { return &errors.HTTP{ Code: http.StatusNotFound, Message: err.Error(), } } return err }
// title: service instance update // path: /services/{service}/instances/{instance} // method: PUT // consume: application/x-www-form-urlencoded // responses: // 200: Service instance updated // 400: Invalid data // 401: Unauthorized // 404: Service instance not found func updateServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { serviceName := r.URL.Query().Get(":service") instanceName := r.URL.Query().Get(":instance") description := r.FormValue("description") if description == "" { return &tsuruErrors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid value for description", } } si, err := getServiceInstanceOrError(serviceName, instanceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceInstanceUpdateDescription, contextsForServiceInstance(si, serviceName)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: serviceInstanceTarget(serviceName, instanceName), Kind: permission.PermServiceInstanceUpdateDescription, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermServiceInstanceReadEvents, contextsForServiceInstance(si, serviceName)...), }) if err != nil { return err } defer func() { evt.Done(err) }() si.Description = description return service.UpdateService(si) }
// 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) }
// title: update application quota // path: /apps/{appname}/quota // method: PUT // consume: application/x-www-form-urlencoded // responses: // 200: Quota updated // 400: Invalid data // 401: Unauthorized // 404: Application not found func changeAppQuota(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() appName := r.URL.Query().Get(":appname") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppAdminQuota, contextsForApp(&a)...) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeApp, Value: appName}, Kind: permission.PermAppAdminQuota, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() limit, err := strconv.Atoi(r.FormValue("limit")) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "Invalid limit", } } return app.ChangeQuota(&a, limit) }
// title: service proxy // path: /services/proxy/service/{service} // method: "*" // responses: // 401: Unauthorized // 404: Service not found func serviceProxy(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { parseFormPreserveBody(r) serviceName := r.URL.Query().Get(":service") s, err := getService(serviceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdateProxy, contextsForServiceProvision(&s)..., ) if !allowed { return permission.ErrUnauthorized } if r.Method != httpMethodGet && r.Method != httpMethodHead { evt, err := event.New(&event.Opts{ Target: serviceTarget(s.Name), Kind: permission.PermServiceUpdateProxy, Owner: t, CustomData: append(event.FormToCustomData(r.Form), map[string]interface{}{ "name": "method", "value": r.Method, }), Allowed: event.Allowed(permission.PermServiceReadEvents, contextsForServiceProvision(&s)...), }) if err != nil { return err } defer func() { evt.Done(err) }() } path := r.URL.Query().Get("callback") return service.Proxy(&s, path, w, r) }
// title: remove team from pool // path: /pools/{name}/team // method: DELETE // responses: // 200: Pool updated // 401: Unauthorized // 400: Invalid data // 404: Pool not found func removeTeamToPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() allowed := permission.Check(t, permission.PermPoolUpdateTeamRemove) if !allowed { return permission.ErrUnauthorized } poolName := r.URL.Query().Get(":name") evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: poolName}, Kind: permission.PermPoolUpdateTeamRemove, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermPoolReadEvents, permission.Context(permission.CtxPool, poolName)), }) if err != nil { return err } defer func() { evt.Done(err) }() if teams, ok := r.URL.Query()["team"]; ok { err := provision.RemoveTeamsFromPool(poolName, teams) if err == provision.ErrPoolNotFound { return &terrors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } return err } return &terrors.HTTP{ Code: http.StatusBadRequest, Message: "You must provide the team", } }
// title: autoscale set rule // path: /docker/autoscale/rules // method: POST // consume: application/x-www-form-urlencoded // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized func autoScaleSetRule(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { allowedSetRule := permission.Check(t, permission.PermNodeAutoscaleUpdate) if !allowedSetRule { return permission.ErrUnauthorized } err = r.ParseForm() if err != nil { return &tsuruErrors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } var rule autoScaleRule dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(&rule, r.Form) if err != nil { return &tsuruErrors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } var ctxs []permission.PermissionContext if rule.MetadataFilter != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, rule.MetadataFilter)) } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: rule.MetadataFilter}, Kind: permission.PermNodeAutoscaleUpdate, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermPoolReadEvents, ctxs...), }) if err != nil { return err } defer func() { evt.Done(err) }() return rule.update() }
// title: remove team // path: /teams/{name} // method: DELETE // responses: // 200: Team removed // 401: Unauthorized // 403: Forbidden // 404: Not found func removeTeam(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() name := r.URL.Query().Get(":name") allowed := permission.Check(t, permission.PermTeamDelete, permission.Context(permission.CtxTeam, name), ) if !allowed { return &errors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf(`Team "%s" not found.`, name)} } evt, err := event.New(&event.Opts{ Target: teamTarget(name), Kind: permission.PermTeamDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermTeamReadEvents, permission.Context(permission.CtxTeam, name)), }) if err != nil { return err } defer func() { evt.Done(err) }() err = auth.RemoveTeam(name) if err != nil { if _, ok := err.(*auth.ErrTeamStillUsed); ok { msg := fmt.Sprintf("This team cannot be removed because there are still references to it:\n%s", err) return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } if err == auth.ErrTeamNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf(`Team "%s" not found.`, name)} } return err } return nil }
// title: remove node healing // path: /healing/node // method: DELETE // produce: application/json // responses: // 200: Ok // 401: Unauthorized func nodeHealingDelete(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() poolName := r.URL.Query().Get("pool") var ctxs []permission.PermissionContext if poolName != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, poolName)) } if !permission.Check(t, permission.PermHealingDelete, ctxs...) { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: poolName}, Kind: permission.PermHealingDelete, 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) }() if len(r.URL.Query()["name"]) == 0 { return healer.RemoveConfig(poolName, "") } for _, v := range r.URL.Query()["name"] { err := healer.RemoveConfig(poolName, v) if err != nil { return err } } return nil }
// title: machine destroy // path: /iaas/machines/{machine_id} // method: DELETE // responses: // 200: OK // 400: Invalid data // 401: Unauthorized // 404: Not found func machineDestroy(w http.ResponseWriter, r *http.Request, token auth.Token) (err error) { r.ParseForm() machineID := r.URL.Query().Get(":machine_id") if machineID == "" { return &errors.HTTP{Code: http.StatusBadRequest, Message: "machine id is required"} } m, err := iaas.FindMachineById(machineID) if err != nil { if err == mgo.ErrNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: "machine not found"} } return err } iaasCtx := permission.Context(permission.CtxIaaS, m.Iaas) allowed := permission.Check(token, permission.PermMachineDelete, iaasCtx) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeIaas, Value: m.Iaas}, Kind: permission.PermMachineDelete, Owner: token, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermMachineReadEvents, iaasCtx), }) if err != nil { return err } defer func() { evt.Done(err) }() return m.Destroy() }
// title: reset password // path: /users/{email}/password // method: POST // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized // 403: Forbidden // 404: Not found func resetPassword(w http.ResponseWriter, r *http.Request) (err error) { managed, ok := app.AuthScheme.(auth.ManagedScheme) if !ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: nonManagedSchemeMsg} } r.ParseForm() email := r.URL.Query().Get(":email") token := r.FormValue("token") evt, err := event.New(&event.Opts{ Target: userTarget(email), Kind: permission.PermUserUpdateReset, RawOwner: event.Owner{Type: event.OwnerTypeUser, Name: email}, 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 { if err == auth.ErrUserNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } else if e, ok := err.(*errors.ValidationError); ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: e.Error()} } return err } if token == "" { return managed.StartPasswordReset(u) } return managed.ResetPassword(u, token) }
// title: template create // path: /iaas/templates // method: POST // consume: application/x-www-form-urlencoded // responses: // 201: Template created // 400: Invalid data // 401: Unauthorized func templateCreate(w http.ResponseWriter, r *http.Request, token auth.Token) (err error) { err = r.ParseForm() if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } var paramTemplate iaas.Template dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(¶mTemplate, r.Form) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } iaasCtx := permission.Context(permission.CtxIaaS, paramTemplate.IaaSName) allowed := permission.Check(token, permission.PermMachineTemplateCreate, iaasCtx) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeIaas, Value: paramTemplate.IaaSName}, Kind: permission.PermMachineTemplateCreate, Owner: token, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermMachineReadEvents, iaasCtx), }) if err != nil { return err } defer func() { evt.Done(err) }() err = paramTemplate.Save() if err != nil { return err } w.WriteHeader(http.StatusCreated) return nil }
// title: template destroy // path: /iaas/templates/{template_name} // method: DELETE // responses: // 200: OK // 401: Unauthorized // 404: Not found func templateDestroy(w http.ResponseWriter, r *http.Request, token auth.Token) (err error) { r.ParseForm() templateName := r.URL.Query().Get(":template_name") t, err := iaas.FindTemplate(templateName) if err != nil { if err == mgo.ErrNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: "template not found"} } return err } iaasCtx := permission.Context(permission.CtxIaaS, t.IaaSName) allowed := permission.Check(token, permission.PermMachineTemplateDelete, iaasCtx) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeIaas, Value: t.IaaSName}, Kind: permission.PermMachineTemplateDelete, Owner: token, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermMachineReadEvents, iaasCtx), }) if err != nil { return err } defer func() { evt.Done(err) }() return iaas.DestroyTemplate(templateName) }
// 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: 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() }
// title: delete autoscale rule // path: /docker/autoscale/rules/{id} // method: DELETE // responses: // 200: Ok // 401: Unauthorized // 404: Not found func autoScaleDeleteRule(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() allowedDeleteRule := permission.Check(t, permission.PermNodeAutoscale) if !allowedDeleteRule { return permission.ErrUnauthorized } rulePool := r.URL.Query().Get(":id") var ctxs []permission.PermissionContext if rulePool != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, rulePool)) } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: rulePool}, Kind: permission.PermNodeAutoscaleDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermPoolReadEvents, ctxs...), }) if err != nil { return err } defer func() { evt.Done(err) }() err = deleteAutoScaleRule(rulePool) if err == mgo.ErrNotFound { return &tsuruErrors.HTTP{Code: http.StatusNotFound, Message: "rule not found"} } 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) (err error) { serviceName := r.URL.Query().Get(":name") s, err := getService(serviceName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceUpdateDoc, contextsForServiceProvision(&s)..., ) if !allowed { return permission.ErrUnauthorized } s.Doc = r.FormValue("doc") evt, err := event.New(&event.Opts{ Target: serviceTarget(s.Name), Kind: permission.PermServiceUpdateDoc, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermServiceReadEvents, contextsForServiceProvision(&s)...), }) if err != nil { return err } defer func() { evt.Done(err) }() return s.Update() }
// title: service delete // path: /services/{name} // method: DELETE // responses: // 200: Service removed // 401: Unauthorized // 403: Forbidden (team is not the owner or service with instances) // 404: Service not found func serviceDelete(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() s, err := getService(r.URL.Query().Get(":name")) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceDelete, contextsForServiceProvision(&s)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: serviceTarget(s.Name), Kind: permission.PermServiceDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermServiceReadEvents, contextsForServiceProvision(&s)...), }) if err != nil { return err } defer func() { evt.Done(err) }() 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.\n" msg += "Please remove these instances before removing the service." return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } return s.Delete() }
// title: node healing update // path: /healing/node // method: POST // consume: application/x-www-form-urlencoded // responses: // 200: Ok // 401: Unauthorized func nodeHealingUpdate(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { err = r.ParseForm() if err != nil { return err } poolName := r.FormValue("pool") var ctxs []permission.PermissionContext if poolName != "" { ctxs = append(ctxs, permission.Context(permission.CtxPool, poolName)) } if !permission.Check(t, permission.PermHealingUpdate, ctxs...) { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypePool, Value: poolName}, Kind: permission.PermHealingUpdate, 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) }() var config healer.NodeHealerConfig delete(r.Form, "pool") dec := form.NewDecoder(nil) dec.IgnoreUnknownKeys(true) err = dec.DecodeValues(&config, r.Form) if err != nil { return err } return healer.UpdateConfig(poolName, config) }
// title: dissociate role from user // path: /roles/{name}/user/{email} // method: DELETE // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized // 404: Role not found func dissociateRole(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() if !permission.Check(t, permission.PermRoleUpdateDissociate) { return permission.ErrUnauthorized } roleName := r.URL.Query().Get(":name") evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeRole, Value: roleName}, Kind: permission.PermRoleUpdateDissociate, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermRoleReadEvents), }) if err != nil { return err } defer func() { evt.Done(err) }() email := r.URL.Query().Get(":email") contextValue := r.URL.Query().Get("context") user, err := auth.GetUserByEmail(email) if err != nil { return err } err = canUseRole(t, roleName, contextValue) if err != nil { return err } err = runWithPermSync([]auth.User{*user}, func() error { return user.RemoveRole(roleName, contextValue) }) return err }
// title: remove role // path: /roles/{name} // method: DELETE // responses: // 200: Role removed // 401: Unauthorized // 404: Role not found func removeRole(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() if !permission.Check(t, permission.PermRoleDelete) { return permission.ErrUnauthorized } roleName := r.URL.Query().Get(":name") evt, err := event.New(&event.Opts{ Target: event.Target{Type: event.TargetTypeRole, Value: roleName}, Kind: permission.PermRoleDelete, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermRoleReadEvents), }) if err != nil { return err } defer func() { evt.Done(err) }() err = auth.RemoveRoleFromAllUsers(roleName) if err != nil { return err } err = permission.DestroyRole(roleName) if err == permission.ErrRoleNotFound { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } return err }
// 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) }
// title: rebuild routes // path: /apps/{app}/routes // method: POST // produce: application/json // responses: // 200: Ok // 401: Unauthorized // 404: App not found func appRebuildRoutes(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { a, err := getAppFromContext(r.URL.Query().Get(":app"), r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppAdminRoutes, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(a.Name), Kind: permission.PermAppAdminRoutes, 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/json") result, err := rebuild.RebuildRoutes(&a) if err != nil { return err } return json.NewEncoder(w).Encode(&result) }
// title: app unlock // path: /apps/{app}/lock // method: DELETE // produce: application/json // responses: // 200: Ok // 401: Unauthorized // 404: App not found func forceDeleteLock(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) { r.ParseForm() appName := r.URL.Query().Get(":app") a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppAdminUnlock, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } evt, err := event.New(&event.Opts{ Target: appTarget(appName), Kind: permission.PermAppAdminUnlock, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...), }) if err != nil { return err } defer func() { evt.Done(err) }() app.ReleaseApplicationLock(a.Name) 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: 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, ) }
// 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) (err 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 == "" { 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 } delete(r.Form, "password") evt, err := event.New(&event.Opts{ Target: serviceTarget(s.Name), Kind: permission.PermServiceCreate, Owner: t, CustomData: event.FormToCustomData(r.Form), Allowed: event.Allowed(permission.PermServiceReadEvents, contextsForServiceProvision(&s)...), }) if err != nil { return err } defer func() { evt.Done(err) }() 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 }