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 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 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 }
func getEnv(w http.ResponseWriter, r *http.Request, t auth.Token) error { var variables []string if r.Body != nil { defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&variables) if err != nil && err != io.EOF { return err } } appName := r.URL.Query().Get(":app") var u *auth.User var err error a, err := getAppFromContext(appName, r) if err != nil { return err } if !t.IsAppToken() { u, err = t.User() if err != nil { return err } rec.Log(u.Email, "get-env", "app="+appName, fmt.Sprintf("envs=%s", variables)) allowed := permission.Check(t, permission.PermAppReadEnv, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } } return writeEnvVars(w, &a, variables...) }
// 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 appRebuildRoutes(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 } allowed := permission.Check(t, permission.PermAppAdminRoutes, 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, "app-rebuild-routes", "app="+r.URL.Query().Get(":app")) w.Header().Set("Content-Type", "application/json") result, err := a.RebuildRoutes() if err != nil { return err } return json.NewEncoder(w).Encode(&result) }
func updateApp(w http.ResponseWriter, r *http.Request, t auth.Token) error { var updateData app.App defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if err = json.Unmarshal(body, &updateData); err != nil { return err } appName := r.URL.Query().Get(":appname") a, err := getAppFromContext(appName, r) if err != nil { return err } if updateData.Description != "" { allowed := permission.Check(t, permission.PermAppUpdate, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } a.Description = updateData.Description } u, err := t.User() if err != nil { return err } rec.Log(u.Email, "update-app", "app="+a.Name, "description="+a.Description) return a.Update() }
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 appChangePool(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 } allowed := permission.Check(t, permission.PermAppUpdatePool, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } defer r.Body.Close() data, err := ioutil.ReadAll(r.Body) if err != nil { return &errors.HTTP{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unable to decode body: %s", err.Error()), } } pool := string(data) rec.Log(u.Email, "app-change-pool", "app="+r.URL.Query().Get(":app"), "pool="+pool) return a.ChangePool(pool) }
func deployDataToEvent(data *DeployData) error { var evt event.Event evt.UniqueID = data.ID evt.Target = event.Target{Type: event.TargetTypeApp, Value: data.App} evt.Owner = event.Owner{Type: event.OwnerTypeUser, Name: data.User} evt.Kind = event.Kind{Type: event.KindTypePermission, Name: permission.PermAppDeploy.FullName()} evt.StartTime = data.Timestamp evt.EndTime = data.Timestamp.Add(data.Duration) evt.Error = data.Error evt.Log = data.Log evt.RemoveDate = data.RemoveDate a, err := GetByName(data.App) if err == nil { evt.Allowed = event.Allowed(permission.PermAppReadEvents, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )...) } else { evt.Allowed = event.Allowed(permission.PermAppReadEvents) } startOpts := DeployOptions{ Commit: data.Commit, Origin: data.Origin, } var otherData map[string]string if data.Diff != "" { otherData = map[string]string{"diff": data.Diff} } endData := map[string]string{"image": data.Image} err = evt.RawInsert(startOpts, otherData, endData) if mgo.IsDup(err) { return nil } return err }
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 }
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 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 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 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 unsetCName(w http.ResponseWriter, r *http.Request, t auth.Token) error { cnames := r.URL.Query()["cname"] if len(cnames) == 0 { msg := "You must provide the cname." return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } 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.PermAppUpdateCnameRemove, 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, "remove-cname", "app="+appName, "cnames="+strings.Join(cnames, ", ")) if err = a.RemoveCName(cnames...); err == nil { return nil } if err.Error() == "Invalid cname" { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } return err }
func sleep(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 } proxy := r.URL.Query().Get("proxy") if proxy == "" { return &errors.HTTP{Code: http.StatusBadRequest, Message: "Empty proxy URL"} } proxyURL, err := url.Parse(proxy) if err != nil { log.Errorf("Invalid url for proxy param: %v", proxy) return err } allowed := permission.Check(t, permission.PermAppUpdateSleep, 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, "sleep", "app="+appName) return a.Sleep(w, process, proxyURL) }
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 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: service instance create // path: /services/{service}/instances // method: POST // consume: application/x-www-form-urlencoded // responses: // 201: Service created // 400: Invalid data // 401: Unauthorized // 409: Service already exists func createServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { serviceName := r.URL.Query().Get(":service") user, err := t.User() if err != nil { return err } srv, err := getService(serviceName) if err != nil { return err } instance := service.ServiceInstance{ Name: r.FormValue("name"), PlanName: r.FormValue("plan"), TeamOwner: r.FormValue("owner"), Description: r.FormValue("description"), } var teamOwner string if instance.TeamOwner == "" { teamOwner, err = permission.TeamForPermission(t, permission.PermServiceInstanceCreate) if err != nil { return err } instance.TeamOwner = teamOwner } allowed := permission.Check(t, permission.PermServiceInstanceCreate, permission.Context(permission.CtxTeam, instance.TeamOwner), ) if !allowed { return permission.ErrUnauthorized } if srv.IsRestricted { allowed := permission.Check(t, permission.PermServiceRead, append(permission.Contexts(permission.CtxTeam, srv.Teams), permission.Context(permission.CtxService, srv.Name))..., ) if !allowed { return permission.ErrUnauthorized } } rec.Log(user.Email, "create-service-instance", fmt.Sprintf("%#v", instance)) err = service.CreateServiceInstance(instance, &srv, user) if err == service.ErrInstanceNameAlreadyExists { return &errors.HTTP{ Code: http.StatusConflict, Message: err.Error(), } } if err == service.ErrInvalidInstanceName { return &errors.HTTP{ Code: http.StatusBadRequest, Message: err.Error(), } } if err == nil { w.WriteHeader(http.StatusCreated) } 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 }
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, _ := strconv.ParseBool(r.URL.Query().Get("noRestart")) u, err := t.User() if err != nil { return err } instance, a, err := getServiceInstance(serviceName, instanceName, appName) if err != nil { return err } allowed := permission.Check(t, permission.PermServiceInstanceUpdateUnbind, append(permission.Contexts(permission.CtxTeam, instance.Teams), permission.Context(permission.CtxServiceInstance, instance.Name), )..., ) if !allowed { return permission.ErrUnauthorized } allowed = permission.Check(t, permission.PermAppUpdateUnbind, 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, "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 (h *ContainerHealer) healContainerIfNeeded(cont container.Container) error { if cont.LastSuccessStatusUpdate.IsZero() { if !cont.MongoID.Time().Before(time.Now().Add(-h.maxUnresponsiveTime)) { return nil } } isAsExpected, err := h.isAsExpected(cont) if err != nil { log.Errorf("Containers healing: couldn't verify running processes in container %q: %s", cont.ID, err) } if isAsExpected { cont.SetStatus(h.provisioner, cont.ExpectedStatus(), true) return nil } locked := h.locker.Lock(cont.AppName) if !locked { return errors.Errorf("Containers healing: unable to heal %q couldn't lock app %s", cont.ID, cont.AppName) } defer h.locker.Unlock(cont.AppName) // Sanity check, now we have a lock, let's find out if the container still exists _, err = h.provisioner.GetContainer(cont.ID) if err != nil { if _, isNotFound := err.(*provision.UnitNotFoundError); isNotFound { return nil } return errors.Wrapf(err, "Containers healing: unable to heal %q couldn't verify it still exists", cont.ID) } a, err := app.GetByName(cont.AppName) if err != nil { return errors.Wrapf(err, "Containers healing: unable to heal %q couldn't get app %q", cont.ID, cont.AppName) } log.Errorf("Initiating healing process for container %q, unresponsive since %s.", cont.ID, cont.LastSuccessStatusUpdate) evt, err := event.NewInternal(&event.Opts{ Target: event.Target{Type: event.TargetTypeContainer, Value: cont.ID}, InternalKind: "healer", CustomData: cont, Allowed: event.Allowed(permission.PermAppReadEvents, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )...), }) if err != nil { return errors.Wrap(err, "Error trying to insert container healing event, healing aborted") } newCont, healErr := h.healContainer(cont) if healErr != nil { healErr = errors.Errorf("Error healing container %q: %s", cont.ID, healErr.Error()) } err = evt.DoneCustomData(healErr, newCont) if err != nil { log.Errorf("Error trying to update containers healing event: %s", err) } return healErr }
// 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 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 }
// Revoke removes the access from a team. It returns an error if the team do // not have access to the app. func (app *App) Revoke(team *auth.Team) error { if len(app.Teams) == 1 { return ErrCannotOrphanApp } index, found := app.findTeam(team) if !found { return ErrNoAccess } last := len(app.Teams) - 1 app.Teams[index] = app.Teams[last] app.Teams = app.Teams[:last] conn, err := db.Conn() if err != nil { return err } defer conn.Close() err = conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$pull": bson.M{"teams": team.Name}}) if err != nil { return err } users, err := auth.ListUsersWithPermissions(permission.Permission{ Scheme: permission.PermAppDeploy, Context: permission.Context(permission.CtxTeam, team.Name), }) if err != nil { conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$addToSet": bson.M{"teams": team.Name}}) return err } for _, user := range users { perms, err := user.Permissions() if err != nil { conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$addToSet": bson.M{"teams": team.Name}}) return err } canDeploy := permission.CheckFromPermList(perms, permission.PermAppDeploy, append(permission.Contexts(permission.CtxTeam, app.Teams), permission.Context(permission.CtxApp, app.Name), permission.Context(permission.CtxPool, app.Pool), )..., ) if canDeploy { continue } err = repository.Manager().RevokeAccess(app.Name, user.Email) if err != nil { conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$addToSet": bson.M{"teams": team.Name}}) return err } } return nil }
func createServiceInstance(w http.ResponseWriter, r *http.Request, t auth.Token) error { b, err := ioutil.ReadAll(r.Body) if err != nil { return err } var body map[string]string err = json.Unmarshal(b, &body) if err != nil { return err } serviceName := body["service_name"] user, err := t.User() if err != nil { return err } srv, err := getService(serviceName) if err != nil { return err } instance := service.ServiceInstance{ Name: body["name"], PlanName: body["plan"], TeamOwner: body["owner"], Description: body["description"], } if instance.TeamOwner == "" { teamOwner, err := permission.TeamForPermission(t, permission.PermServiceInstanceCreate) if err != nil { return err } instance.TeamOwner = teamOwner } allowed := permission.Check(t, permission.PermServiceInstanceCreate, permission.Context(permission.CtxTeam, instance.TeamOwner), ) if !allowed { return permission.ErrUnauthorized } if srv.IsRestricted { allowed := permission.Check(t, permission.PermServiceRead, append(permission.Contexts(permission.CtxTeam, srv.Teams), permission.Context(permission.CtxService, srv.Name))..., ) if !allowed { return permission.ErrUnauthorized } } rec.Log(user.Email, "create-service-instance", string(b)) return service.CreateServiceInstance(instance, &srv, user) }
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 } a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppUpdateEnvUnset, 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, "unset-env", "app="+appName, fmt.Sprintf("envs=%s", variables)) w.Header().Set("Content-Type", "application/json") keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "") defer keepAliveWriter.Stop() writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)} noRestart, _ := strconv.ParseBool(r.URL.Query().Get("noRestart")) err = a.UnsetEnvs( bind.UnsetEnvApp{ VariableNames: variables, PublicOnly: true, ShouldRestart: !noRestart, }, writer) if err != nil { writer.Encode(tsuruIo.SimpleJsonMessage{Error: err.Error()}) return nil } return nil }
func healingEventToEvent(data *HealingEvent) error { var evt event.Event evt.UniqueID = data.ID.(bson.ObjectId) var startOpts, endOpts interface{} switch data.Action { case "node-healing": evt.Target = event.Target{Type: event.TargetTypeNode, Value: data.FailingNode.Address} var lastCheck *healer.NodeChecks if data.Extra != nil { checkRaw, err := json.Marshal(data.Extra) if err == nil { json.Unmarshal(checkRaw, &lastCheck) } } startOpts = healer.NodeHealerCustomData{ Node: data.FailingNode, Reason: data.Reason, LastCheck: lastCheck, } endOpts = data.CreatedNode poolName := data.FailingNode.Metadata["pool"] evt.Allowed = event.Allowed(permission.PermPoolReadEvents, permission.Context(permission.CtxPool, poolName)) case "container-healing": evt.Target = event.Target{Type: event.TargetTypeContainer, Value: data.FailingContainer.ID} startOpts = data.FailingContainer endOpts = data.CreatedContainer a, err := app.GetByName(data.FailingContainer.AppName) if err == nil { evt.Allowed = event.Allowed(permission.PermAppReadEvents, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )...) } else { evt.Allowed = event.Allowed(permission.PermAppReadEvents) } default: return errors.Errorf("invalid action %q", data.Action) } evt.Owner = event.Owner{Type: event.OwnerTypeInternal} evt.Kind = event.Kind{Type: event.KindTypeInternal, Name: "healer"} evt.StartTime = data.StartTime evt.EndTime = data.EndTime evt.Error = data.Error err := evt.RawInsert(startOpts, nil, endOpts) if mgo.IsDup(err) { return nil } return err }
func getAppQuota(w http.ResponseWriter, r *http.Request, t auth.Token) error { a, err := getAppFromContext(r.URL.Query().Get(":appname"), r) if err != nil { return err } canRead := permission.Check(t, permission.PermAppRead, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !canRead { return permission.ErrUnauthorized } return json.NewEncoder(w).Encode(a.Quota) }