func (srv *server) handleInstanceByIDTerminate(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) err := srv.i.Terminate(context.TODO(), vars["id"]) if err != nil { switch err.(type) { case jupiterbrain.VirtualMachineNotFoundError: jsonapi.Error(w, err, http.StatusNotFound) return default: srv.log.WithFields(logrus.Fields{ "err": err, "id": vars["id"], }).Error("failed to terminate instance") jsonapi.Error(w, err, http.StatusInternalServerError) return } } err = srv.db.DestroyInstance(vars["id"]) if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) }
func (srv *server) handleInstanceByIDFetch(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) instance, err := srv.i.Fetch(context.TODO(), vars["id"]) if err != nil { switch err.(type) { case jupiterbrain.VirtualMachineNotFoundError: jsonapi.Error(w, err, http.StatusNotFound) return default: srv.log.WithFields(logrus.Fields{ "err": err, "id": vars["id"], }).Error("failed to fetch instance") jsonapi.Error(w, err, http.StatusInternalServerError) return } } response := map[string][]interface{}{ "data": {MarshalInstance(instance)}, } b, err := json.MarshalIndent(response, "", " ") if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/vnd.api+json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, string(b)+"\n") }
func (srv *server) authMiddleware(w http.ResponseWriter, req *http.Request, f http.HandlerFunc) { authHeader := req.Header.Get("Authorization") srv.log.WithField("authorization", authHeader).Debug("raw authorization header") if authHeader == "" { w.Header().Set("WWW-Authenticate", "token") srv.log.WithField("request_id", req.Header.Get("X-Request-ID")).Debug("responding 401 due to empty Authorization header") jsonapi.Error(w, errors.New("token is required"), http.StatusUnauthorized) return } if authHeader != ("token "+srv.authToken) && authHeader != ("token="+srv.authToken) { jsonapi.Error(w, errors.New("incorrect token"), http.StatusUnauthorized) return } f(w, req) }
func (srv *server) handleInstanceSync(w http.ResponseWriter, req *http.Request) { instances, err := srv.i.List(context.TODO()) if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } for _, instance := range instances { instance.CreatedAt = time.Now().UTC() err = srv.db.SaveInstance(instance) if err != nil { srv.log.WithFields(logrus.Fields{ "err": err, "id": instance.ID, }).Warn("failed to save instance") continue } srv.log.WithField("id", instance.ID).Debug("synced instance") } w.WriteHeader(http.StatusNoContent) }
func (srv *server) handleInstancesCreate(w http.ResponseWriter, req *http.Request) { var requestBody map[string]map[string]string err := json.NewDecoder(req.Body).Decode(&requestBody) if err != nil { jsonapi.Error(w, err, http.StatusBadRequest) return } if requestBody["data"] == nil { jsonapi.Error(w, &jsonapi.JSONError{Status: "422", Code: "missing-field", Title: "root object must have data field"}, 422) return } if requestBody["data"]["type"] != "instances" { jsonapi.Error(w, &jsonapi.JSONError{Status: "409", Code: "incorrect-type", Title: "data must be of type instances"}, http.StatusConflict) return } if requestBody["data"]["base-image"] == "" { jsonapi.Error(w, &jsonapi.JSONError{Status: "422", Code: "missing-field", Title: "instance must have base-image field"}, 422) return } instance, err := srv.i.Start(context.TODO(), requestBody["data"]["base-image"]) if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } recoverDelete := false defer func() { if recoverDelete && instance != nil { go func() { _ = srv.i.Terminate(context.TODO(), instance.ID) }() } }() instance.CreatedAt = time.Now().UTC() err = srv.db.SaveInstance(instance) if err != nil { recoverDelete = true jsonapi.Error(w, err, http.StatusInternalServerError) return } response := map[string][]interface{}{ "data": {MarshalInstance(instance)}, } b, err := json.MarshalIndent(response, "", " ") if err != nil { recoverDelete = true jsonapi.Error(w, err, http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/vnd.api+json") w.Header().Set("Location", fmt.Sprintf("/instances/%s", instance.ID)) w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, string(b)+"\n") }
func (srv *server) handleInstancesList(w http.ResponseWriter, req *http.Request) { instances, err := srv.i.List(context.TODO()) if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } dbInstanceIDs := []string{} dbInstanceIDCreatedMap := map[string]time.Time{} applyDBFilter := false if req.FormValue("min_age") != "" { dur, err := time.ParseDuration(req.FormValue("min_age")) if err != nil { jsonapi.Error(w, err, http.StatusBadRequest) return } res, err := srv.db.FetchInstances(&databaseQuery{MinAge: dur}) if err != nil { jsonapi.Error(w, err, http.StatusBadRequest) return } srv.log.WithFields(logrus.Fields{ "n": len(res), }).Debug("retrieved instances from database") for _, r := range res { dbInstanceIDCreatedMap[r.ID] = r.CreatedAt dbInstanceIDs = append(dbInstanceIDs, r.ID) } applyDBFilter = true } response := map[string][]interface{}{ "data": make([]interface{}, 0), } if applyDBFilter { keptInstances := []*jupiterbrain.Instance{} for _, instance := range instances { for _, instID := range dbInstanceIDs { if instID == instance.ID { instance.CreatedAt = dbInstanceIDCreatedMap[instID] keptInstances = append(keptInstances, instance) } } } srv.log.WithFields(logrus.Fields{ "pre_filter": len(instances), "post_filter": len(keptInstances), }).Debug("applying known instance filter") instances = keptInstances } for _, instance := range instances { response["data"] = append(response["data"], MarshalInstance(instance)) } b, err := json.MarshalIndent(response, "", " ") if err != nil { jsonapi.Error(w, err, http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/vnd.api+json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, string(b)+"\n") }