func (h *jobAPI) ResourceCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { var req host.ResourceCheck if err := httphelper.DecodeJSON(r, &req); err != nil { httphelper.Error(w, err) return } var conflicts []host.Port for _, p := range req.Ports { if p.Proto == "" { p.Proto = "tcp" } if !checkPort(p) { conflicts = append(conflicts, p) } } if len(conflicts) > 0 { resp := host.ResourceCheck{Ports: conflicts} detail, err := json.Marshal(resp) if err != nil { httphelper.Error(w, err) return } httphelper.JSON(w, 409, &httphelper.JSONError{ Code: httphelper.ConflictErrorCode, Message: "Conflicting resources found", Detail: detail, }) return } httphelper.JSON(w, 200, struct{}{}) }
func (p *pgAPI) createDatabase(ctx context.Context, w http.ResponseWriter, req *http.Request) { username, password, database := random.Hex(16), random.Hex(16), random.Hex(16) if err := p.db.Exec(fmt.Sprintf(`CREATE USER "%s" WITH PASSWORD '%s'`, username, password)); err != nil { httphelper.Error(w, err) return } if err := p.db.Exec(fmt.Sprintf(`CREATE DATABASE "%s"`, database)); err != nil { p.db.Exec(fmt.Sprintf(`DROP USER "%s"`, username)) httphelper.Error(w, err) return } if err := p.db.Exec(fmt.Sprintf(`GRANT ALL ON DATABASE "%s" TO "%s"`, database, username)); err != nil { p.db.Exec(fmt.Sprintf(`DROP DATABASE "%s"`, database)) p.db.Exec(fmt.Sprintf(`DROP USER "%s"`, username)) httphelper.Error(w, err) return } url := fmt.Sprintf("postgres://%s:%s@%s:5432/%s", username, password, serviceHost, database) httphelper.JSON(w, 200, resource.Resource{ ID: fmt.Sprintf("/databases/%s:%s", username, database), Env: map[string]string{ "FLYNN_POSTGRES": serviceName, "PGHOST": serviceHost, "PGUSER": username, "PGPASSWORD": password, "PGDATABASE": database, "DATABASE_URL": url, }, }) }
func (api *API) GetConfig(ctx context.Context, w http.ResponseWriter, req *http.Request) { config := baseConfig config.Endpoints["cluster_controller"] = fmt.Sprintf("https://%s", api.conf.ControllerDomain) config.Endpoints["cluster_status"] = fmt.Sprintf("https://status.%s", api.conf.DefaultRouteDomain) config.Endpoints["cert"] = fmt.Sprintf("http://%s/ca-cert", api.conf.ControllerDomain) config.DefaultRouteDomain = api.conf.DefaultRouteDomain config.GithubAPIURL = api.conf.GithubAPIURL config.GithubTokenURL = api.conf.GithubTokenURL config.GithubCloneAuthRequired = api.conf.GithubCloneAuthRequired if api.IsAuthenticated(ctx) { config.User = &ExpandedUser{} config.User.Auths = make(map[string]*OAuthToken) if api.conf.GithubToken != "" { config.User.Auths["github"] = &OAuthToken{AccessToken: api.conf.GithubToken} } config.User.ControllerKey = api.conf.ControllerKey config.User.StatusKey = api.conf.StatusKey } httphelper.JSON(w, 200, config) }
func (api *API) UpdateRoute(ctx context.Context, w http.ResponseWriter, req *http.Request) { log, _ := ctxhelper.LoggerFromContext(ctx) params, _ := ctxhelper.ParamsFromContext(ctx) var route *router.Route if err := json.NewDecoder(req.Body).Decode(&route); err != nil { log.Error(err.Error()) httphelper.Error(w, err) return } route.Type = params.ByName("route_type") route.ID = params.ByName("id") l := api.router.ListenerFor(route.Type) if l == nil { httphelper.ValidationError(w, "type", "Invalid route type") return } if err := l.UpdateRoute(route); err != nil { if err == ErrNotFound { w.WriteHeader(404) return } log.Error(err.Error()) httphelper.Error(w, err) return } httphelper.JSON(w, 200, route) }
func (api *API) CreateCert(ctx context.Context, w http.ResponseWriter, req *http.Request) { var cert *router.Certificate if err := json.NewDecoder(req.Body).Decode(&cert); err != nil { httphelper.Error(w, err) return } l := api.router.HTTP.(*HTTPListener) err := l.AddCert(cert) if err != nil { jsonError := httphelper.JSONError{} switch err { case ErrConflict: jsonError.Code = httphelper.ConflictErrorCode jsonError.Message = "Duplicate cert" case ErrInvalid: jsonError.Code = httphelper.ValidationErrorCode jsonError.Message = "Invalid cert" default: httphelper.Error(w, err) return } } httphelper.JSON(w, 200, cert) }
func (api *API) GetRoutes(ctx context.Context, w http.ResponseWriter, req *http.Request) { log, _ := ctxhelper.LoggerFromContext(ctx) routes, err := api.router.HTTP.List() if err != nil { log.Error(err.Error()) httphelper.Error(w, err) return } tcpRoutes, err := api.router.TCP.List() if err != nil { log.Error(err.Error()) httphelper.Error(w, err) return } routes = append(routes, tcpRoutes...) if ref := req.URL.Query().Get("parent_ref"); ref != "" { filtered := make([]*router.Route, 0) for _, route := range routes { if route.ParentRef == ref { filtered = append(filtered, route) } } routes = filtered } sort.Sort(sortedRoutes(routes)) httphelper.JSON(w, 200, routes) }
func (c *controllerAPI) DeleteResource(ctx context.Context, w http.ResponseWriter, req *http.Request) { params, _ := ctxhelper.ParamsFromContext(ctx) id := params.ByName("resources_id") p, err := c.getProvider(ctx) if err != nil { respondWithError(w, err) return } res, err := c.resourceRepo.Get(id) if err != nil { respondWithError(w, err) return } if err := resource.Deprovision(p.URL, res.ExternalID); err != nil { respondWithError(w, err) return } if err := c.resourceRepo.Remove(res); err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, res) }
func (c *controllerAPI) PutResource(ctx context.Context, w http.ResponseWriter, req *http.Request) { params, _ := ctxhelper.ParamsFromContext(ctx) p, err := c.getProvider(ctx) if err != nil { respondWithError(w, err) return } var resource ct.Resource if err = httphelper.DecodeJSON(req, &resource); err != nil { respondWithError(w, err) return } resource.ID = params.ByName("resources_id") resource.ProviderID = p.ID if err := schema.Validate(resource); err != nil { respondWithError(w, err) return } if err := c.resourceRepo.Add(&resource); err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, &resource) }
func (h *jobAPI) Update(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { log := h.host.log.New("fn", "Update") log.Info("decoding command") var cmd host.Command if err := httphelper.DecodeJSON(req, &cmd); err != nil { log.Error("error decoding command", "err", err) httphelper.Error(w, err) return } log.Info("updating host") err := h.host.Update(&cmd) if err != nil { httphelper.Error(w, err) return } // send an ok response and then shutdown after 1s to give the response // chance to reach the client. httphelper.JSON(w, http.StatusOK, cmd) log.Info("shutting down in 1s") time.AfterFunc(time.Second, func() { log.Info("exiting") os.Exit(0) }) }
// serveGetLeader returns the current leader for a service. func (h *Handler) serveGetLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) { // Process as a stream if that's what the client wants. if strings.Contains(r.Header.Get("Accept"), "text/event-stream") { if !h.Store.IsLeader() { h.redirectToLeader(w, r) return } h.serveStream(w, params, discoverd.EventKindLeader) return } // Otherwise retrieve the current leader. service := params.ByName("service") leader, err := h.Store.ServiceLeader(service) if err != nil { hh.Error(w, err) return } else if leader == nil { hh.ObjectNotFoundError(w, "no leader found") return } // Write leader to the response. hh.JSON(w, 200, leader) }
func (api *HTTPAPI) CreateProvider(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { pspec := &volume.ProviderSpec{} if err := httphelper.DecodeJSON(r, &pspec); err != nil { httphelper.Error(w, err) return } if pspec.ID == "" { pspec.ID = random.UUID() } if pspec.Kind == "" { httphelper.ValidationError(w, "kind", "must not be blank") return } var provider volume.Provider provider, err := volumemanager.NewProvider(pspec) if err == volume.UnknownProviderKind { httphelper.ValidationError(w, "kind", fmt.Sprintf("%q is not known", pspec.Kind)) return } if err := api.vman.AddProvider(pspec.ID, provider); err != nil { switch err { case volumemanager.ErrProviderExists: httphelper.ObjectExistsError(w, fmt.Sprintf("provider %q already exists", pspec.ID)) return default: httphelper.Error(w, err) return } } httphelper.JSON(w, 200, pspec) }
func (c *controllerAPI) SetAppRelease(ctx context.Context, w http.ResponseWriter, req *http.Request) { var rid releaseID if err := httphelper.DecodeJSON(req, &rid); err != nil { respondWithError(w, err) return } rel, err := c.releaseRepo.Get(rid.ID) if err != nil { if err == ErrNotFound { err = ct.ValidationError{ Message: fmt.Sprintf("could not find release with ID %s", rid.ID), } } respondWithError(w, err) return } release := rel.(*ct.Release) if err := schema.Validate(release); err != nil { respondWithError(w, err) return } app := c.getApp(ctx) c.appRepo.SetRelease(app, release.ID) httphelper.JSON(w, 200, release) }
func (api *httpAPI) GetCloudRegions(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { params := req.URL.Query() cloud := params.Get("cloud") if cloud != "digital_ocean" && cloud != "azure" { httphelper.ObjectNotFoundError(w, "") return } credentialID := params.Get("credential_id") creds, err := api.Installer.FindCredentials(credentialID) if err != nil { httphelper.ValidationError(w, "credential_id", "Invalid credential id") return } var res interface{} switch cloud { case "digital_ocean": res, err = api.Installer.ListDigitalOceanRegions(creds) case "azure": res, err = api.Installer.ListAzureRegions(creds) } if err != nil { httphelper.Error(w, err) return } httphelper.JSON(w, 200, res) }
func (c *controllerAPI) UpdateApp(ctx context.Context, rw http.ResponseWriter, req *http.Request) { params, _ := ctxhelper.ParamsFromContext(ctx) var data appUpdate if err := httphelper.DecodeJSON(req, &data); err != nil { respondWithError(rw, err) return } if v, ok := data["meta"]; ok && v == nil { // handle {"meta": null} delete(data, "meta") } if err := schema.Validate(data); err != nil { respondWithError(rw, err) return } app, err := c.appRepo.Update(params.ByName("apps_id"), data) if err != nil { respondWithError(rw, err) return } httphelper.JSON(rw, 200, app) }
func (a *API) createDatabase(ctx context.Context, w http.ResponseWriter, req *http.Request) { username, password, database := random.Hex(16), random.Hex(16), random.Hex(16) if _, err := a.db.Exec(fmt.Sprintf("CREATE USER '%s'@'%%' IDENTIFIED BY '%s'", username, password)); err != nil { httphelper.Error(w, err) return } if _, err := a.db.Exec(fmt.Sprintf("CREATE DATABASE `%s`", database)); err != nil { a.db.Exec(fmt.Sprintf("DROP USER '%s'", username)) httphelper.Error(w, err) return } if _, err := a.db.Exec(fmt.Sprintf("GRANT ALL ON `%s`.* TO '%s'@'%%'", database, username)); err != nil { a.db.Exec(fmt.Sprintf("DROP DATABASE `%s`", database)) a.db.Exec(fmt.Sprintf("DROP USER '%s'", username)) httphelper.Error(w, err) return } url := fmt.Sprintf("mysql://%s:%s@%s:3306/%s", username, password, serviceHost, database) httphelper.JSON(w, 200, resource.Resource{ ID: fmt.Sprintf("/databases/%s:%s", username, database), Env: map[string]string{ "FLYNN_MYSQL": serviceName, "MYSQL_HOST": serviceHost, "MYSQL_USER": username, "MYSQL_PWD": password, "MYSQL_DATABASE": database, "DATABASE_URL": url, }, }) }
func (c *controllerAPI) UpdateAppMeta(ctx context.Context, rw http.ResponseWriter, req *http.Request) { params, _ := ctxhelper.ParamsFromContext(ctx) var data appUpdate if err := httphelper.DecodeJSON(req, &data); err != nil { respondWithError(rw, err) return } if err := schema.Validate(data); err != nil { respondWithError(rw, err) return } if data["meta"] == nil { data["meta"] = make(map[string]interface{}) } app, err := c.appRepo.Update(params.ByName("apps_id"), data) if err != nil { respondWithError(rw, err) return } httphelper.JSON(rw, 200, app) }
func (api *HTTPAPI) Pull(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { volumeID := ps.ByName("volume_id") pull := &volume.PullCoordinate{} if err := httphelper.DecodeJSON(r, &pull); err != nil { httphelper.Error(w, err) return } hostClient, err := api.cluster.Host(pull.HostID) if err != nil { httphelper.Error(w, err) return } haves, err := api.vman.ListHaves(volumeID) if err != nil { httphelper.Error(w, err) return } reader, err := hostClient.SendSnapshot(pull.SnapshotID, haves) if err != nil { httphelper.Error(w, err) return } snap, err := api.vman.ReceiveSnapshot(volumeID, reader) if err != nil { httphelper.Error(w, err) return } httphelper.JSON(w, 200, snap.Info()) }
func (api *httpAPI) ServeTemplate(w http.ResponseWriter, req *http.Request, params httprouter.Params) { if req.Header.Get("Accept") == "application/json" { s, err := api.Installer.FindBaseCluster(params.ByName("id")) if err != nil { httphelper.ObjectNotFoundError(w, err.Error()) return } httphelper.JSON(w, 200, s) return } manifest, err := api.AssetManifest() if err != nil { httphelper.Error(w, err) api.logger.Debug(err.Error()) return } w.Header().Add("Content-Type", "text/html; charset=utf-8") w.Header().Add("Cache-Control", "max-age=0") err = htmlTemplate.Execute(w, &htmlTemplateData{ ApplicationJSPath: manifest.Assets["application.js"], ApplicationCSSPath: manifest.Assets["application.css"], ReactJSPath: manifest.Assets["react.js"], }) if err != nil { httphelper.Error(w, err) api.logger.Debug(err.Error()) return } }
func (c *controllerAPI) PutFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) { app := c.getApp(ctx) release, err := c.getRelease(ctx) if err != nil { respondWithError(w, err) return } var formation ct.Formation if err = httphelper.DecodeJSON(req, &formation); err != nil { respondWithError(w, err) return } if release.ArtifactID == "" { respondWithError(w, ct.ValidationError{Message: "release is not deployable"}) return } formation.AppID = app.ID formation.ReleaseID = release.ID if err = schema.Validate(formation); err != nil { respondWithError(w, err) return } if err = c.formationRepo.Add(&formation); err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, &formation) }
// serveGetRaftPeers returns the current raft peers. func (h *Handler) serveGetRaftPeers(w http.ResponseWriter, r *http.Request, params httprouter.Params) { peers, err := h.Store.GetPeers() if err != nil { hh.Error(w, err) } hh.JSON(w, 200, peers) }
func (c *controllerAPI) ListActiveJobs(ctx context.Context, w http.ResponseWriter, req *http.Request) { list, err := c.jobRepo.ListActive() if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, list) }
func (api *HTTPAPI) List(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { vols := api.vman.Volumes() volList := make([]*volume.Info, 0, len(vols)) for _, v := range vols { volList = append(volList, v.Info()) } httphelper.JSON(w, 200, volList) }
func (c *controllerAPI) GetRouteList(ctx context.Context, w http.ResponseWriter, req *http.Request) { routes, err := c.routerc.ListRoutes(routeParentRef(c.getApp(ctx).ID)) if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, routes) }
func (h *httpAPI) GetServiceMeta(w http.ResponseWriter, r *http.Request, params httprouter.Params) { meta := h.Store.GetServiceMeta(params.ByName("service")) if meta == nil { hh.ObjectNotFoundError(w, "service meta not found") return } hh.JSON(w, 200, meta) }
func (c *controllerAPI) GetAppResources(ctx context.Context, w http.ResponseWriter, req *http.Request) { res, err := c.resourceRepo.AppList(c.getApp(ctx).ID) if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, res) }
func (c *controllerAPI) GetAppRelease(ctx context.Context, w http.ResponseWriter, req *http.Request) { release, err := c.appRepo.GetRelease(c.getApp(ctx).ID) if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, release) }
func (h *Handler) serveShutdown(w http.ResponseWriter, r *http.Request, params httprouter.Params) { h.Shutdown.Store(true) targetLogIndex, err := h.Main.Close() if err != nil { hh.Error(w, err) return } hh.JSON(w, 200, targetLogIndex) }
// serveGetRaftLeader returns the current raft leader. func (h *Handler) serveGetRaftLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) { leader := h.Store.Leader() if leader == "" { hh.Error(w, ErrNoKnownLeader) return } hh.JSON(w, 200, dt.RaftLeader{Host: h.Store.Leader()}) }
func (c *controllerAPI) GetJob(ctx context.Context, w http.ResponseWriter, req *http.Request) { params, _ := ctxhelper.ParamsFromContext(ctx) job, err := c.jobRepo.Get(params.ByName("jobs_id")) if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, job) }
func (c *controllerAPI) ListFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) { app := c.getApp(ctx) list, err := c.formationRepo.List(app.ID) if err != nil { respondWithError(w, err) return } httphelper.JSON(w, 200, list) }