Beispiel #1
0
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)
}
Beispiel #2
0
func (a *aggregatorAPI) GetLog(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	ctx, cancel := context.WithCancel(ctx)
	if cn, ok := w.(http.CloseNotifier); ok {
		ch := cn.CloseNotify()
		go func() {
			select {
			case <-ch:
				cancel()
			case <-ctx.Done():
			}
		}()
	}
	defer cancel()

	params, _ := ctxhelper.ParamsFromContext(ctx)

	follow := false
	if strFollow := req.FormValue("follow"); strFollow == "true" {
		follow = true
	}

	var (
		backlog bool
		lines   int
		err     error
	)
	if strLines := req.FormValue("lines"); strLines != "" {
		if lines, err = strconv.Atoi(strLines); err != nil {
			httphelper.ValidationError(w, "lines", err.Error())
			return
		}
		if lines < 0 || lines > 10000 {
			httphelper.ValidationError(w, "lines", "lines must be an integer between 0 and 10000")
			return
		}
		backlog = lines > 0
	}

	filters := make(filterSlice, 0)
	if jobID := req.FormValue("job_id"); jobID != "" {
		filters = append(filters, filterJobID(jobID))
	}
	if processTypeVals, ok := req.Form["process_type"]; ok && len(processTypeVals) > 0 {
		val := processTypeVals[len(processTypeVals)-1]
		filters = append(filters, filterProcessType(val))
	}

	iter := &Iterator{
		id:      params.ByName("channel_id"),
		follow:  follow,
		backlog: backlog,
		lines:   lines,
		filter:  filters,
		donec:   ctx.Done(),
	}

	writeMessages(ctx, w, iter.Scan(a.agg))
}
Beispiel #3
0
func (c *controllerAPI) KillJob(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
	} else if job.HostID == "" {
		httphelper.ValidationError(w, "", "cannot kill a job which has not been placed on a host")
		return
	}

	client, err := c.clusterClient.Host(job.HostID)
	if err != nil {
		respondWithError(w, err)
		return
	}

	if err = client.StopJob(job.ID); err != nil {
		if _, ok := err.(ct.NotFoundError); ok {
			err = ErrNotFound
		}
		respondWithError(w, err)
		return
	}
}
Beispiel #4
0
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)
}
Beispiel #5
0
func (api *HTTPAPI) Send(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	volumeID := ps.ByName("volume_id")

	if !strings.Contains(r.Header.Get("Accept"), snapshotContentType) {
		httphelper.ValidationError(w, "", fmt.Sprintf("must be prepared to accept a content type of %q", snapshotContentType))
		return
	}
	w.Header().Set("Content-Type", snapshotContentType)

	var haves []json.RawMessage
	if err := httphelper.DecodeJSON(r, &haves); err != nil {
		httphelper.Error(w, err)
		return
	}

	err := api.vman.SendSnapshot(volumeID, haves, w)
	if err != nil {
		switch err {
		case volume.ErrNoSuchVolume:
			httphelper.ObjectNotFoundError(w, fmt.Sprintf("no volume with id %q", volumeID))
			return
		default:
			httphelper.Error(w, err)
			return
		}
	}
}
Beispiel #6
0
// servePutInstance adds an instance to a service.
func (h *Handler) servePutInstance(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Read path parameter.
	service := params.ByName("service")

	// Read instance from request.
	inst := &discoverd.Instance{}
	if err := json.NewDecoder(r.Body).Decode(inst); err != nil {
		hh.Error(w, err)
		return
	}

	// Ensure instance is valid.
	if err := inst.Valid(); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}

	// Add instance to service in the store.
	if err := h.Store.AddInstance(service, inst); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if IsNotFound(err) {
		hh.ObjectNotFoundError(w, err.Error())
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Beispiel #7
0
func (a *API) dropDatabase(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	id := strings.SplitN(strings.TrimPrefix(req.FormValue("id"), "/databases/"), ":", 2)
	if len(id) != 2 || id[1] == "" {
		httphelper.ValidationError(w, "id", "is invalid")
		return
	}

	db, err := a.connect()
	if err != nil {
		httphelper.Error(w, err)
		return
	}
	defer db.Close()

	if _, err := db.Exec(fmt.Sprintf("DROP DATABASE `%s`", id[1])); err != nil {
		httphelper.Error(w, err)
		return
	}

	if _, err := db.Exec(fmt.Sprintf("DROP USER '%s'", id[0])); err != nil {
		httphelper.Error(w, err)
		return
	}

	w.WriteHeader(200)
}
Beispiel #8
0
func (p *pgAPI) dropDatabase(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	id := strings.SplitN(strings.TrimPrefix(req.FormValue("id"), "/databases/"), ":", 2)
	if len(id) != 2 || id[1] == "" {
		httphelper.ValidationError(w, "id", "is invalid")
		return
	}

	// disable new connections to the target database
	if err := p.db.Exec(disallowConns, id[1]); err != nil {
		httphelper.Error(w, err)
		return
	}

	// terminate current connections
	if err := p.db.Exec(disconnectConns, id[1]); err != nil {
		httphelper.Error(w, err)
		return
	}

	if err := p.db.Exec(fmt.Sprintf(`DROP DATABASE "%s"`, id[1])); err != nil {
		httphelper.Error(w, err)
		return
	}

	if err := p.db.Exec(fmt.Sprintf(`DROP USER "%s"`, id[0])); err != nil {
		httphelper.Error(w, err)
		return
	}

	w.WriteHeader(200)
}
Beispiel #9
0
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)
}
Beispiel #10
0
func (a *API) dropDatabase(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	id := strings.SplitN(strings.TrimPrefix(req.FormValue("id"), "/databases/"), ":", 2)
	if len(id) != 2 || id[1] == "" {
		httphelper.ValidationError(w, "id", "is invalid")
		return
	}
	user, database := id[0], id[1]

	session, err := mgo.DialWithInfo(&mgo.DialInfo{
		Addrs:    []string{net.JoinHostPort(serviceHost, "27017")},
		Username: "******",
		Password: os.Getenv("MONGO_PWD"),
		Database: "admin",
	})
	if err != nil {
		httphelper.Error(w, err)
		return
	}
	defer session.Close()

	// Delete user.
	if err := session.DB(database).Run(bson.D{{"dropUser", user}}, nil); err != nil {
		httphelper.Error(w, err)
		return
	}

	// Delete database.
	if err := session.DB(database).Run(bson.D{{"dropDatabase", 1}}, nil); err != nil {
		httphelper.Error(w, err)
		return
	}

	w.WriteHeader(200)
}
Beispiel #11
0
// servePutLeader sets the leader for a service.
func (h *Handler) servePutLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Retrieve path parameters.
	service := params.ByName("service")

	// Check if the service allows manual leader election.
	config := h.Store.Config(service)
	if config == nil || config.LeaderType != discoverd.LeaderTypeManual {
		hh.ValidationError(w, "", "service leader election type is not manual")
		return
	}

	// Read instance from the request.
	inst := &discoverd.Instance{}
	if err := hh.DecodeJSON(r, inst); err != nil {
		hh.Error(w, err)
		return
	}

	// Manually set the leader on the service.
	if err := h.Store.SetServiceLeader(service, inst.ID); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Beispiel #12
