func serviceInstances(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } rec.Log(u.Email, "list-service-instances") services, _ := service.GetServicesByTeamKindAndNoRestriction("teams", u) sInstances, _ := service.GetServiceInstancesByServicesAndTeams(services, u) result := make([]service.ServiceModel, len(services)) for i, s := range services { result[i].Service = s.Name for _, si := range sInstances { if si.ServiceName == s.Name { result[i].Instances = append(result[i].Instances, si.Name) } } } 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."} } return err }
func removeUserFromTeam(w http.ResponseWriter, r *http.Request, t *auth.Token) error { email := r.URL.Query().Get(":user") teamName := r.URL.Query().Get(":team") u, err := t.User() if err != nil { return err } rec.Log(u.Email, "remove-user-from-team", "team="+teamName, "user="******"Team not found"} } if !team.ContainsUser(u) { msg := fmt.Sprintf("You are not authorized to remove a member from the team %s", team.Name) return &errors.HTTP{Code: http.StatusUnauthorized, Message: msg} } if len(team.Users) == 1 { msg := "You can not remove this user from this team, because it is the last user within the team, and a team can not be orphaned" return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } user, err := auth.GetUserByEmail(email) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } err = removeUserFromTeamInGandalf(user, team.Name) if err != nil { return nil } return removeUserFromTeamInDatabase(user, team) }
func createTeam(w http.ResponseWriter, r *http.Request, t *auth.Token) error { var params map[string]string err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } name, ok := params["name"] if !ok { msg := "You must provide the team name" return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } rec.Log(u.Email, "create-team", name) conn, err := db.Conn() if err != nil { return err } defer conn.Close() team := &auth.Team{Name: name, Users: []string{u.Email}} if err := conn.Teams().Insert(team); err != nil && strings.Contains(err.Error(), "duplicate key error") { msg := "This team already exists" return &errors.HTTP{Code: http.StatusConflict, Message: msg} } return nil }
func unsetEnv(w http.ResponseWriter, r *http.Request, t *auth.Token) error { msg := "You must provide the list of environment variables, in JSON format" if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } var variables []string defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&variables) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } if len(variables) == 0 { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } appName := r.URL.Query().Get(":app") u, err := t.User() if err != nil { return err } rec.Log(u.Email, "unset-env", "app="+appName, fmt.Sprintf("envs=%s", variables)) app, err := getApp(appName, u) if err != nil { return err } return app.UnsetEnvs(variables, true) }
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 } appName := r.URL.Query().Get(":app") rec.Log(u.Email, "set-env", "app="+appName, variables) app, err := getApp(appName, u) if err != nil { return err } envs := make([]bind.EnvVar, 0, len(variables)) for k, v := range variables { envs = append(envs, bind.EnvVar{Name: k, Value: v, Public: true}) } return app.SetEnvs(envs, true) }
func setCName(w http.ResponseWriter, r *http.Request, t *auth.Token) error { msg := "You must provide the cname." if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } var v map[string]string err := json.NewDecoder(r.Body).Decode(&v) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: "Invalid JSON in request body."} } if _, ok := v["cname"]; !ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") rec.Log(u.Email, "set-cname", "app="+appName, "cname="+v["cname"]) app, err := getApp(appName, u) if err != nil { return err } if err = app.SetCName(v["cname"]); err == nil { return nil } if err.Error() == "Invalid cname" { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } return err }
func serviceInfo(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } serviceName := r.URL.Query().Get(":name") rec.Log(u.Email, "service-info", serviceName) _, err = getServiceOrError(serviceName, u) if err != nil { return err } instances := []service.ServiceInstance{} teams, err := u.Teams() if err != nil { return err } conn, err := db.Conn() if err != nil { return err } defer conn.Close() teamsNames := auth.GetTeamsNames(teams) q := bson.M{"service_name": serviceName, "teams": bson.M{"$in": teamsNames}} err = conn.ServiceInstances().Find(q).All(&instances) if err != nil { return err } b, err := json.Marshal(instances) if err != nil { return nil } w.Write(b) return nil }
func revokeServiceAccess(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } serviceName := r.URL.Query().Get(":service") teamName := r.URL.Query().Get(":team") rec.Log(u.Email, "revoke-service-access", "service="+serviceName, "team="+teamName) service, team, err := getServiceAndTeam(serviceName, teamName, u) if err != nil { return err } if len(service.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} } err = service.RevokeAccess(team) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()} } conn, err := db.Conn() if err != nil { return err } return conn.Services().Update(bson.M{"_id": service.Name}, service) }
// ChangePassword changes the password from the logged in user. // // It reads the request body in JSON format. The JSON in the request body // should contain two attributes: // // - old: the old password // - new: the new password // // This handler will return 403 if the password didn't match the user, or 412 // if the new password is invalid. func ChangePassword(w http.ResponseWriter, r *http.Request, t *auth.Token) error { 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.", } } u, err := t.User() if err != nil { return err } if err := u.CheckPassword(body["old"]); err != nil { return &errors.Http{ Code: http.StatusForbidden, Message: "The given password didn't match the user's current password.", } } if !validation.ValidateLength(body["new"], passwordMinLen, passwordMaxLen) { return &errors.Http{ Code: http.StatusPreconditionFailed, Message: passwordError, } } u.Password = body["new"] u.HashPassword() return u.Update() }
func serviceDelete(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } rec.Log(u.Email, "delete-service", r.URL.Query().Get(":name")) s, err := getServiceByOwner(r.URL.Query().Get(":name"), u) if err != nil { return err } conn, err := db.Conn() if err != nil { return err } n, err := conn.ServiceInstances().Find(bson.M{"service_name": s.Name}).Count() if err != nil { return err } if n > 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 teamList(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } rec.Log(u.Email, "list-teams") teams, err := u.Teams() if err != nil { return err } if len(teams) > 0 { var result []map[string]string for _, team := range teams { result = append(result, map[string]string{"name": team.Name}) } 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."} } } else { w.WriteHeader(http.StatusNoContent) } return nil }
func addUserToTeam(w http.ResponseWriter, r *http.Request, t *auth.Token) error { teamName := r.URL.Query().Get(":team") email := r.URL.Query().Get(":user") u, err := t.User() if err != nil { return err } rec.Log(u.Email, "add-user-to-team", "team="+teamName, "user="******"Team not found"} } if !team.ContainsUser(u) { msg := fmt.Sprintf("You are not authorized to add new users to the team %s", team.Name) return &errors.HTTP{Code: http.StatusUnauthorized, Message: msg} } user, err := auth.GetUserByEmail(email) if err != nil { return &errors.HTTP{Code: http.StatusNotFound, Message: "User not found"} } actions := []*action.Action{ &addUserToTeamInGandalfAction, &addUserToTeamInDatabaseAction, } pipeline := action.NewPipeline(actions...) return pipeline.Execute(user.Email, u, team) }
func runCommand(w http.ResponseWriter, r *http.Request, t *auth.Token) error { w.Header().Set("Content-Type", "text") msg := "You must provide the command to run" if r.Body == nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } c, err := ioutil.ReadAll(r.Body) if err != nil { return err } if len(c) < 1 { return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") once := r.URL.Query().Get("once") rec.Log(u.Email, "run-command", "app="+appName, "command="+string(c)) app, err := getApp(appName, u) if err != nil { return err } return app.Run(string(c), w, once == "true") }
func RemoveUserFromTeam(w http.ResponseWriter, r *http.Request, t *auth.Token) error { email := r.URL.Query().Get(":user") team := r.URL.Query().Get(":team") u, err := t.User() if err != nil { return err } return removeUserFromTeam(email, team, u) }
func AddUserToTeam(w http.ResponseWriter, r *http.Request, t *auth.Token) error { team := r.URL.Query().Get(":team") email := r.URL.Query().Get(":user") u, err := t.User() if err != nil { return err } return addUserToTeam(email, team, u) }
func appLog(w http.ResponseWriter, r *http.Request, t *auth.Token) error { var err error var lines int if l := r.URL.Query().Get("lines"); l != "" { lines, err = strconv.Atoi(l) if err != nil { msg := `Parameter "lines" must be an integer.` return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } } else { return &errors.HTTP{Code: http.StatusBadRequest, Message: `Parameter "lines" is mandatory.`} } w.Header().Set("Content-Type", "application/json") source := r.URL.Query().Get("source") u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") extra := []interface{}{ "app=" + appName, fmt.Sprintf("lines=%d", lines), } if source != "" { extra = append(extra, "source="+source) } if r.URL.Query().Get("follow") == "1" { extra = append(extra, "follow=1") } rec.Log(u.Email, "app-log", extra...) a, err := getApp(appName, u) if err != nil { return err } logs, err := a.LastLogs(lines, source) if err != nil { return err } encoder := json.NewEncoder(w) err = encoder.Encode(logs) if err != nil { return err } // TODO(fss): write an automated test for this code. if r.URL.Query().Get("follow") == "1" { l := app.NewLogListener(&a) defer l.Close() for log := range l.C { err := encoder.Encode([]app.Applog{log}) if err != nil { break } } } return nil }
// RemoveKeyFromUser removes a key from a user. // // This function is just an http wrapper around removeKeyFromUser. The latter function // exists to be used in other places in the package without the http stuff (request and // response). func RemoveKeyFromUser(w http.ResponseWriter, r *http.Request, t *auth.Token) error { key, err := getKeyFromBody(r.Body) if err != nil { return err } u, err := t.User() if err != nil { return err } return removeKeyFromUser(key, u) }
func serviceInstance(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } instance, err := getServiceInstanceOrError(r.URL.Query().Get(":name"), u) if err != nil { return err } return json.NewEncoder(w).Encode(instance) }
func appInfo(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } app, err := getApp(r.URL.Query().Get(":app"), u) if err != nil { return err } return json.NewEncoder(w).Encode(&app) }
func platformList(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } rec.Log(u.Email, "platform-list") platforms, err := app.Platforms() if err != nil { return err } return json.NewEncoder(w).Encode(platforms) }
func restart(w http.ResponseWriter, r *http.Request, t *auth.Token) error { w.Header().Set("Content-Type", "text") u, err := t.User() if err != nil { return err } instance, err := getApp(r.URL.Query().Get(":app"), u) if err != nil { return err } return instance.Restart(w) }
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 sy serviceYaml err = goyaml.Unmarshal(body, &sy) if err != nil { return err } if _, ok := sy.Endpoint["production"]; !ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: "You must provide a production endpoint in the manifest file."} } u, err := t.User() if err != nil { return err } rec.Log(u.Email, "create-service", sy.Id, sy.Endpoint) conn, err := db.Conn() if err != nil { return err } defer conn.Close() var teams []auth.Team err = conn.Teams().Find(bson.M{"users": u.Email}).All(&teams) if err != nil { return err } if len(teams) == 0 { msg := "In order to create a service, you should be member of at least one team" return &errors.HTTP{Code: http.StatusForbidden, Message: msg} } n, err := conn.Services().Find(bson.M{"_id": sy.Id}).Count() if err != nil { return &errors.HTTP{Code: http.StatusInternalServerError, Message: err.Error()} } if n != 0 { msg := fmt.Sprintf("Service with name %s already exists.", sy.Id) return &errors.HTTP{Code: http.StatusInternalServerError, Message: msg} } s := service.Service{ Name: sy.Id, Endpoint: sy.Endpoint, OwnerTeams: auth.GetTeamsNames(teams), } err = s.Create() if err != nil { return err } fmt.Fprint(w, "success") return nil }
func unbindServiceInstance(w http.ResponseWriter, r *http.Request, t *auth.Token) error { instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app") u, err := t.User() if err != nil { return err } instance, a, err := getServiceInstace(instanceName, appName, u) if err != nil { return err } return instance.UnbindApp(&a) }
func GetDocHandler(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } s, err := getServiceByOwner(r.URL.Query().Get(":name"), u) if err != nil { return err } w.Write([]byte(s.Doc)) return nil }
func createApp(w http.ResponseWriter, r *http.Request, t *auth.Token) error { var a app.App var japp jsonApp defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if err = json.Unmarshal(body, &japp); err != nil { return err } a.Name = japp.Name a.Platform = japp.Platform if japp.Units == 0 { japp.Units = 1 } u, err := t.User() if err != nil { return err } rec.Log(u.Email, "create-app", "name="+japp.Name, "platform="+japp.Platform, fmt.Sprintf("units=%d", japp.Units)) teams, err := u.Teams() if err != nil { return err } if len(teams) < 1 { msg := "In order to create an app, you should be member of at least one team" return &errors.Http{Code: http.StatusForbidden, Message: msg} } err = app.CreateApp(&a, japp.Units, teams) if err != nil { log.Printf("Got error while creating app: %s", err) if e, ok := err.(*errors.ValidationError); ok { return &errors.Http{Code: http.StatusBadRequest, Message: e.Message} } if strings.Contains(err.Error(), "key error") { msg := fmt.Sprintf(`There is already an app named "%s".`, a.Name) return &errors.Http{Code: http.StatusConflict, Message: msg} } return err } msg := map[string]string{ "status": "success", "repository_url": repository.GetUrl(a.Name), } jsonMsg, err := json.Marshal(msg) if err != nil { return err } fmt.Fprintf(w, "%s", jsonMsg) return nil }
func forceDeleteApp(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } a, err := getApp(r.URL.Query().Get(":app"), u) if err != nil { return err } app.ForceDestroy(&a) fmt.Fprint(w, "success") return nil }
func unbindServiceInstance(w http.ResponseWriter, r *http.Request, t *auth.Token) error { instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app") u, err := t.User() if err != nil { return err } instance, a, err := getServiceInstance(instanceName, appName, u) if err != nil { return err } rec.Log(u.Email, "unbind-app", "instance="+instanceName, "app="+appName) return instance.UnbindApp(a) }
func unsetCName(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") app, err := getApp(appName, u) if err != nil { return err } rec.Log(u.Email, "unset-cname", "app="+appName) return app.UnsetCName() }
func Doc(w http.ResponseWriter, r *http.Request, t *auth.Token) error { u, err := t.User() if err != nil { return err } sName := r.URL.Query().Get(":name") s, err := getServiceOrError(sName, u) if err != nil { return err } w.Write([]byte(s.Doc)) return nil }
func createApp(w http.ResponseWriter, r *http.Request, t *auth.Token) error { var a app.App defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if err = json.Unmarshal(body, &a); err != nil { return err } u, err := t.User() if err != nil { return err } rec.Log(u.Email, "create-app", "name="+a.Name, "platform="+a.Platform) err = app.CreateApp(&a, u) if err != nil { log.Errorf("Got error while creating app: %s", err) if e, ok := err.(*errors.ValidationError); ok { return &errors.HTTP{Code: http.StatusBadRequest, Message: e.Message} } if _, ok := err.(app.NoTeamsError); ok { return &errors.HTTP{ Code: http.StatusBadRequest, Message: "In order to create an app, you should be member of at least one team", } } if e, ok := err.(*app.AppCreationError); ok { if e.Err == app.ErrAppAlreadyExists { return &errors.HTTP{Code: http.StatusConflict, Message: e.Error()} } if _, ok := e.Err.(*quota.QuotaExceededError); ok { return &errors.HTTP{ Code: http.StatusForbidden, Message: "Quota exceeded", } } } return err } msg := map[string]string{ "status": "success", "repository_url": repository.ReadWriteURL(a.Name), } jsonMsg, err := json.Marshal(msg) if err != nil { return err } fmt.Fprintf(w, "%s", jsonMsg) return nil }