402 lines
12 KiB
Go
402 lines
12 KiB
Go
// Copyright 2022 Cloudbase Solutions SRL
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
gErrors "github.com/cloudbase/garm-provider-common/errors"
|
|
"github.com/cloudbase/garm/apiserver/params"
|
|
runnerParams "github.com/cloudbase/garm/params"
|
|
)
|
|
|
|
// swagger:route GET /pools/{poolID}/instances instances ListPoolInstances
|
|
//
|
|
// List runner instances in a pool.
|
|
//
|
|
// Parameters:
|
|
// + name: poolID
|
|
// description: Runner pool ID.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListPoolInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
poolID, ok := vars["poolID"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No pool ID specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
instances, err := a.r.ListPoolInstances(ctx, poolID)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing pool instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /scalesets/{scalesetID}/instances instances ListScaleSetInstances
|
|
//
|
|
// List runner instances in a scale set.
|
|
//
|
|
// Parameters:
|
|
// + name: scalesetID
|
|
// description: Runner scale set ID.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListScaleSetInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
scalesetID, ok := vars["scalesetID"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No pool ID specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
id, err := strconv.ParseUint(scalesetID, 10, 64)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
|
|
handleError(ctx, w, gErrors.ErrBadRequest)
|
|
return
|
|
}
|
|
|
|
instances, err := a.r.ListScaleSetInstances(ctx, uint(id))
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing pool instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /instances/{instanceName} instances GetInstance
|
|
//
|
|
// Get runner instance by name.
|
|
//
|
|
// Parameters:
|
|
// + name: instanceName
|
|
// description: Runner instance name.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instance
|
|
// default: APIErrorResponse
|
|
func (a *APIController) GetInstanceHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
instanceName, ok := vars["instanceName"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No runner name specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
instance, err := a.r.GetInstance(ctx, instanceName)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route DELETE /instances/{instanceName} instances DeleteInstance
|
|
//
|
|
// Delete runner instance by name.
|
|
//
|
|
// Parameters:
|
|
// + name: instanceName
|
|
// description: Runner instance name.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// + name: forceRemove
|
|
// description: If true GARM will ignore any provider error when removing the runner and will continue to remove the runner from github and the GARM database.
|
|
// type: boolean
|
|
// in: query
|
|
// required: false
|
|
//
|
|
// + name: bypassGHUnauthorized
|
|
// description: If true GARM will ignore unauthorized errors returned by GitHub when removing a runner. This is useful if you want to clean up runners and your credentials have expired.
|
|
// type: boolean
|
|
// in: query
|
|
// required: false
|
|
//
|
|
// Responses:
|
|
//
|
|
// default: APIErrorResponse
|
|
func (a *APIController) DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
instanceName, ok := vars["instanceName"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No instance name specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
forceRemove, _ := strconv.ParseBool(r.URL.Query().Get("forceRemove"))
|
|
bypassGHUnauthorized, _ := strconv.ParseBool(r.URL.Query().Get("bypassGHUnauthorized"))
|
|
if err := a.r.DeleteRunner(ctx, instanceName, forceRemove, bypassGHUnauthorized); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "removing runner")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
// swagger:route GET /repositories/{repoID}/instances repositories instances ListRepoInstances
|
|
//
|
|
// List repository instances.
|
|
//
|
|
// Parameters:
|
|
// + name: repoID
|
|
// description: Repository ID.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListRepoInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
repoID, ok := vars["repoID"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No repo ID specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
instances, err := a.r.ListRepoInstances(ctx, repoID)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing pools")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /organizations/{orgID}/instances organizations instances ListOrgInstances
|
|
//
|
|
// List organization instances.
|
|
//
|
|
// Parameters:
|
|
// + name: orgID
|
|
// description: Organization ID.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListOrgInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
orgID, ok := vars["orgID"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No org ID specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
instances, err := a.r.ListOrgInstances(ctx, orgID)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /enterprises/{enterpriseID}/instances enterprises instances ListEnterpriseInstances
|
|
//
|
|
// List enterprise instances.
|
|
//
|
|
// Parameters:
|
|
// + name: enterpriseID
|
|
// description: Enterprise ID.
|
|
// type: string
|
|
// in: path
|
|
// required: true
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListEnterpriseInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
vars := mux.Vars(r)
|
|
enterpriseID, ok := vars["enterpriseID"]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
if err := json.NewEncoder(w).Encode(params.APIErrorResponse{
|
|
Error: "Bad Request",
|
|
Details: "No enterprise ID specified",
|
|
}); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
return
|
|
}
|
|
|
|
instances, err := a.r.ListEnterpriseInstances(ctx, enterpriseID)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /instances instances ListInstances
|
|
//
|
|
// Get all runners' instances.
|
|
//
|
|
// Responses:
|
|
// 200: Instances
|
|
// default: APIErrorResponse
|
|
func (a *APIController) ListAllInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
instances, err := a.r.ListAllInstances(ctx)
|
|
if err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing instances")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(instances); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
|
}
|
|
}
|
|
|
|
func (a *APIController) InstanceStatusMessageHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var updateMessage runnerParams.InstanceUpdateMessage
|
|
if err := json.NewDecoder(r.Body).Decode(&updateMessage); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
|
|
handleError(ctx, w, gErrors.ErrBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := a.r.AddInstanceStatusMessage(ctx, updateMessage); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "error saving status message")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (a *APIController) InstanceSystemInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var updateMessage runnerParams.UpdateSystemInfoParams
|
|
if err := json.NewDecoder(r.Body).Decode(&updateMessage); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
|
|
handleError(ctx, w, gErrors.ErrBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := a.r.UpdateSystemInfo(ctx, updateMessage); err != nil {
|
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "error saving status message")
|
|
handleError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|