0
// servePutService creates a service.
func (h *Handler) servePutService(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Retrieve the path parameter.
	service := params.ByName("service")
	if err := ValidServiceName(service); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}

	// Read config from the request.
	config := &discoverd.ServiceConfig{}
	if err := hh.DecodeJSON(r, config); err != nil {
		hh.Error(w, err)
		return
	}

	// Add the service to the store.
	if err := h.Store.AddService(service, config); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if IsServiceExists(err) {
		hh.ObjectExistsError(w, err.Error())
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Beispiel #13
0
func (h *jobAPI) SignalJob(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	sig := ps.ByName("signal")
	if sig == "" {
		httphelper.ValidationError(w, "sig", "must not be empty")
		return
	}
	sigInt, err := strconv.Atoi(sig)
	if err != nil {
		httphelper.ValidationError(w, "sig", "must be an integer")
		return
	}
	id := ps.ByName("id")
	if err := h.host.SignalJob(id, sigInt); err != nil {
		httphelper.Error(w, err)
		return
	}
	w.WriteHeader(200)
}
Beispiel #14
0
// NOTE: this is temporary until httphelper supports custom errors
func respondWithError(w http.ResponseWriter, err error) {
	switch v := err.(type) {
	case ct.ValidationError:
		httphelper.ValidationError(w, v.Field, v.Message)
	default:
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		httphelper.Error(w, err)
	}
}
Beispiel #15
0
func (api *httpAPI) InstallHandler(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	var input *jsonInput
	if err := httphelper.DecodeJSON(req, &input); err != nil {
		httphelper.Error(w, err)
		return
	}
	api.InstallerStackMtx.Lock()
	defer api.InstallerStackMtx.Unlock()

	if len(api.InstallerStacks) > 0 {
		httphelper.ObjectExistsError(w, "install already started")
		return
	}

	var id = random.Hex(16)
	var creds aws.CredentialsProvider
	if input.Creds.AccessKeyID != "" && input.Creds.SecretAccessKey != "" {
		creds = aws.Creds(input.Creds.AccessKeyID, input.Creds.SecretAccessKey, "")
	} else {
		var err error
		creds, err = aws.EnvCreds()
		if err != nil {
			httphelper.ValidationError(w, "", err.Error())
			return
		}
	}
	s := &httpInstaller{
		ID:            id,
		PromptOutChan: make(chan *httpPrompt),
		PromptInChan:  make(chan *httpPrompt),
		logger:        log.New(),
		api:           api,
	}
	s.Stack = &Stack{
		Creds:        creds,
		Region:       input.Region,
		InstanceType: input.InstanceType,
		NumInstances: input.NumInstances,
		VpcCidr:      input.VpcCidr,
		SubnetCidr:   input.SubnetCidr,
		PromptInput:  s.PromptInput,
		YesNoPrompt:  s.YesNoPrompt,
	}
	if err := s.Stack.RunAWS(); err != nil {
		httphelper.Error(w, err)
		return
	}
	api.InstallerStacks[id] = s
	go s.handleEvents()
	httphelper.JSON(w, 200, s)
}
Beispiel #16
0
func (a *aggregatorAPI) GetLog(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	ctx, cancel := context.WithCancel(ctx)
	if cn, ok := w.(http.CloseNotifier); ok {
		go func() {
			select {
			case <-cn.CloseNotify():
				cancel()
			case <-ctx.Done():
			}
		}()
	}
	defer cancel()

	params, _ := ctxhelper.ParamsFromContext(ctx)
	channelID := params.ByName("channel_id")

	follow := false
	if strFollow := req.FormValue("follow"); strFollow == "true" {
		follow = true
	}

	lines := -1 // default to all lines
	if strLines := req.FormValue("lines"); strLines != "" {
		var err error
		lines, err = strconv.Atoi(strLines)
		if err != nil || lines < 0 || lines > 10000 {
			httphelper.ValidationError(w, "lines", "lines must be an integer between 0 and 10000")
			return
		}
	}

	filters := make(filterSlice, 0)
	if jobID := req.FormValue("job_id"); jobID != "" {
		filters = append(filters, filterJobID(jobID))
	}
	if processTypeVals, ok := req.Form["process_type"]; ok && len(processTypeVals) > 0 {
		val := processTypeVals[len(processTypeVals)-1]
		filters = append(filters, filterProcessType(val))
	}

	w.WriteHeader(200)

	var msgc <-chan *rfc5424.Message
	if follow {
		msgc = a.agg.ReadLastNAndSubscribe(channelID, lines, filters, ctx.Done())
	} else {
		msgc = a.agg.ReadLastN(channelID, lines, filters, ctx.Done())
	}
	writeMessages(ctx, w, msgc)
}
Beispiel #17
0
func (h *httpAPI) RemoveService(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	service := params.ByName("service")
	if err := ValidServiceName(service); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}
	if err := h.Store.RemoveService(params.ByName("service")); err != nil {
		if IsNotFound(err) {
			hh.ObjectNotFoundError(w, err.Error())
		} else {
			hh.Error(w, err)
		}
		return
	}
}
Beispiel #18
0
func (api *httpAPI) GetAzureSubscriptions(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	params := req.URL.Query()
	credentialID := params.Get("credential_id")
	creds, err := api.Installer.FindCredentials(credentialID)
	if err != nil {
		httphelper.ValidationError(w, "credential_id", "Invalid credential id")
		return
	}
	client := api.Installer.azureClient(creds)
	res, err := client.ListSubscriptions()
	if err != nil {
		httphelper.Error(w, err)
		return
	}
	httphelper.JSON(w, 200, res)
}
Beispiel #19
0
func (h *httpAPI) AddInstance(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	inst := &discoverd.Instance{}
	if err := json.NewDecoder(r.Body).Decode(inst); err != nil {
		hh.Error(w, err)
		return
	}
	if err := inst.Valid(); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}
	if err := h.Store.AddInstance(params.ByName("service"), inst); err != nil {
		if IsNotFound(err) {
			hh.ObjectNotFoundError(w, err.Error())
		} else {
			hh.Error(w, err)
		}
		return
	}
}
Beispiel #20
0
func (h *httpAPI) SetLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	service := params.ByName("service")
	config := h.Store.GetConfig(service)
	if config == nil || config.LeaderType != discoverd.LeaderTypeManual {
		hh.ValidationError(w, "", "service leader election type is not manual")
		return
	}

	inst := &discoverd.Instance{}
	if err := hh.DecodeJSON(r, inst); err != nil {
		hh.Error(w, err)
		return
	}

	if err := h.Store.SetLeader(service, inst.ID); err != nil {
		hh.Error(w, err)
		return
	}
}
Beispiel #21
0
func (p *pgAPI) dropDatabase(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	id := strings.SplitN(strings.TrimPrefix(req.FormValue("id"), "/databases/"), ":", 2)
	if len(id) != 2 || id[1] == "" {
		httphelper.ValidationError(w, "id", "is invalid")
		return
	}

	if err := p.db.Exec(fmt.Sprintf(`DROP DATABASE "%s"`, id[1])); err != nil {
		httphelper.Error(w, err)
		return
	}

	if err := p.db.Exec(fmt.Sprintf(`DROP USER "%s"`, id[0])); err != nil {
		httphelper.Error(w, err)
		return
	}

	w.WriteHeader(200)
}
Beispiel #22
0
func (c *controllerAPI) GetFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	if strings.Contains(req.Header.Get("Accept"), "text/event-stream") {
		c.streamFormations(ctx, w, req)
		return
	}

	if req.URL.Query().Get("active") == "true" {
		list, err := c.formationRepo.ListActive()
		if err != nil {
			respondWithError(w, err)
			return
		}
		httphelper.JSON(w, 200, list)
	}

	// don't return a list of all formations, there will be lots of them
	// and no components currently need such a list
	httphelper.ValidationError(w, "", "must either request a stream or only active formations")
}
Beispiel #23
0
func (api *API) CreateRoute(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	log, _ := ctxhelper.LoggerFromContext(ctx)

	var route *router.Route
	if err := json.NewDecoder(req.Body).Decode(&route); err != nil {
		log.Error(err.Error())
		httphelper.Error(w, err)
		return
	}

	l := api.router.ListenerFor(route.Type)
	if l == nil {
		httphelper.ValidationError(w, "type", "Invalid route type")
		return
	}

	err := l.AddRoute(route)
	if err != nil {
		rjson, jerr := json.Marshal(&route)
		if jerr != nil {
			log.Error(jerr.Error())
			httphelper.Error(w, jerr)
			return
		}
		jsonError := httphelper.JSONError{Detail: rjson}
		switch err {
		case ErrConflict:
			jsonError.Code = httphelper.ConflictErrorCode
			jsonError.Message = "Duplicate route"
		case ErrInvalid:
			jsonError.Code = httphelper.ValidationErrorCode
			jsonError.Message = "Invalid route"
		default:
			log.Error(err.Error())
			httphelper.Error(w, err)
			return
		}
		httphelper.Error(w, jsonError)
		return
	}
	httphelper.JSON(w, 200, route)
}
Beispiel #24
0
// serveDeleteService removes a service from the store by name.
func (h *Handler) serveDeleteService(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Retrieve the path parameter.
	service := params.ByName("service")
	if err := ValidServiceName(service); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}

	// Delete from the store.
	if err := h.Store.RemoveService(params.ByName("service")); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if IsNotFound(err) {
		hh.ObjectNotFoundError(w, err.Error())
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Beispiel #25
0
func (h *httpAPI) AddService(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	service := params.ByName("service")
	if err := ValidServiceName(service); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}

	config := &discoverd.ServiceConfig{}
	if err := hh.DecodeJSON(r, config); err != nil {
		hh.Error(w, err)
		return
	}

	if err := h.Store.AddService(service, config); err != nil {
		if IsServiceExists(err) {
			hh.ObjectExistsError(w, err.Error())
		} else {
			hh.Error(w, err)
		}
		return
	}
}
Beispiel #26
0
func (h *Handler) serveDeleteCluster(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// Extract release ID.
	h.Logger.Info("parsing id", "id", req.FormValue("id"))
	releaseID := strings.TrimPrefix(req.FormValue("id"), "/clusters/")
	if releaseID == "" {
		h.Logger.Error("error parsing id", "id", req.FormValue("id"))
		httphelper.ValidationError(w, "id", "is invalid")
		return
	}

	// Retrieve release.
	h.Logger.Info("retrieving release", "release.id", releaseID)
	release, err := h.ControllerClient.GetRelease(releaseID)
	if err != nil {
		h.Logger.Error("error finding release", "err", err, "release.id", releaseID)
		httphelper.Error(w, err)
		return
	}

	// Retrieve app name from env variable.
	appName := release.Env["FLYNN_REDIS"]
	if appName == "" {
		h.Logger.Error("unable to find app name", "release.id", releaseID)
		httphelper.Error(w, err)
		return
	}
	h.Logger.Info("found release app", "app.name", appName)

	// Destroy app release.
	h.Logger.Info("destroying app", "app.name", appName)
	if _, err := h.ControllerClient.DeleteApp(appName); err != nil {
		h.Logger.Error("error destroying app", "err", err)
		httphelper.Error(w, err)
		return
	}

	w.WriteHeader(200)
}
Beispiel #27
0
func (h *jobAPI) AddJob(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	// TODO(titanous): validate UUID
	id := ps.ByName("id")

	log := h.host.log.New("fn", "AddJob", "job.id", id)

	if !h.addJobRateLimitBucket.Take() {
		log.Warn("maximum concurrent AddJob calls running")
		httphelper.Error(w, httphelper.JSONError{
			Code:    httphelper.RatelimitedErrorCode,
			Message: "maximum concurrent AddJob calls running, try again later",
			Retry:   true,
		})
		return
	}

	if shutdown.IsActive() {
		log.Warn("refusing to start job due to active shutdown")
		httphelper.JSON(w, 500, struct{}{})
		h.addJobRateLimitBucket.Put()
		return
	}

	log.Info("decoding job")
	job := &host.Job{ID: id}
	if err := httphelper.DecodeJSON(r, job); err != nil {
		log.Error("error decoding job", "err", err)
		httphelper.Error(w, err)
		h.addJobRateLimitBucket.Put()
		return
	}
	if len(job.Mountspecs) == 0 {
		log.Warn("rejecting job as no mountspecs set")
		httphelper.ValidationError(w, "mountspecs", "must be set")
		h.addJobRateLimitBucket.Put()
		return
	}

	log.Info("acquiring state database")
	if err := h.host.state.Acquire(); err != nil {
		log.Error("error acquiring state database", "err", err)
		httphelper.Error(w, err)
		h.addJobRateLimitBucket.Put()
		return
	}

	if err := h.host.state.AddJob(job); err != nil {
		log.Error("error adding job to state database", "err", err)
		if err == ErrJobExists {
			httphelper.ConflictError(w, err.Error())
		} else {
			httphelper.Error(w, err)
		}
		h.addJobRateLimitBucket.Put()
		return
	}

	go func() {
		log.Info("running job")
		err := h.host.backend.Run(job, nil, h.addJobRateLimitBucket)
		h.host.state.Release()
		if err != nil {
			log.Error("error running job", "err", err)
			h.host.state.SetStatusFailed(job.ID, err)
		}
		h.addJobRateLimitBucket.Put()
	}()

	// TODO(titanous): return 201 Accepted
	httphelper.JSON(w, 200, struct{}{})
}
Beispiel #28
0
func (api *httpAPI) LaunchCluster(w http.ResponseWriter, req *http.Request, params httprouter.Params) {

	var inputJSON bytes.Buffer
	if _, err := inputJSON.ReadFrom(req.Body); err != nil {
		httphelper.Error(w, err)
		return
	}

	decodeJSON := func(dst interface{}) error {
		return json.Unmarshal(inputJSON.Bytes(), dst)
	}

	var base *BaseCluster
	if err := decodeJSON(&base); err != nil {
		httphelper.Error(w, err)
		return
	}

	if base.CredentialID == "" {
		httphelper.ValidationError(w, "credential_id", "Missing credential id")
		return
	}

	var creds *Credential
	if base.Type == "aws" && base.CredentialID == "aws_env" {
		creds = &Credential{
			ID: base.CredentialID,
		}
	} else {
		var err error
		creds, err = api.Installer.FindCredentials(base.CredentialID)
		if err != nil {
			httphelper.ValidationError(w, "credential_id", "Invalid credential id")
			return
		}
	}

	var cluster Cluster
	switch base.Type {
	case "aws":
		cluster = &AWSCluster{}
	case "digital_ocean":
		cluster = &DigitalOceanCluster{}
	case "azure":
		cluster = &AzureCluster{}
	default:
		httphelper.ValidationError(w, "type", fmt.Sprintf("Invalid type \"%s\"", base.Type))
		return
	}

	base.ID = fmt.Sprintf("flynn-%d", time.Now().Unix())
	base.State = "starting"
	base.installer = api.Installer

	if err := decodeJSON(&cluster); err != nil {
		httphelper.Error(w, err)
		return
	}

	cluster.SetBase(base)

	if err := cluster.SetCreds(creds); err != nil {
		httphelper.Error(w, err)
		return
	}

	if err := api.Installer.LaunchCluster(cluster); err != nil {
		httphelper.Error(w, err)
		return
	}
	httphelper.JSON(w, 200, base)
}
Beispiel #29
0
func (c *controllerAPI) RunJob(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	var newJob ct.NewJob
	if err := httphelper.DecodeJSON(req, &newJob); err != nil {
		respondWithError(w, err)
		return
	}

	if err := schema.Validate(newJob); err != nil {
		respondWithError(w, err)
		return
	}

	data, err := c.releaseRepo.Get(newJob.ReleaseID)
	if err != nil {
		respondWithError(w, err)
		return
	}
	release := data.(*ct.Release)
	var artifactIDs []string
	if len(newJob.ArtifactIDs) > 0 {
		artifactIDs = newJob.ArtifactIDs
	} else if len(release.ArtifactIDs) > 0 {
		artifactIDs = release.ArtifactIDs
	} else {
		httphelper.ValidationError(w, "release.ArtifactIDs", "cannot be empty")
		return
	}

	artifacts := make([]*ct.Artifact, len(artifactIDs))
	artifactList, err := c.artifactRepo.ListIDs(artifactIDs...)
	if err != nil {
		respondWithError(w, err)
		return
	}
	for i, id := range artifactIDs {
		artifacts[i] = artifactList[id]
	}

	var entrypoint ct.ImageEntrypoint
	if e := utils.GetEntrypoint(artifacts, ""); e != nil {
		entrypoint = *e
	}

	attach := strings.Contains(req.Header.Get("Upgrade"), "flynn-attach/0")

	hosts, err := c.clusterClient.Hosts()
	if err != nil {
		respondWithError(w, err)
		return
	}
	if len(hosts) == 0 {
		respondWithError(w, errors.New("no hosts found"))
		return
	}
	client := hosts[random.Math.Intn(len(hosts))]

	uuid := random.UUID()
	hostID := client.ID()
	id := cluster.GenerateJobID(hostID, uuid)
	app := c.getApp(ctx)
	env := make(map[string]string, len(entrypoint.Env)+len(release.Env)+len(newJob.Env)+4)
	env["FLYNN_APP_ID"] = app.ID
	env["FLYNN_RELEASE_ID"] = release.ID
	env["FLYNN_PROCESS_TYPE"] = ""
	env["FLYNN_JOB_ID"] = id
	for k, v := range entrypoint.Env {
		env[k] = v
	}
	if newJob.ReleaseEnv {
		for k, v := range release.Env {
			env[k] = v
		}
	}
	for k, v := range newJob.Env {
		env[k] = v
	}
	metadata := make(map[string]string, len(newJob.Meta)+3)
	for k, v := range newJob.Meta {
		metadata[k] = v
	}
	metadata["flynn-controller.app"] = app.ID
	metadata["flynn-controller.app_name"] = app.Name
	metadata["flynn-controller.release"] = release.ID
	job := &host.Job{
		ID:       id,
		Metadata: metadata,
		Config: host.ContainerConfig{
			Args:       entrypoint.Args,
			Env:        env,
			WorkingDir: entrypoint.WorkingDir,
			Uid:        entrypoint.Uid,
			Gid:        entrypoint.Gid,
			TTY:        newJob.TTY,
			Stdin:      attach,
			DisableLog: newJob.DisableLog,
		},
		Resources: newJob.Resources,
		Partition: string(newJob.Partition),
	}
	resource.SetDefaults(&job.Resources)
	if len(newJob.Args) > 0 {
		job.Config.Args = newJob.Args
	}
	utils.SetupMountspecs(job, artifacts)

	// provision data volume if required
	if newJob.Data {
		vol := &ct.VolumeReq{Path: "/data", DeleteOnStop: true}
		if _, err := utils.ProvisionVolume(vol, client, job); err != nil {
			respondWithError(w, err)
			return
		}
	}

	var attachClient cluster.AttachClient
	if attach {
		attachReq := &host.AttachReq{
			JobID:  job.ID,
			Flags:  host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream,
			Height: uint16(newJob.Lines),
			Width:  uint16(newJob.Columns),
		}
		attachClient, err = client.Attach(attachReq, true)
		if err != nil {
			respondWithError(w, fmt.Errorf("attach failed: %s", err.Error()))
			return
		}
		defer attachClient.Close()
	}

	if err := client.AddJob(job); err != nil {
		respondWithError(w, fmt.Errorf("schedule failed: %s", err.Error()))
		return
	}

	if attach {
		// TODO(titanous): This Wait could block indefinitely if something goes
		// wrong, a context should be threaded in that cancels if the client
		// goes away.
		if err := attachClient.Wait(); err != nil {
			respondWithError(w, fmt.Errorf("attach wait failed: %s", err.Error()))
			return
		}
		w.Header().Set("Connection", "upgrade")
		w.Header().Set("Upgrade", "flynn-attach/0")
		w.WriteHeader(http.StatusSwitchingProtocols)
		conn, _, err := w.(http.Hijacker).Hijack()
		if err != nil {
			panic(err)
		}
		defer conn.Close()

		done := make(chan struct{}, 2)
		cp := func(to io.Writer, from io.Reader) {
			io.Copy(to, from)
			done <- struct{}{}
		}
		go cp(conn, attachClient.Conn())
		go cp(attachClient.Conn(), conn)

		// Wait for one of the connections to be closed or interrupted. EOF is
		// framed inside the attach protocol, so a read/write error indicates
		// that we're done and should clean up.
		<-done

		return
	} else {
		httphelper.JSON(w, 200, &ct.Job{
			ID:        job.ID,
			UUID:      uuid,
			HostID:    hostID,
			ReleaseID: newJob.ReleaseID,
			Args:      newJob.Args,
		})
	}
}
Beispiel #30
0
func (c *controllerAPI) CreateDeployment(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)
	app := c.getApp(ctx)

	// TODO: wrap all of this in a transaction
	oldRelease, err := c.appRepo.GetRelease(app.ID)
	if err == ErrNotFound {
		oldRelease = &ct.Release{}
	} else if err != nil {
		respondWithError(w, err)
		return
	}
	oldFormation, err := c.formationRepo.Get(app.ID, oldRelease.ID)
	if err == ErrNotFound {
		oldFormation = &ct.Formation{}
	} else if err != nil {
		respondWithError(w, err)
		return
	}
	procCount := 0
	for _, i := range oldFormation.Processes {
		procCount += i
	}

	deployment := &ct.Deployment{
		AppID:         app.ID,
		NewReleaseID:  release.ID,
		Strategy:      app.Strategy,
		OldReleaseID:  oldRelease.ID,
		Processes:     oldFormation.Processes,
		DeployTimeout: app.DeployTimeout,
	}

	if err := schema.Validate(deployment); err != nil {
		respondWithError(w, err)
		return
	}
	if procCount == 0 {
		// immediately set app release
		if err := c.appRepo.SetRelease(app, release.ID); err != nil {
			respondWithError(w, err)
			return
		}
		now := time.Now()
		deployment.FinishedAt = &now
	}

	d, err := c.deploymentRepo.Add(deployment)
	if err != nil {
		if postgres.IsUniquenessError(err, "isolate_deploys") {
			httphelper.ValidationError(w, "", "Cannot create deploy, there is already one in progress for this app.")
			return
		}
		respondWithError(w, err)
		return
	}

	httphelper.JSON(w, 200, d)
}