Merge pull request #376 from gabriel-samfira/scalesets

Add scale sets
This commit is contained in:
Gabriel 2025-05-07 00:12:57 +03:00 committed by GitHub
commit e49b35d3d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
351 changed files with 17734 additions and 61881 deletions

View file

@ -106,7 +106,7 @@ $(LOCALBIN):
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
## Tool Versions
GOLANGCI_LINT_VERSION ?= v1.61.0
GOLANGCI_LINT_VERSION ?= v1.64.8
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. If wrong version is installed, it will be overwritten.

View file

@ -277,6 +277,62 @@ func (a *APIController) CreateEnterprisePoolHandler(w http.ResponseWriter, r *ht
}
}
// swagger:route POST /enterprises/{enterpriseID}/scalesets enterprises scalesets CreateEnterpriseScaleSet
//
// Create enterprise pool with the parameters given.
//
// Parameters:
// + name: enterpriseID
// description: Enterprise ID.
// type: string
// in: path
// required: true
//
// + name: Body
// description: Parameters used when creating the enterprise scale set.
// type: CreateScaleSetParams
// in: body
// required: true
//
// Responses:
// 200: ScaleSet
// default: APIErrorResponse
func (a *APIController) CreateEnterpriseScaleSetHandler(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
}
var scaleSetData runnerParams.CreateScaleSetParams
if err := json.NewDecoder(r.Body).Decode(&scaleSetData); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
scaleSet, err := a.r.CreateEntityScaleSet(ctx, runnerParams.GithubEntityTypeEnterprise, enterpriseID, scaleSetData)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "error creating enterprise scale set")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSet); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /enterprises/{enterpriseID}/pools enterprises pools ListEnterprisePools
//
// List enterprise pools.
@ -319,6 +375,48 @@ func (a *APIController) ListEnterprisePoolsHandler(w http.ResponseWriter, r *htt
}
}
// swagger:route GET /enterprises/{enterpriseID}/scalesets enterprises scalesets ListEnterpriseScaleSets
//
// List enterprise scale sets.
//
// Parameters:
// + name: enterpriseID
// description: Enterprise ID.
// type: string
// in: path
// required: true
//
// Responses:
// 200: ScaleSets
// default: APIErrorResponse
func (a *APIController) ListEnterpriseScaleSetsHandler(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
}
scaleSets, err := a.r.ListEntityScaleSets(ctx, runnerParams.GithubEntityTypeEnterprise, enterpriseID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing scale sets")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSets); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /enterprises/{enterpriseID}/pools/{poolID} enterprises pools GetEnterprisePool
//
// Get enterprise pool by ID.

View file

@ -69,6 +69,54 @@ func (a *APIController) ListPoolInstancesHandler(w http.ResponseWriter, r *http.
}
}
// 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, 32)
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.

View file

@ -287,6 +287,62 @@ func (a *APIController) CreateOrgPoolHandler(w http.ResponseWriter, r *http.Requ
}
}
// swagger:route POST /organizations/{orgID}/scalesets organizations scalesets CreateOrgScaleSet
//
// Create organization scale set with the parameters given.
//
// Parameters:
// + name: orgID
// description: Organization ID.
// type: string
// in: path
// required: true
//
// + name: Body
// description: Parameters used when creating the organization scale set.
// type: CreateScaleSetParams
// in: body
// required: true
//
// Responses:
// 200: ScaleSet
// default: APIErrorResponse
func (a *APIController) CreateOrgScaleSetHandler(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
}
var scalesetData runnerParams.CreateScaleSetParams
if err := json.NewDecoder(r.Body).Decode(&scalesetData); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
scaleSet, err := a.r.CreateEntityScaleSet(ctx, runnerParams.GithubEntityTypeOrganization, orgID, scalesetData)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "error creating organization scale set")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSet); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /organizations/{orgID}/pools organizations pools ListOrgPools
//
// List organization pools.
@ -329,6 +385,48 @@ func (a *APIController) ListOrgPoolsHandler(w http.ResponseWriter, r *http.Reque
}
}
// swagger:route GET /organizations/{orgID}/scalesets organizations scalesets ListOrgScaleSets
//
// List organization scale sets.
//
// Parameters:
// + name: orgID
// description: Organization ID.
// type: string
// in: path
// required: true
//
// Responses:
// 200: ScaleSets
// default: APIErrorResponse
func (a *APIController) ListOrgScaleSetsHandler(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
}
scaleSets, err := a.r.ListEntityScaleSets(ctx, runnerParams.GithubEntityTypeOrganization, orgID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing scale sets")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSets); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /organizations/{orgID}/pools/{poolID} organizations pools GetOrgPool
//
// Get organization pool by ID.

View file

@ -286,6 +286,62 @@ func (a *APIController) CreateRepoPoolHandler(w http.ResponseWriter, r *http.Req
}
}
// swagger:route POST /repositories/{repoID}/scalesets repositories scalesets CreateRepoScaleSet
//
// Create repository scale set with the parameters given.
//
// Parameters:
// + name: repoID
// description: Repository ID.
// type: string
// in: path
// required: true
//
// + name: Body
// description: Parameters used when creating the repository scale set.
// type: CreateScaleSetParams
// in: body
// required: true
//
// Responses:
// 200: ScaleSet
// default: APIErrorResponse
func (a *APIController) CreateRepoScaleSetHandler(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
}
var scaleSetData runnerParams.CreateScaleSetParams
if err := json.NewDecoder(r.Body).Decode(&scaleSetData); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
scaleSet, err := a.r.CreateEntityScaleSet(ctx, runnerParams.GithubEntityTypeRepository, repoID, scaleSetData)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "error creating repository scale set")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSet); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /repositories/{repoID}/pools repositories pools ListRepoPools
//
// List repository pools.
@ -328,6 +384,48 @@ func (a *APIController) ListRepoPoolsHandler(w http.ResponseWriter, r *http.Requ
}
}
// swagger:route GET /repositories/{repoID}/scalesets repositories scalesets ListRepoScaleSets
//
// List repository scale sets.
//
// Parameters:
// + name: repoID
// description: Repository ID.
// type: string
// in: path
// required: true
//
// Responses:
// 200: ScaleSets
// default: APIErrorResponse
func (a *APIController) ListRepoScaleSetsHandler(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
}
scaleSets, err := a.r.ListEntityScaleSets(ctx, runnerParams.GithubEntityTypeRepository, repoID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing scale sets")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSets); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /repositories/{repoID}/pools/{poolID} repositories pools GetRepoPool
//
// Get repository pool by ID.

View file

@ -0,0 +1,211 @@
// 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 /scalesets scalesets ListScalesets
//
// List all scalesets.
//
// Responses:
// 200: ScaleSets
// default: APIErrorResponse
func (a *APIController) ListAllScaleSetsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
scalesets, err := a.r.ListAllScaleSets(ctx)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing scale sets")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scalesets); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /scalesets/{scalesetID} scalesets GetScaleSet
//
// Get scale set by ID.
//
// Parameters:
// + name: scalesetID
// description: ID of the scale set to fetch.
// type: string
// in: path
// required: true
//
// Responses:
// 200: ScaleSet
// default: APIErrorResponse
func (a *APIController) GetScaleSetByIDHandler(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 scale set ID specified",
}); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
return
}
id, err := strconv.ParseUint(scaleSetID, 10, 32)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
scaleSet, err := a.r.GetScaleSetByID(ctx, uint(id))
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "fetching scale set")
handleError(ctx, w, err)
return
}
scaleSet.RunnerBootstrapTimeout = scaleSet.RunnerTimeout()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSet); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route DELETE /scalesets/{scalesetID} scalesets DeleteScaleSet
//
// Delete scale set by ID.
//
// Parameters:
// + name: scalesetID
// description: ID of the scale set to delete.
// type: string
// in: path
// required: true
//
// Responses:
// default: APIErrorResponse
func (a *APIController) DeleteScaleSetByIDHandler(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 scale set ID specified",
}); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
return
}
id, err := strconv.ParseUint(scalesetID, 10, 32)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if err := a.r.DeleteScaleSetByID(ctx, uint(id)); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "removing scale set")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
// swagger:route PUT /scalesets/{scalesetID} scalesets UpdateScaleSet
//
// Update scale set by ID.
//
// Parameters:
// + name: scalesetID
// description: ID of the scale set to update.
// type: string
// in: path
// required: true
//
// + name: Body
// description: Parameters to update the scale set with.
// type: UpdateScaleSetParams
// in: body
// required: true
//
// Responses:
// 200: ScaleSet
// default: APIErrorResponse
func (a *APIController) UpdateScaleSetByIDHandler(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 scale set ID specified",
}); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
return
}
id, err := strconv.ParseUint(scalesetID, 10, 32)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
var scaleSetData runnerParams.UpdateScaleSetParams
if err := json.NewDecoder(r.Body).Decode(&scaleSetData); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
scaleSet, err := a.r.UpdateScaleSetByID(ctx, uint(id), scaleSetData)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "updating scale set")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(scaleSet); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}

View file

@ -214,6 +214,25 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/pools/{poolID}/instances/", http.HandlerFunc(han.ListPoolInstancesHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/pools/{poolID}/instances", http.HandlerFunc(han.ListPoolInstancesHandler)).Methods("GET", "OPTIONS")
////////////////
// Scale sets //
////////////////
// List all pools
apiRouter.Handle("/scalesets/", http.HandlerFunc(han.ListAllScaleSetsHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/scalesets", http.HandlerFunc(han.ListAllScaleSetsHandler)).Methods("GET", "OPTIONS")
// Get one pool
apiRouter.Handle("/scalesets/{scalesetID}/", http.HandlerFunc(han.GetScaleSetByIDHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/scalesets/{scalesetID}", http.HandlerFunc(han.GetScaleSetByIDHandler)).Methods("GET", "OPTIONS")
// Delete one pool
apiRouter.Handle("/scalesets/{scalesetID}/", http.HandlerFunc(han.DeleteScaleSetByIDHandler)).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/scalesets/{scalesetID}", http.HandlerFunc(han.DeleteScaleSetByIDHandler)).Methods("DELETE", "OPTIONS")
// Update one pool
apiRouter.Handle("/scalesets/{scalesetID}/", http.HandlerFunc(han.UpdateScaleSetByIDHandler)).Methods("PUT", "OPTIONS")
apiRouter.Handle("/scalesets/{scalesetID}", http.HandlerFunc(han.UpdateScaleSetByIDHandler)).Methods("PUT", "OPTIONS")
// List pool instances
apiRouter.Handle("/scalesets/{scalesetID}/instances/", http.HandlerFunc(han.ListScaleSetInstancesHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/scalesets/{scalesetID}/instances", http.HandlerFunc(han.ListScaleSetInstancesHandler)).Methods("GET", "OPTIONS")
/////////////
// Runners //
/////////////
@ -246,6 +265,14 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/repositories/{repoID}/pools/", http.HandlerFunc(han.CreateRepoPoolHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/pools", http.HandlerFunc(han.CreateRepoPoolHandler)).Methods("POST", "OPTIONS")
// Create scale set
apiRouter.Handle("/repositories/{repoID}/scalesets/", http.HandlerFunc(han.CreateRepoScaleSetHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/scalesets", http.HandlerFunc(han.CreateRepoScaleSetHandler)).Methods("POST", "OPTIONS")
// List scale sets
apiRouter.Handle("/repositories/{repoID}/scalesets/", http.HandlerFunc(han.ListRepoScaleSetsHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/scalesets", http.HandlerFunc(han.ListRepoScaleSetsHandler)).Methods("GET", "OPTIONS")
// Repo instances list
apiRouter.Handle("/repositories/{repoID}/instances/", http.HandlerFunc(han.ListRepoInstancesHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/repositories/{repoID}/instances", http.HandlerFunc(han.ListRepoInstancesHandler)).Methods("GET", "OPTIONS")
@ -296,6 +323,14 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/organizations/{orgID}/pools/", http.HandlerFunc(han.CreateOrgPoolHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/organizations/{orgID}/pools", http.HandlerFunc(han.CreateOrgPoolHandler)).Methods("POST", "OPTIONS")
// Create org scale set
apiRouter.Handle("/organizations/{orgID}/scalesets/", http.HandlerFunc(han.CreateOrgScaleSetHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/organizations/{orgID}/scalesets", http.HandlerFunc(han.CreateOrgScaleSetHandler)).Methods("POST", "OPTIONS")
// List org scale sets
apiRouter.Handle("/organizations/{orgID}/scalesets/", http.HandlerFunc(han.ListOrgScaleSetsHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/organizations/{orgID}/scalesets", http.HandlerFunc(han.ListOrgScaleSetsHandler)).Methods("GET", "OPTIONS")
// Org instances list
apiRouter.Handle("/organizations/{orgID}/instances/", http.HandlerFunc(han.ListOrgInstancesHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/organizations/{orgID}/instances", http.HandlerFunc(han.ListOrgInstancesHandler)).Methods("GET", "OPTIONS")
@ -346,6 +381,14 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/enterprises/{enterpriseID}/pools/", http.HandlerFunc(han.CreateEnterprisePoolHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools", http.HandlerFunc(han.CreateEnterprisePoolHandler)).Methods("POST", "OPTIONS")
// Create enterprise scale sets
apiRouter.Handle("/enterprises/{enterpriseID}/scalesets/", http.HandlerFunc(han.CreateEnterpriseScaleSetHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/scalesets", http.HandlerFunc(han.CreateEnterpriseScaleSetHandler)).Methods("POST", "OPTIONS")
// List enterprise scale sets
apiRouter.Handle("/enterprises/{enterpriseID}/scalesets/", http.HandlerFunc(han.ListEnterpriseScaleSetsHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/scalesets", http.HandlerFunc(han.ListEnterpriseScaleSetsHandler)).Methods("GET", "OPTIONS")
// Enterprise instances list
apiRouter.Handle("/enterprises/{enterpriseID}/instances/", http.HandlerFunc(han.ListEnterpriseInstancesHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/instances", http.HandlerFunc(han.ListEnterpriseInstancesHandler)).Methods("GET", "OPTIONS")

View file

@ -130,6 +130,22 @@ definitions:
import:
package: github.com/cloudbase/garm/params
alias: garm_params
ScaleSets:
type: array
x-go-type:
type: ScaleSets
import:
package: github.com/cloudbase/garm/params
alias: garm_params
items:
$ref: '#/definitions/ScaleSet'
ScaleSet:
type: object
x-go-type:
type: ScaleSet
import:
package: github.com/cloudbase/garm/params
alias: garm_params
Repositories:
type: array
x-go-type:
@ -213,6 +229,13 @@ definitions:
import:
package: github.com/cloudbase/garm/params
alias: garm_params
CreateScaleSetParams:
type: object
x-go-type:
type: CreateScaleSetParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
UpdatePoolParams:
type: object
x-go-type:
@ -220,6 +243,13 @@ definitions:
import:
package: github.com/cloudbase/garm/params
alias: garm_params
UpdateScaleSetParams:
type: object
x-go-type:
type: UpdateScaleSetParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
APIErrorResponse:
type: object
x-go-type:

View file

@ -65,6 +65,13 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateRepoParams
CreateScaleSetParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateScaleSetParams
Credentials:
items:
$ref: '#/definitions/GithubCredentials'
@ -244,6 +251,22 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: Repository
ScaleSet:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: ScaleSet
ScaleSets:
items:
$ref: '#/definitions/ScaleSet'
type: array
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: ScaleSets
UpdateControllerParams:
type: object
x-go-type:
@ -279,6 +302,13 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdatePoolParams
UpdateScaleSetParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateScaleSetParams
User:
type: object
x-go-type:
@ -616,6 +646,57 @@ paths:
tags:
- enterprises
- pools
/enterprises/{enterpriseID}/scalesets:
get:
operationId: ListEnterpriseScaleSets
parameters:
- description: Enterprise ID.
in: path
name: enterpriseID
required: true
type: string
responses:
"200":
description: ScaleSets
schema:
$ref: '#/definitions/ScaleSets'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List enterprise scale sets.
tags:
- enterprises
- scalesets
post:
operationId: CreateEnterpriseScaleSet
parameters:
- description: Enterprise ID.
in: path
name: enterpriseID
required: true
type: string
- description: Parameters used when creating the enterprise scale set.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/CreateScaleSetParams'
description: Parameters used when creating the enterprise scale set.
type: object
responses:
"200":
description: ScaleSet
schema:
$ref: '#/definitions/ScaleSet'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Create enterprise pool with the parameters given.
tags:
- enterprises
- scalesets
/first-run:
post:
operationId: FirstRun
@ -1199,6 +1280,57 @@ paths:
tags:
- organizations
- pools
/organizations/{orgID}/scalesets:
get:
operationId: ListOrgScaleSets
parameters:
- description: Organization ID.
in: path
name: orgID
required: true
type: string
responses:
"200":
description: ScaleSets
schema:
$ref: '#/definitions/ScaleSets'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List organization scale sets.
tags:
- organizations
- scalesets
post:
operationId: CreateOrgScaleSet
parameters:
- description: Organization ID.
in: path
name: orgID
required: true
type: string
- description: Parameters used when creating the organization scale set.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/CreateScaleSetParams'
description: Parameters used when creating the organization scale set.
type: object
responses:
"200":
description: ScaleSet
schema:
$ref: '#/definitions/ScaleSet'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Create organization scale set with the parameters given.
tags:
- organizations
- scalesets
/organizations/{orgID}/webhook:
delete:
operationId: UninstallOrgWebhook
@ -1648,6 +1780,57 @@ paths:
tags:
- repositories
- pools
/repositories/{repoID}/scalesets:
get:
operationId: ListRepoScaleSets
parameters:
- description: Repository ID.
in: path
name: repoID
required: true
type: string
responses:
"200":
description: ScaleSets
schema:
$ref: '#/definitions/ScaleSets'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List repository scale sets.
tags:
- repositories
- scalesets
post:
operationId: CreateRepoScaleSet
parameters:
- description: Repository ID.
in: path
name: repoID
required: true
type: string
- description: Parameters used when creating the repository scale set.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/CreateScaleSetParams'
description: Parameters used when creating the repository scale set.
type: object
responses:
"200":
description: ScaleSet
schema:
$ref: '#/definitions/ScaleSet'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Create repository scale set with the parameters given.
tags:
- repositories
- scalesets
/repositories/{repoID}/webhook:
delete:
operationId: UninstallRepoWebhook
@ -1718,6 +1901,107 @@ paths:
tags:
- repositories
- hooks
/scalesets:
get:
operationId: ListScalesets
responses:
"200":
description: ScaleSets
schema:
$ref: '#/definitions/ScaleSets'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List all scalesets.
tags:
- scalesets
/scalesets/{scalesetID}:
delete:
operationId: DeleteScaleSet
parameters:
- description: ID of the scale set to delete.
in: path
name: scalesetID
required: true
type: string
responses:
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Delete scale set by ID.
tags:
- scalesets
get:
operationId: GetScaleSet
parameters:
- description: ID of the scale set to fetch.
in: path
name: scalesetID
required: true
type: string
responses:
"200":
description: ScaleSet
schema:
$ref: '#/definitions/ScaleSet'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Get scale set by ID.
tags:
- scalesets
put:
operationId: UpdateScaleSet
parameters:
- description: ID of the scale set to update.
in: path
name: scalesetID
required: true
type: string
- description: Parameters to update the scale set with.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/UpdateScaleSetParams'
description: Parameters to update the scale set with.
type: object
responses:
"200":
description: ScaleSet
schema:
$ref: '#/definitions/ScaleSet'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Update scale set by ID.
tags:
- scalesets
/scalesets/{scalesetID}/instances:
get:
operationId: ListScaleSetInstances
parameters:
- description: Runner scale set ID.
in: path
name: scalesetID
required: true
type: string
responses:
"200":
description: Instances
schema:
$ref: '#/definitions/Instances'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List runner instances in a scale set.
tags:
- instances
produces:
- application/json
security:

View file

@ -18,6 +18,7 @@ import (
"context"
"fmt"
"log/slog"
"math"
"net/http"
"strings"
"time"
@ -59,11 +60,17 @@ type instanceToken struct {
jwtSecret string
}
func (i *instanceToken) NewInstanceJWTToken(instance params.Instance, entity string, poolType params.GithubEntityType, ttlMinutes uint) (string, error) {
func (i *instanceToken) NewInstanceJWTToken(instance params.Instance, entity string, entityType params.GithubEntityType, ttlMinutes uint) (string, error) {
// Token expiration is equal to the bootstrap timeout set on the pool plus the polling
// interval garm uses to check for timed out runners. Runners that have not sent their info
// by the end of this interval are most likely failed and will be reaped by garm anyway.
expireToken := time.Now().Add(time.Duration(ttlMinutes)*time.Minute + common.PoolReapTimeoutInterval)
var ttl int
if ttlMinutes > math.MaxInt {
ttl = math.MaxInt
} else {
ttl = int(ttlMinutes)
}
expireToken := time.Now().Add(time.Duration(ttl)*time.Minute + common.PoolReapTimeoutInterval)
expires := &jwt.NumericDate{
Time: expireToken,
}
@ -75,7 +82,7 @@ func (i *instanceToken) NewInstanceJWTToken(instance params.Instance, entity str
ID: instance.ID,
Name: instance.Name,
PoolID: instance.PoolID,
Scope: poolType,
Scope: entityType,
Entity: entity,
CreateAttempt: instance.CreateAttempt,
}

619
cache/cache_test.go vendored Normal file
View file

@ -0,0 +1,619 @@
package cache
import (
"testing"
"time"
"github.com/stretchr/testify/suite"
commonParams "github.com/cloudbase/garm-provider-common/params"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
type CacheTestSuite struct {
suite.Suite
entity params.GithubEntity
}
func (c *CacheTestSuite) SetupTest() {
c.entity = params.GithubEntity{
ID: "1234",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
}
func (c *CacheTestSuite) TearDownTest() {
// Clean up the cache after each test
githubToolsCache.mux.Lock()
defer githubToolsCache.mux.Unlock()
githubToolsCache.entities = make(map[string]GithubEntityTools)
credentialsCache.cache = make(map[uint]params.GithubCredentials)
instanceCache.cache = make(map[string]params.Instance)
entityCache = &EntityCache{
entities: make(map[string]EntityItem),
}
}
func (c *CacheTestSuite) TestCacheIsInitialized() {
c.Require().NotNil(githubToolsCache)
c.Require().NotNil(credentialsCache)
c.Require().NotNil(instanceCache)
c.Require().NotNil(entityCache)
}
func (c *CacheTestSuite) TestSetCacheWorks() {
tools := []commonParams.RunnerApplicationDownload{
{
DownloadURL: garmTesting.Ptr("https://example.com"),
},
}
c.Require().NotNil(githubToolsCache)
c.Require().Len(githubToolsCache.entities, 0)
SetGithubToolsCache(c.entity, tools)
c.Require().Len(githubToolsCache.entities, 1)
cachedTools, ok := GetGithubToolsCache(c.entity)
c.Require().True(ok)
c.Require().Len(cachedTools, 1)
c.Require().Equal(tools[0].GetDownloadURL(), cachedTools[0].GetDownloadURL())
}
func (c *CacheTestSuite) TestTimedOutToolsCache() {
tools := []commonParams.RunnerApplicationDownload{
{
DownloadURL: garmTesting.Ptr("https://example.com"),
},
}
c.Require().NotNil(githubToolsCache)
c.Require().Len(githubToolsCache.entities, 0)
SetGithubToolsCache(c.entity, tools)
c.Require().Len(githubToolsCache.entities, 1)
entity := githubToolsCache.entities[c.entity.String()]
entity.updatedAt = entity.updatedAt.Add(-2 * time.Hour)
githubToolsCache.entities[c.entity.String()] = entity
cachedTools, ok := GetGithubToolsCache(c.entity)
c.Require().False(ok)
c.Require().Nil(cachedTools)
}
func (c *CacheTestSuite) TestGetInexistentCache() {
c.Require().NotNil(githubToolsCache)
c.Require().Len(githubToolsCache.entities, 0)
cachedTools, ok := GetGithubToolsCache(c.entity)
c.Require().False(ok)
c.Require().Nil(cachedTools)
}
func (c *CacheTestSuite) TestSetGithubCredentials() {
credentials := params.GithubCredentials{
ID: 1,
}
SetGithubCredentials(credentials)
cachedCreds, ok := GetGithubCredentials(1)
c.Require().True(ok)
c.Require().Equal(credentials.ID, cachedCreds.ID)
}
func (c *CacheTestSuite) TestGetGithubCredentials() {
credentials := params.GithubCredentials{
ID: 1,
}
SetGithubCredentials(credentials)
cachedCreds, ok := GetGithubCredentials(1)
c.Require().True(ok)
c.Require().Equal(credentials.ID, cachedCreds.ID)
nonExisting, ok := GetGithubCredentials(2)
c.Require().False(ok)
c.Require().Equal(params.GithubCredentials{}, nonExisting)
}
func (c *CacheTestSuite) TestDeleteGithubCredentials() {
credentials := params.GithubCredentials{
ID: 1,
}
SetGithubCredentials(credentials)
cachedCreds, ok := GetGithubCredentials(1)
c.Require().True(ok)
c.Require().Equal(credentials.ID, cachedCreds.ID)
DeleteGithubCredentials(1)
cachedCreds, ok = GetGithubCredentials(1)
c.Require().False(ok)
c.Require().Equal(params.GithubCredentials{}, cachedCreds)
}
func (c *CacheTestSuite) TestGetAllGithubCredentials() {
credentials1 := params.GithubCredentials{
ID: 1,
}
credentials2 := params.GithubCredentials{
ID: 2,
}
SetGithubCredentials(credentials1)
SetGithubCredentials(credentials2)
cachedCreds := GetAllGithubCredentials()
c.Require().Len(cachedCreds, 2)
c.Require().Contains(cachedCreds, credentials1)
c.Require().Contains(cachedCreds, credentials2)
}
func (c *CacheTestSuite) TestSetInstanceCache() {
instance := params.Instance{
Name: "test-instance",
}
SetInstanceCache(instance)
cachedInstance, ok := GetInstanceCache("test-instance")
c.Require().True(ok)
c.Require().Equal(instance.Name, cachedInstance.Name)
}
func (c *CacheTestSuite) TestGetInstanceCache() {
instance := params.Instance{
Name: "test-instance",
}
SetInstanceCache(instance)
cachedInstance, ok := GetInstanceCache("test-instance")
c.Require().True(ok)
c.Require().Equal(instance.Name, cachedInstance.Name)
nonExisting, ok := GetInstanceCache("non-existing")
c.Require().False(ok)
c.Require().Equal(params.Instance{}, nonExisting)
}
func (c *CacheTestSuite) TestDeleteInstanceCache() {
instance := params.Instance{
Name: "test-instance",
}
SetInstanceCache(instance)
cachedInstance, ok := GetInstanceCache("test-instance")
c.Require().True(ok)
c.Require().Equal(instance.Name, cachedInstance.Name)
DeleteInstanceCache("test-instance")
cachedInstance, ok = GetInstanceCache("test-instance")
c.Require().False(ok)
c.Require().Equal(params.Instance{}, cachedInstance)
}
func (c *CacheTestSuite) TestGetAllInstances() {
instance1 := params.Instance{
Name: "test-instance-1",
}
instance2 := params.Instance{
Name: "test-instance-2",
}
SetInstanceCache(instance1)
SetInstanceCache(instance2)
cachedInstances := GetAllInstancesCache()
c.Require().Len(cachedInstances, 2)
c.Require().Contains(cachedInstances, instance1)
c.Require().Contains(cachedInstances, instance2)
}
func (c *CacheTestSuite) TestGetInstancesForPool() {
instance1 := params.Instance{
Name: "test-instance-1",
PoolID: "pool-1",
}
instance2 := params.Instance{
Name: "test-instance-2",
PoolID: "pool-1",
}
instance3 := params.Instance{
Name: "test-instance-3",
PoolID: "pool-2",
}
SetInstanceCache(instance1)
SetInstanceCache(instance2)
SetInstanceCache(instance3)
cachedInstances := GetInstancesForPool("pool-1")
c.Require().Len(cachedInstances, 2)
c.Require().Contains(cachedInstances, instance1)
c.Require().Contains(cachedInstances, instance2)
cachedInstances = GetInstancesForPool("pool-2")
c.Require().Len(cachedInstances, 1)
c.Require().Contains(cachedInstances, instance3)
}
func (c *CacheTestSuite) TestGetInstancesForScaleSet() {
instance1 := params.Instance{
Name: "test-instance-1",
ScaleSetID: 1,
}
instance2 := params.Instance{
Name: "test-instance-2",
ScaleSetID: 1,
}
instance3 := params.Instance{
Name: "test-instance-3",
ScaleSetID: 2,
}
SetInstanceCache(instance1)
SetInstanceCache(instance2)
SetInstanceCache(instance3)
cachedInstances := GetInstancesForScaleSet(1)
c.Require().Len(cachedInstances, 2)
c.Require().Contains(cachedInstances, instance1)
c.Require().Contains(cachedInstances, instance2)
cachedInstances = GetInstancesForScaleSet(2)
c.Require().Len(cachedInstances, 1)
c.Require().Contains(cachedInstances, instance3)
}
func (c *CacheTestSuite) TestSetGetEntityCache() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
SetEntity(entity)
cachedEntity, ok := GetEntity("test-entity")
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
entity.Credentials.Description = "test description"
SetEntity(entity)
cachedEntity, ok = GetEntity("test-entity")
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
c.Require().Equal(entity.Credentials.Description, cachedEntity.Credentials.Description)
}
func (c *CacheTestSuite) TestReplaceEntityPools() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
Credentials: params.GithubCredentials{
ID: 1,
},
}
pool1 := params.Pool{
ID: "pool-1",
}
pool2 := params.Pool{
ID: "pool-2",
}
credentials := params.GithubCredentials{
ID: 1,
Name: "test",
}
SetGithubCredentials(credentials)
SetEntity(entity)
ReplaceEntityPools(entity.ID, []params.Pool{pool1, pool2})
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
c.Require().Equal("test", cachedEntity.Credentials.Name)
pools := GetEntityPools(entity.ID)
c.Require().Len(pools, 2)
c.Require().Contains(pools, pool1)
c.Require().Contains(pools, pool2)
}
func (c *CacheTestSuite) TestReplaceEntityScaleSets() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
scaleSet1 := params.ScaleSet{
ID: 1,
}
scaleSet2 := params.ScaleSet{
ID: 2,
}
SetEntity(entity)
ReplaceEntityScaleSets(entity.ID, map[uint]params.ScaleSet{1: scaleSet1, 2: scaleSet2})
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
scaleSets := GetEntityScaleSets(entity.ID)
c.Require().Len(scaleSets, 2)
c.Require().Contains(scaleSets, scaleSet1)
c.Require().Contains(scaleSets, scaleSet2)
}
func (c *CacheTestSuite) TestDeleteEntity() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
SetEntity(entity)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
DeleteEntity(entity.ID)
cachedEntity, ok = GetEntity(entity.ID)
c.Require().False(ok)
c.Require().Equal(params.GithubEntity{}, cachedEntity)
}
func (c *CacheTestSuite) TestSetEntityPool() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
pool := params.Pool{
ID: "pool-1",
}
SetEntity(entity)
SetEntityPool(entity.ID, pool)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
pools := GetEntityPools(entity.ID)
c.Require().Len(pools, 1)
c.Require().Contains(pools, pool)
c.Require().False(pools[0].Enabled)
pool.Enabled = true
SetEntityPool(entity.ID, pool)
cachedEntity, ok = GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
pools = GetEntityPools(entity.ID)
c.Require().Len(pools, 1)
c.Require().Contains(pools, pool)
c.Require().True(pools[0].Enabled)
}
func (c *CacheTestSuite) TestSetEntityScaleSet() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
scaleSet := params.ScaleSet{
ID: 1,
}
SetEntity(entity)
SetEntityScaleSet(entity.ID, scaleSet)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
scaleSets := GetEntityScaleSets(entity.ID)
c.Require().Len(scaleSets, 1)
c.Require().Contains(scaleSets, scaleSet)
c.Require().False(scaleSets[0].Enabled)
scaleSet.Enabled = true
SetEntityScaleSet(entity.ID, scaleSet)
scaleSets = GetEntityScaleSets(entity.ID)
c.Require().Len(scaleSets, 1)
c.Require().Contains(scaleSets, scaleSet)
c.Require().True(scaleSets[0].Enabled)
}
func (c *CacheTestSuite) TestDeleteEntityPool() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
pool := params.Pool{
ID: "pool-1",
}
SetEntity(entity)
SetEntityPool(entity.ID, pool)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
DeleteEntityPool(entity.ID, pool.ID)
pools := GetEntityPools(entity.ID)
c.Require().Len(pools, 0)
c.Require().NotContains(pools, pool)
}
func (c *CacheTestSuite) TestDeleteEntityScaleSet() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
scaleSet := params.ScaleSet{
ID: 1,
}
SetEntity(entity)
SetEntityScaleSet(entity.ID, scaleSet)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
DeleteEntityScaleSet(entity.ID, scaleSet.ID)
scaleSets := GetEntityScaleSets(entity.ID)
c.Require().Len(scaleSets, 0)
c.Require().NotContains(scaleSets, scaleSet)
}
func (c *CacheTestSuite) TestFindPoolsMatchingAllTags() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
pool1 := params.Pool{
ID: "pool-1",
Tags: []params.Tag{
{
Name: "tag1",
},
{
Name: "tag2",
},
},
}
pool2 := params.Pool{
ID: "pool-2",
Tags: []params.Tag{
{
Name: "tag1",
},
},
}
pool3 := params.Pool{
ID: "pool-3",
Tags: []params.Tag{
{
Name: "tag3",
},
},
}
SetEntity(entity)
SetEntityPool(entity.ID, pool1)
SetEntityPool(entity.ID, pool2)
SetEntityPool(entity.ID, pool3)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
pools := FindPoolsMatchingAllTags(entity.ID, []string{"tag1", "tag2"})
c.Require().Len(pools, 1)
c.Require().Contains(pools, pool1)
pools = FindPoolsMatchingAllTags(entity.ID, []string{"tag1"})
c.Require().Len(pools, 2)
c.Require().Contains(pools, pool1)
c.Require().Contains(pools, pool2)
pools = FindPoolsMatchingAllTags(entity.ID, []string{"tag3"})
c.Require().Len(pools, 1)
c.Require().Contains(pools, pool3)
pools = FindPoolsMatchingAllTags(entity.ID, []string{"tag4"})
c.Require().Len(pools, 0)
}
func (c *CacheTestSuite) TestGetEntityPools() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
pool1 := params.Pool{
ID: "pool-1",
Tags: []params.Tag{
{
Name: "tag1",
},
{
Name: "tag2",
},
},
}
pool2 := params.Pool{
ID: "pool-2",
Tags: []params.Tag{
{
Name: "tag1",
},
{
Name: "tag3",
},
},
}
SetEntity(entity)
SetEntityPool(entity.ID, pool1)
SetEntityPool(entity.ID, pool2)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
pools := GetEntityPools(entity.ID)
c.Require().Len(pools, 2)
c.Require().Contains(pools, pool1)
c.Require().Contains(pools, pool2)
}
func (c *CacheTestSuite) TestGetEntityScaleSet() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
scaleSet := params.ScaleSet{
ID: 1,
}
SetEntity(entity)
SetEntityScaleSet(entity.ID, scaleSet)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
scaleSets, ok := GetEntityScaleSet(entity.ID, scaleSet.ID)
c.Require().True(ok)
c.Require().Equal(scaleSet.ID, scaleSets.ID)
}
func (c *CacheTestSuite) TestGetEntityPool() {
entity := params.GithubEntity{
ID: "test-entity",
EntityType: params.GithubEntityTypeOrganization,
Name: "test",
Owner: "test",
}
pool := params.Pool{
ID: "pool-1",
Tags: []params.Tag{
{
Name: "tag1",
},
{
Name: "tag2",
},
},
}
SetEntity(entity)
SetEntityPool(entity.ID, pool)
cachedEntity, ok := GetEntity(entity.ID)
c.Require().True(ok)
c.Require().Equal(entity.ID, cachedEntity.ID)
poolFromCache, ok := GetEntityPool(entity.ID, pool.ID)
c.Require().True(ok)
c.Require().Equal(pool.ID, poolFromCache.ID)
}
func TestCacheTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(CacheTestSuite))
}

73
cache/credentials_cache.go vendored Normal file
View file

@ -0,0 +1,73 @@
package cache
import (
"sync"
"github.com/cloudbase/garm/params"
)
var credentialsCache *GithubCredentials
func init() {
ghCredentialsCache := &GithubCredentials{
cache: make(map[uint]params.GithubCredentials),
}
credentialsCache = ghCredentialsCache
}
type GithubCredentials struct {
mux sync.Mutex
cache map[uint]params.GithubCredentials
}
func (g *GithubCredentials) SetCredentials(credentials params.GithubCredentials) {
g.mux.Lock()
defer g.mux.Unlock()
g.cache[credentials.ID] = credentials
}
func (g *GithubCredentials) GetCredentials(id uint) (params.GithubCredentials, bool) {
g.mux.Lock()
defer g.mux.Unlock()
if creds, ok := g.cache[id]; ok {
return creds, true
}
return params.GithubCredentials{}, false
}
func (g *GithubCredentials) DeleteCredentials(id uint) {
g.mux.Lock()
defer g.mux.Unlock()
delete(g.cache, id)
}
func (g *GithubCredentials) GetAllCredentials() []params.GithubCredentials {
g.mux.Lock()
defer g.mux.Unlock()
creds := make([]params.GithubCredentials, 0, len(g.cache))
for _, cred := range g.cache {
creds = append(creds, cred)
}
return creds
}
func SetGithubCredentials(credentials params.GithubCredentials) {
credentialsCache.SetCredentials(credentials)
}
func GetGithubCredentials(id uint) (params.GithubCredentials, bool) {
return credentialsCache.GetCredentials(id)
}
func DeleteGithubCredentials(id uint) {
credentialsCache.DeleteCredentials(id)
}
func GetAllGithubCredentials() []params.GithubCredentials {
return credentialsCache.GetAllCredentials()
}

264
cache/entity_cache.go vendored Normal file
View file

@ -0,0 +1,264 @@
package cache
import (
"log/slog"
"sync"
"github.com/cloudbase/garm/params"
)
var entityCache *EntityCache
func init() {
ghEntityCache := &EntityCache{
entities: make(map[string]EntityItem),
}
entityCache = ghEntityCache
}
type EntityItem struct {
Entity params.GithubEntity
Pools map[string]params.Pool
ScaleSets map[uint]params.ScaleSet
}
type EntityCache struct {
mux sync.Mutex
// entity IDs are UUID4s. It is highly unlikely they will collide (🤞).
entities map[string]EntityItem
}
func (e *EntityCache) GetEntity(entityID string) (params.GithubEntity, bool) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
// Updating specific credential details will not update entity cache which
// uses those credentials.
// Entity credentials in the cache are only updated if you swap the creds
// on the entity. We get the updated credentials from the credentials cache.
creds, ok := GetGithubCredentials(cache.Entity.Credentials.ID)
if ok {
cache.Entity.Credentials = creds
}
return cache.Entity, true
}
return params.GithubEntity{}, false
}
func (e *EntityCache) SetEntity(entity params.GithubEntity) {
e.mux.Lock()
defer e.mux.Unlock()
_, ok := e.entities[entity.ID]
if !ok {
e.entities[entity.ID] = EntityItem{
Entity: entity,
Pools: make(map[string]params.Pool),
ScaleSets: make(map[uint]params.ScaleSet),
}
return
}
e.entities[entity.ID] = EntityItem{
Entity: entity,
}
}
func (e *EntityCache) ReplaceEntityPools(entityID string, pools []params.Pool) {
e.mux.Lock()
defer e.mux.Unlock()
cache, ok := e.entities[entityID]
if !ok {
return
}
poolsByID := map[string]params.Pool{}
for _, pool := range pools {
poolsByID[pool.ID] = pool
}
cache.Pools = poolsByID
e.entities[entityID] = cache
}
func (e *EntityCache) ReplaceEntityScaleSets(entityID string, scaleSets map[uint]params.ScaleSet) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
cache.ScaleSets = scaleSets
e.entities[entityID] = cache
}
}
func (e *EntityCache) DeleteEntity(entityID string) {
e.mux.Lock()
defer e.mux.Unlock()
delete(e.entities, entityID)
}
func (e *EntityCache) SetEntityPool(entityID string, pool params.Pool) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
cache.Pools[pool.ID] = pool
e.entities[entityID] = cache
}
}
func (e *EntityCache) SetEntityScaleSet(entityID string, scaleSet params.ScaleSet) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
cache.ScaleSets[scaleSet.ID] = scaleSet
e.entities[entityID] = cache
}
}
func (e *EntityCache) DeleteEntityPool(entityID string, poolID string) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
delete(cache.Pools, poolID)
e.entities[entityID] = cache
}
}
func (e *EntityCache) DeleteEntityScaleSet(entityID string, scaleSetID uint) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
delete(cache.ScaleSets, scaleSetID)
e.entities[entityID] = cache
}
}
func (e *EntityCache) GetEntityPool(entityID string, poolID string) (params.Pool, bool) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
if pool, ok := cache.Pools[poolID]; ok {
return pool, true
}
}
return params.Pool{}, false
}
func (e *EntityCache) GetEntityScaleSet(entityID string, scaleSetID uint) (params.ScaleSet, bool) {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
if scaleSet, ok := cache.ScaleSets[scaleSetID]; ok {
return scaleSet, true
}
}
return params.ScaleSet{}, false
}
func (e *EntityCache) FindPoolsMatchingAllTags(entityID string, tags []string) []params.Pool {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
var pools []params.Pool
slog.Debug("Finding pools matching all tags", "entityID", entityID, "tags", tags, "pools", cache.Pools)
for _, pool := range cache.Pools {
if pool.HasRequiredLabels(tags) {
pools = append(pools, pool)
}
}
return pools
}
return nil
}
func (e *EntityCache) GetEntityPools(entityID string) []params.Pool {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
var pools []params.Pool
for _, pool := range cache.Pools {
pools = append(pools, pool)
}
return pools
}
return nil
}
func (e *EntityCache) GetEntityScaleSets(entityID string) []params.ScaleSet {
e.mux.Lock()
defer e.mux.Unlock()
if cache, ok := e.entities[entityID]; ok {
var scaleSets []params.ScaleSet
for _, scaleSet := range cache.ScaleSets {
scaleSets = append(scaleSets, scaleSet)
}
return scaleSets
}
return nil
}
func GetEntity(entityID string) (params.GithubEntity, bool) {
return entityCache.GetEntity(entityID)
}
func SetEntity(entity params.GithubEntity) {
entityCache.SetEntity(entity)
}
func ReplaceEntityPools(entityID string, pools []params.Pool) {
entityCache.ReplaceEntityPools(entityID, pools)
}
func ReplaceEntityScaleSets(entityID string, scaleSets map[uint]params.ScaleSet) {
entityCache.ReplaceEntityScaleSets(entityID, scaleSets)
}
func DeleteEntity(entityID string) {
entityCache.DeleteEntity(entityID)
}
func SetEntityPool(entityID string, pool params.Pool) {
entityCache.SetEntityPool(entityID, pool)
}
func SetEntityScaleSet(entityID string, scaleSet params.ScaleSet) {
entityCache.SetEntityScaleSet(entityID, scaleSet)
}
func DeleteEntityPool(entityID string, poolID string) {
entityCache.DeleteEntityPool(entityID, poolID)
}
func DeleteEntityScaleSet(entityID string, scaleSetID uint) {
entityCache.DeleteEntityScaleSet(entityID, scaleSetID)
}
func GetEntityPool(entityID string, poolID string) (params.Pool, bool) {
return entityCache.GetEntityPool(entityID, poolID)
}
func GetEntityScaleSet(entityID string, scaleSetID uint) (params.ScaleSet, bool) {
return entityCache.GetEntityScaleSet(entityID, scaleSetID)
}
func FindPoolsMatchingAllTags(entityID string, tags []string) []params.Pool {
return entityCache.FindPoolsMatchingAllTags(entityID, tags)
}
func GetEntityPools(entityID string) []params.Pool {
return entityCache.GetEntityPools(entityID)
}
func GetEntityScaleSets(entityID string) []params.ScaleSet {
return entityCache.GetEntityScaleSets(entityID)
}

107
cache/instance_cache.go vendored Normal file
View file

@ -0,0 +1,107 @@
package cache
import (
"sync"
"github.com/cloudbase/garm/params"
)
var instanceCache *InstanceCache
func init() {
cache := &InstanceCache{
cache: make(map[string]params.Instance),
}
instanceCache = cache
}
type InstanceCache struct {
mux sync.Mutex
cache map[string]params.Instance
}
func (i *InstanceCache) SetInstance(instance params.Instance) {
i.mux.Lock()
defer i.mux.Unlock()
i.cache[instance.Name] = instance
}
func (i *InstanceCache) GetInstance(name string) (params.Instance, bool) {
i.mux.Lock()
defer i.mux.Unlock()
if instance, ok := i.cache[name]; ok {
return instance, true
}
return params.Instance{}, false
}
func (i *InstanceCache) DeleteInstance(name string) {
i.mux.Lock()
defer i.mux.Unlock()
delete(i.cache, name)
}
func (i *InstanceCache) GetAllInstances() []params.Instance {
i.mux.Lock()
defer i.mux.Unlock()
instances := make([]params.Instance, 0, len(i.cache))
for _, instance := range i.cache {
instances = append(instances, instance)
}
return instances
}
func (i *InstanceCache) GetInstancesForPool(poolID string) []params.Instance {
i.mux.Lock()
defer i.mux.Unlock()
var filteredInstances []params.Instance
for _, instance := range i.cache {
if instance.PoolID == poolID {
filteredInstances = append(filteredInstances, instance)
}
}
return filteredInstances
}
func (i *InstanceCache) GetInstancesForScaleSet(scaleSetID uint) []params.Instance {
i.mux.Lock()
defer i.mux.Unlock()
var filteredInstances []params.Instance
for _, instance := range i.cache {
if instance.ScaleSetID == scaleSetID {
filteredInstances = append(filteredInstances, instance)
}
}
return filteredInstances
}
func SetInstanceCache(instance params.Instance) {
instanceCache.SetInstance(instance)
}
func GetInstanceCache(name string) (params.Instance, bool) {
return instanceCache.GetInstance(name)
}
func DeleteInstanceCache(name string) {
instanceCache.DeleteInstance(name)
}
func GetAllInstancesCache() []params.Instance {
return instanceCache.GetAllInstances()
}
func GetInstancesForPool(poolID string) []params.Instance {
return instanceCache.GetInstancesForPool(poolID)
}
func GetInstancesForScaleSet(scaleSetID uint) []params.Instance {
return instanceCache.GetInstancesForScaleSet(scaleSetID)
}

64
cache/tools_cache.go vendored Normal file
View file

@ -0,0 +1,64 @@
package cache
import (
"sync"
"time"
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/params"
)
var githubToolsCache *GithubToolsCache
func init() {
ghToolsCache := &GithubToolsCache{
entities: make(map[string]GithubEntityTools),
}
githubToolsCache = ghToolsCache
}
type GithubEntityTools struct {
updatedAt time.Time
entity params.GithubEntity
tools []commonParams.RunnerApplicationDownload
}
type GithubToolsCache struct {
mux sync.Mutex
// entity IDs are UUID4s. It is highly unlikely they will collide (🤞).
entities map[string]GithubEntityTools
}
func (g *GithubToolsCache) Get(entity params.GithubEntity) ([]commonParams.RunnerApplicationDownload, bool) {
g.mux.Lock()
defer g.mux.Unlock()
if cache, ok := g.entities[entity.String()]; ok {
if time.Since(cache.updatedAt) > 1*time.Hour {
// Stale cache, remove it.
delete(g.entities, entity.String())
return nil, false
}
return cache.tools, true
}
return nil, false
}
func (g *GithubToolsCache) Set(entity params.GithubEntity, tools []commonParams.RunnerApplicationDownload) {
g.mux.Lock()
defer g.mux.Unlock()
g.entities[entity.String()] = GithubEntityTools{
updatedAt: time.Now(),
entity: entity,
tools: tools,
}
}
func SetGithubToolsCache(entity params.GithubEntity, tools []commonParams.RunnerApplicationDownload) {
githubToolsCache.Set(entity, tools)
}
func GetGithubToolsCache(entity params.GithubEntity) ([]commonParams.RunnerApplicationDownload, bool) {
return githubToolsCache.Get(entity)
}

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package enterprises
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
garm_params "github.com/cloudbase/garm/params"
)
// NewCreateEnterpriseScaleSetParams creates a new CreateEnterpriseScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewCreateEnterpriseScaleSetParams() *CreateEnterpriseScaleSetParams {
return &CreateEnterpriseScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewCreateEnterpriseScaleSetParamsWithTimeout creates a new CreateEnterpriseScaleSetParams object
// with the ability to set a timeout on a request.
func NewCreateEnterpriseScaleSetParamsWithTimeout(timeout time.Duration) *CreateEnterpriseScaleSetParams {
return &CreateEnterpriseScaleSetParams{
timeout: timeout,
}
}
// NewCreateEnterpriseScaleSetParamsWithContext creates a new CreateEnterpriseScaleSetParams object
// with the ability to set a context for a request.
func NewCreateEnterpriseScaleSetParamsWithContext(ctx context.Context) *CreateEnterpriseScaleSetParams {
return &CreateEnterpriseScaleSetParams{
Context: ctx,
}
}
// NewCreateEnterpriseScaleSetParamsWithHTTPClient creates a new CreateEnterpriseScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewCreateEnterpriseScaleSetParamsWithHTTPClient(client *http.Client) *CreateEnterpriseScaleSetParams {
return &CreateEnterpriseScaleSetParams{
HTTPClient: client,
}
}
/*
CreateEnterpriseScaleSetParams contains all the parameters to send to the API endpoint
for the create enterprise scale set operation.
Typically these are written to a http.Request.
*/
type CreateEnterpriseScaleSetParams struct {
/* Body.
Parameters used when creating the enterprise scale set.
*/
Body garm_params.CreateScaleSetParams
/* EnterpriseID.
Enterprise ID.
*/
EnterpriseID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the create enterprise scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateEnterpriseScaleSetParams) WithDefaults() *CreateEnterpriseScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the create enterprise scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateEnterpriseScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) WithTimeout(timeout time.Duration) *CreateEnterpriseScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) WithContext(ctx context.Context) *CreateEnterpriseScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) WithHTTPClient(client *http.Client) *CreateEnterpriseScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) WithBody(body garm_params.CreateScaleSetParams) *CreateEnterpriseScaleSetParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) SetBody(body garm_params.CreateScaleSetParams) {
o.Body = body
}
// WithEnterpriseID adds the enterpriseID to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) WithEnterpriseID(enterpriseID string) *CreateEnterpriseScaleSetParams {
o.SetEnterpriseID(enterpriseID)
return o
}
// SetEnterpriseID adds the enterpriseId to the create enterprise scale set params
func (o *CreateEnterpriseScaleSetParams) SetEnterpriseID(enterpriseID string) {
o.EnterpriseID = enterpriseID
}
// WriteToRequest writes these params to a swagger request
func (o *CreateEnterpriseScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param enterpriseID
if err := r.SetPathParam("enterpriseID", o.EnterpriseID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package enterprises
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// CreateEnterpriseScaleSetReader is a Reader for the CreateEnterpriseScaleSet structure.
type CreateEnterpriseScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *CreateEnterpriseScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewCreateEnterpriseScaleSetOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewCreateEnterpriseScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewCreateEnterpriseScaleSetOK creates a CreateEnterpriseScaleSetOK with default headers values
func NewCreateEnterpriseScaleSetOK() *CreateEnterpriseScaleSetOK {
return &CreateEnterpriseScaleSetOK{}
}
/*
CreateEnterpriseScaleSetOK describes a response with status code 200, with default header values.
ScaleSet
*/
type CreateEnterpriseScaleSetOK struct {
Payload garm_params.ScaleSet
}
// IsSuccess returns true when this create enterprise scale set o k response has a 2xx status code
func (o *CreateEnterpriseScaleSetOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this create enterprise scale set o k response has a 3xx status code
func (o *CreateEnterpriseScaleSetOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this create enterprise scale set o k response has a 4xx status code
func (o *CreateEnterpriseScaleSetOK) IsClientError() bool {
return false
}
// IsServerError returns true when this create enterprise scale set o k response has a 5xx status code
func (o *CreateEnterpriseScaleSetOK) IsServerError() bool {
return false
}
// IsCode returns true when this create enterprise scale set o k response a status code equal to that given
func (o *CreateEnterpriseScaleSetOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the create enterprise scale set o k response
func (o *CreateEnterpriseScaleSetOK) Code() int {
return 200
}
func (o *CreateEnterpriseScaleSetOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /enterprises/{enterpriseID}/scalesets][%d] createEnterpriseScaleSetOK %s", 200, payload)
}
func (o *CreateEnterpriseScaleSetOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /enterprises/{enterpriseID}/scalesets][%d] createEnterpriseScaleSetOK %s", 200, payload)
}
func (o *CreateEnterpriseScaleSetOK) GetPayload() garm_params.ScaleSet {
return o.Payload
}
func (o *CreateEnterpriseScaleSetOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewCreateEnterpriseScaleSetDefault creates a CreateEnterpriseScaleSetDefault with default headers values
func NewCreateEnterpriseScaleSetDefault(code int) *CreateEnterpriseScaleSetDefault {
return &CreateEnterpriseScaleSetDefault{
_statusCode: code,
}
}
/*
CreateEnterpriseScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type CreateEnterpriseScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this create enterprise scale set default response has a 2xx status code
func (o *CreateEnterpriseScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this create enterprise scale set default response has a 3xx status code
func (o *CreateEnterpriseScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this create enterprise scale set default response has a 4xx status code
func (o *CreateEnterpriseScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this create enterprise scale set default response has a 5xx status code
func (o *CreateEnterpriseScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this create enterprise scale set default response a status code equal to that given
func (o *CreateEnterpriseScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the create enterprise scale set default response
func (o *CreateEnterpriseScaleSetDefault) Code() int {
return o._statusCode
}
func (o *CreateEnterpriseScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /enterprises/{enterpriseID}/scalesets][%d] CreateEnterpriseScaleSet default %s", o._statusCode, payload)
}
func (o *CreateEnterpriseScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /enterprises/{enterpriseID}/scalesets][%d] CreateEnterpriseScaleSet default %s", o._statusCode, payload)
}
func (o *CreateEnterpriseScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *CreateEnterpriseScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -58,6 +58,8 @@ type ClientService interface {
CreateEnterprisePool(params *CreateEnterprisePoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateEnterprisePoolOK, error)
CreateEnterpriseScaleSet(params *CreateEnterpriseScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateEnterpriseScaleSetOK, error)
DeleteEnterprise(params *DeleteEnterpriseParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
DeleteEnterprisePool(params *DeleteEnterprisePoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
@ -70,6 +72,8 @@ type ClientService interface {
ListEnterprisePools(params *ListEnterprisePoolsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListEnterprisePoolsOK, error)
ListEnterpriseScaleSets(params *ListEnterpriseScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListEnterpriseScaleSetsOK, error)
ListEnterprises(params *ListEnterprisesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListEnterprisesOK, error)
UpdateEnterprise(params *UpdateEnterpriseParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateEnterpriseOK, error)
@ -155,6 +159,44 @@ func (a *Client) CreateEnterprisePool(params *CreateEnterprisePoolParams, authIn
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
CreateEnterpriseScaleSet creates enterprise pool with the parameters given
*/
func (a *Client) CreateEnterpriseScaleSet(params *CreateEnterpriseScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateEnterpriseScaleSetOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewCreateEnterpriseScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "CreateEnterpriseScaleSet",
Method: "POST",
PathPattern: "/enterprises/{enterpriseID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &CreateEnterpriseScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*CreateEnterpriseScaleSetOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*CreateEnterpriseScaleSetDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
DeleteEnterprise deletes enterprise by ID
*/
@ -371,6 +413,44 @@ func (a *Client) ListEnterprisePools(params *ListEnterprisePoolsParams, authInfo
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListEnterpriseScaleSets lists enterprise scale sets
*/
func (a *Client) ListEnterpriseScaleSets(params *ListEnterpriseScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListEnterpriseScaleSetsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListEnterpriseScaleSetsParams()
}
op := &runtime.ClientOperation{
ID: "ListEnterpriseScaleSets",
Method: "GET",
PathPattern: "/enterprises/{enterpriseID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListEnterpriseScaleSetsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListEnterpriseScaleSetsOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListEnterpriseScaleSetsDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListEnterprises lists all enterprises
*/

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package enterprises
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewListEnterpriseScaleSetsParams creates a new ListEnterpriseScaleSetsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListEnterpriseScaleSetsParams() *ListEnterpriseScaleSetsParams {
return &ListEnterpriseScaleSetsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListEnterpriseScaleSetsParamsWithTimeout creates a new ListEnterpriseScaleSetsParams object
// with the ability to set a timeout on a request.
func NewListEnterpriseScaleSetsParamsWithTimeout(timeout time.Duration) *ListEnterpriseScaleSetsParams {
return &ListEnterpriseScaleSetsParams{
timeout: timeout,
}
}
// NewListEnterpriseScaleSetsParamsWithContext creates a new ListEnterpriseScaleSetsParams object
// with the ability to set a context for a request.
func NewListEnterpriseScaleSetsParamsWithContext(ctx context.Context) *ListEnterpriseScaleSetsParams {
return &ListEnterpriseScaleSetsParams{
Context: ctx,
}
}
// NewListEnterpriseScaleSetsParamsWithHTTPClient creates a new ListEnterpriseScaleSetsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListEnterpriseScaleSetsParamsWithHTTPClient(client *http.Client) *ListEnterpriseScaleSetsParams {
return &ListEnterpriseScaleSetsParams{
HTTPClient: client,
}
}
/*
ListEnterpriseScaleSetsParams contains all the parameters to send to the API endpoint
for the list enterprise scale sets operation.
Typically these are written to a http.Request.
*/
type ListEnterpriseScaleSetsParams struct {
/* EnterpriseID.
Enterprise ID.
*/
EnterpriseID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list enterprise scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListEnterpriseScaleSetsParams) WithDefaults() *ListEnterpriseScaleSetsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list enterprise scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListEnterpriseScaleSetsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) WithTimeout(timeout time.Duration) *ListEnterpriseScaleSetsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) WithContext(ctx context.Context) *ListEnterpriseScaleSetsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) WithHTTPClient(client *http.Client) *ListEnterpriseScaleSetsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithEnterpriseID adds the enterpriseID to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) WithEnterpriseID(enterpriseID string) *ListEnterpriseScaleSetsParams {
o.SetEnterpriseID(enterpriseID)
return o
}
// SetEnterpriseID adds the enterpriseId to the list enterprise scale sets params
func (o *ListEnterpriseScaleSetsParams) SetEnterpriseID(enterpriseID string) {
o.EnterpriseID = enterpriseID
}
// WriteToRequest writes these params to a swagger request
func (o *ListEnterpriseScaleSetsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param enterpriseID
if err := r.SetPathParam("enterpriseID", o.EnterpriseID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package enterprises
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListEnterpriseScaleSetsReader is a Reader for the ListEnterpriseScaleSets structure.
type ListEnterpriseScaleSetsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListEnterpriseScaleSetsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListEnterpriseScaleSetsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListEnterpriseScaleSetsDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewListEnterpriseScaleSetsOK creates a ListEnterpriseScaleSetsOK with default headers values
func NewListEnterpriseScaleSetsOK() *ListEnterpriseScaleSetsOK {
return &ListEnterpriseScaleSetsOK{}
}
/*
ListEnterpriseScaleSetsOK describes a response with status code 200, with default header values.
ScaleSets
*/
type ListEnterpriseScaleSetsOK struct {
Payload garm_params.ScaleSets
}
// IsSuccess returns true when this list enterprise scale sets o k response has a 2xx status code
func (o *ListEnterpriseScaleSetsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list enterprise scale sets o k response has a 3xx status code
func (o *ListEnterpriseScaleSetsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list enterprise scale sets o k response has a 4xx status code
func (o *ListEnterpriseScaleSetsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list enterprise scale sets o k response has a 5xx status code
func (o *ListEnterpriseScaleSetsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list enterprise scale sets o k response a status code equal to that given
func (o *ListEnterpriseScaleSetsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list enterprise scale sets o k response
func (o *ListEnterpriseScaleSetsOK) Code() int {
return 200
}
func (o *ListEnterpriseScaleSetsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /enterprises/{enterpriseID}/scalesets][%d] listEnterpriseScaleSetsOK %s", 200, payload)
}
func (o *ListEnterpriseScaleSetsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /enterprises/{enterpriseID}/scalesets][%d] listEnterpriseScaleSetsOK %s", 200, payload)
}
func (o *ListEnterpriseScaleSetsOK) GetPayload() garm_params.ScaleSets {
return o.Payload
}
func (o *ListEnterpriseScaleSetsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListEnterpriseScaleSetsDefault creates a ListEnterpriseScaleSetsDefault with default headers values
func NewListEnterpriseScaleSetsDefault(code int) *ListEnterpriseScaleSetsDefault {
return &ListEnterpriseScaleSetsDefault{
_statusCode: code,
}
}
/*
ListEnterpriseScaleSetsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListEnterpriseScaleSetsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list enterprise scale sets default response has a 2xx status code
func (o *ListEnterpriseScaleSetsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list enterprise scale sets default response has a 3xx status code
func (o *ListEnterpriseScaleSetsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list enterprise scale sets default response has a 4xx status code
func (o *ListEnterpriseScaleSetsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list enterprise scale sets default response has a 5xx status code
func (o *ListEnterpriseScaleSetsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list enterprise scale sets default response a status code equal to that given
func (o *ListEnterpriseScaleSetsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list enterprise scale sets default response
func (o *ListEnterpriseScaleSetsDefault) Code() int {
return o._statusCode
}
func (o *ListEnterpriseScaleSetsDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /enterprises/{enterpriseID}/scalesets][%d] ListEnterpriseScaleSets default %s", o._statusCode, payload)
}
func (o *ListEnterpriseScaleSetsDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /enterprises/{enterpriseID}/scalesets][%d] ListEnterpriseScaleSets default %s", o._statusCode, payload)
}
func (o *ListEnterpriseScaleSetsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListEnterpriseScaleSetsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -24,6 +24,7 @@ import (
"github.com/cloudbase/garm/client/pools"
"github.com/cloudbase/garm/client/providers"
"github.com/cloudbase/garm/client/repositories"
"github.com/cloudbase/garm/client/scalesets"
)
// Default garm API HTTP client.
@ -82,6 +83,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *GarmAPI {
cli.Pools = pools.New(transport, formats)
cli.Providers = providers.New(transport, formats)
cli.Repositories = repositories.New(transport, formats)
cli.Scalesets = scalesets.New(transport, formats)
return cli
}
@ -154,6 +156,8 @@ type GarmAPI struct {
Repositories repositories.ClientService
Scalesets scalesets.ClientService
Transport runtime.ClientTransport
}
@ -174,4 +178,5 @@ func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) {
c.Pools.SetTransport(transport)
c.Providers.SetTransport(transport)
c.Repositories.SetTransport(transport)
c.Scalesets.SetTransport(transport)
}

View file

@ -62,6 +62,8 @@ type ClientService interface {
ListPoolInstances(params *ListPoolInstancesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListPoolInstancesOK, error)
ListScaleSetInstances(params *ListScaleSetInstancesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListScaleSetInstancesOK, error)
SetTransport(transport runtime.ClientTransport)
}
@ -211,6 +213,44 @@ func (a *Client) ListPoolInstances(params *ListPoolInstancesParams, authInfo run
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListScaleSetInstances lists runner instances in a scale set
*/
func (a *Client) ListScaleSetInstances(params *ListScaleSetInstancesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListScaleSetInstancesOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListScaleSetInstancesParams()
}
op := &runtime.ClientOperation{
ID: "ListScaleSetInstances",
Method: "GET",
PathPattern: "/scalesets/{scalesetID}/instances",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListScaleSetInstancesReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListScaleSetInstancesOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListScaleSetInstancesDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package instances
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewListScaleSetInstancesParams creates a new ListScaleSetInstancesParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListScaleSetInstancesParams() *ListScaleSetInstancesParams {
return &ListScaleSetInstancesParams{
timeout: cr.DefaultTimeout,
}
}
// NewListScaleSetInstancesParamsWithTimeout creates a new ListScaleSetInstancesParams object
// with the ability to set a timeout on a request.
func NewListScaleSetInstancesParamsWithTimeout(timeout time.Duration) *ListScaleSetInstancesParams {
return &ListScaleSetInstancesParams{
timeout: timeout,
}
}
// NewListScaleSetInstancesParamsWithContext creates a new ListScaleSetInstancesParams object
// with the ability to set a context for a request.
func NewListScaleSetInstancesParamsWithContext(ctx context.Context) *ListScaleSetInstancesParams {
return &ListScaleSetInstancesParams{
Context: ctx,
}
}
// NewListScaleSetInstancesParamsWithHTTPClient creates a new ListScaleSetInstancesParams object
// with the ability to set a custom HTTPClient for a request.
func NewListScaleSetInstancesParamsWithHTTPClient(client *http.Client) *ListScaleSetInstancesParams {
return &ListScaleSetInstancesParams{
HTTPClient: client,
}
}
/*
ListScaleSetInstancesParams contains all the parameters to send to the API endpoint
for the list scale set instances operation.
Typically these are written to a http.Request.
*/
type ListScaleSetInstancesParams struct {
/* ScalesetID.
Runner scale set ID.
*/
ScalesetID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list scale set instances params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListScaleSetInstancesParams) WithDefaults() *ListScaleSetInstancesParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list scale set instances params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListScaleSetInstancesParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list scale set instances params
func (o *ListScaleSetInstancesParams) WithTimeout(timeout time.Duration) *ListScaleSetInstancesParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list scale set instances params
func (o *ListScaleSetInstancesParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list scale set instances params
func (o *ListScaleSetInstancesParams) WithContext(ctx context.Context) *ListScaleSetInstancesParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list scale set instances params
func (o *ListScaleSetInstancesParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list scale set instances params
func (o *ListScaleSetInstancesParams) WithHTTPClient(client *http.Client) *ListScaleSetInstancesParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list scale set instances params
func (o *ListScaleSetInstancesParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithScalesetID adds the scalesetID to the list scale set instances params
func (o *ListScaleSetInstancesParams) WithScalesetID(scalesetID string) *ListScaleSetInstancesParams {
o.SetScalesetID(scalesetID)
return o
}
// SetScalesetID adds the scalesetId to the list scale set instances params
func (o *ListScaleSetInstancesParams) SetScalesetID(scalesetID string) {
o.ScalesetID = scalesetID
}
// WriteToRequest writes these params to a swagger request
func (o *ListScaleSetInstancesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param scalesetID
if err := r.SetPathParam("scalesetID", o.ScalesetID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package instances
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListScaleSetInstancesReader is a Reader for the ListScaleSetInstances structure.
type ListScaleSetInstancesReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListScaleSetInstancesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListScaleSetInstancesOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListScaleSetInstancesDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewListScaleSetInstancesOK creates a ListScaleSetInstancesOK with default headers values
func NewListScaleSetInstancesOK() *ListScaleSetInstancesOK {
return &ListScaleSetInstancesOK{}
}
/*
ListScaleSetInstancesOK describes a response with status code 200, with default header values.
Instances
*/
type ListScaleSetInstancesOK struct {
Payload garm_params.Instances
}
// IsSuccess returns true when this list scale set instances o k response has a 2xx status code
func (o *ListScaleSetInstancesOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list scale set instances o k response has a 3xx status code
func (o *ListScaleSetInstancesOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list scale set instances o k response has a 4xx status code
func (o *ListScaleSetInstancesOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list scale set instances o k response has a 5xx status code
func (o *ListScaleSetInstancesOK) IsServerError() bool {
return false
}
// IsCode returns true when this list scale set instances o k response a status code equal to that given
func (o *ListScaleSetInstancesOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list scale set instances o k response
func (o *ListScaleSetInstancesOK) Code() int {
return 200
}
func (o *ListScaleSetInstancesOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}/instances][%d] listScaleSetInstancesOK %s", 200, payload)
}
func (o *ListScaleSetInstancesOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}/instances][%d] listScaleSetInstancesOK %s", 200, payload)
}
func (o *ListScaleSetInstancesOK) GetPayload() garm_params.Instances {
return o.Payload
}
func (o *ListScaleSetInstancesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListScaleSetInstancesDefault creates a ListScaleSetInstancesDefault with default headers values
func NewListScaleSetInstancesDefault(code int) *ListScaleSetInstancesDefault {
return &ListScaleSetInstancesDefault{
_statusCode: code,
}
}
/*
ListScaleSetInstancesDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListScaleSetInstancesDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list scale set instances default response has a 2xx status code
func (o *ListScaleSetInstancesDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list scale set instances default response has a 3xx status code
func (o *ListScaleSetInstancesDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list scale set instances default response has a 4xx status code
func (o *ListScaleSetInstancesDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list scale set instances default response has a 5xx status code
func (o *ListScaleSetInstancesDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list scale set instances default response a status code equal to that given
func (o *ListScaleSetInstancesDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list scale set instances default response
func (o *ListScaleSetInstancesDefault) Code() int {
return o._statusCode
}
func (o *ListScaleSetInstancesDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}/instances][%d] ListScaleSetInstances default %s", o._statusCode, payload)
}
func (o *ListScaleSetInstancesDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}/instances][%d] ListScaleSetInstances default %s", o._statusCode, payload)
}
func (o *ListScaleSetInstancesDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListScaleSetInstancesDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package organizations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
garm_params "github.com/cloudbase/garm/params"
)
// NewCreateOrgScaleSetParams creates a new CreateOrgScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewCreateOrgScaleSetParams() *CreateOrgScaleSetParams {
return &CreateOrgScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewCreateOrgScaleSetParamsWithTimeout creates a new CreateOrgScaleSetParams object
// with the ability to set a timeout on a request.
func NewCreateOrgScaleSetParamsWithTimeout(timeout time.Duration) *CreateOrgScaleSetParams {
return &CreateOrgScaleSetParams{
timeout: timeout,
}
}
// NewCreateOrgScaleSetParamsWithContext creates a new CreateOrgScaleSetParams object
// with the ability to set a context for a request.
func NewCreateOrgScaleSetParamsWithContext(ctx context.Context) *CreateOrgScaleSetParams {
return &CreateOrgScaleSetParams{
Context: ctx,
}
}
// NewCreateOrgScaleSetParamsWithHTTPClient creates a new CreateOrgScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewCreateOrgScaleSetParamsWithHTTPClient(client *http.Client) *CreateOrgScaleSetParams {
return &CreateOrgScaleSetParams{
HTTPClient: client,
}
}
/*
CreateOrgScaleSetParams contains all the parameters to send to the API endpoint
for the create org scale set operation.
Typically these are written to a http.Request.
*/
type CreateOrgScaleSetParams struct {
/* Body.
Parameters used when creating the organization scale set.
*/
Body garm_params.CreateScaleSetParams
/* OrgID.
Organization ID.
*/
OrgID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the create org scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateOrgScaleSetParams) WithDefaults() *CreateOrgScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the create org scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateOrgScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the create org scale set params
func (o *CreateOrgScaleSetParams) WithTimeout(timeout time.Duration) *CreateOrgScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the create org scale set params
func (o *CreateOrgScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the create org scale set params
func (o *CreateOrgScaleSetParams) WithContext(ctx context.Context) *CreateOrgScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the create org scale set params
func (o *CreateOrgScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the create org scale set params
func (o *CreateOrgScaleSetParams) WithHTTPClient(client *http.Client) *CreateOrgScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the create org scale set params
func (o *CreateOrgScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the create org scale set params
func (o *CreateOrgScaleSetParams) WithBody(body garm_params.CreateScaleSetParams) *CreateOrgScaleSetParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the create org scale set params
func (o *CreateOrgScaleSetParams) SetBody(body garm_params.CreateScaleSetParams) {
o.Body = body
}
// WithOrgID adds the orgID to the create org scale set params
func (o *CreateOrgScaleSetParams) WithOrgID(orgID string) *CreateOrgScaleSetParams {
o.SetOrgID(orgID)
return o
}
// SetOrgID adds the orgId to the create org scale set params
func (o *CreateOrgScaleSetParams) SetOrgID(orgID string) {
o.OrgID = orgID
}
// WriteToRequest writes these params to a swagger request
func (o *CreateOrgScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param orgID
if err := r.SetPathParam("orgID", o.OrgID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package organizations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// CreateOrgScaleSetReader is a Reader for the CreateOrgScaleSet structure.
type CreateOrgScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *CreateOrgScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewCreateOrgScaleSetOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewCreateOrgScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewCreateOrgScaleSetOK creates a CreateOrgScaleSetOK with default headers values
func NewCreateOrgScaleSetOK() *CreateOrgScaleSetOK {
return &CreateOrgScaleSetOK{}
}
/*
CreateOrgScaleSetOK describes a response with status code 200, with default header values.
ScaleSet
*/
type CreateOrgScaleSetOK struct {
Payload garm_params.ScaleSet
}
// IsSuccess returns true when this create org scale set o k response has a 2xx status code
func (o *CreateOrgScaleSetOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this create org scale set o k response has a 3xx status code
func (o *CreateOrgScaleSetOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this create org scale set o k response has a 4xx status code
func (o *CreateOrgScaleSetOK) IsClientError() bool {
return false
}
// IsServerError returns true when this create org scale set o k response has a 5xx status code
func (o *CreateOrgScaleSetOK) IsServerError() bool {
return false
}
// IsCode returns true when this create org scale set o k response a status code equal to that given
func (o *CreateOrgScaleSetOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the create org scale set o k response
func (o *CreateOrgScaleSetOK) Code() int {
return 200
}
func (o *CreateOrgScaleSetOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /organizations/{orgID}/scalesets][%d] createOrgScaleSetOK %s", 200, payload)
}
func (o *CreateOrgScaleSetOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /organizations/{orgID}/scalesets][%d] createOrgScaleSetOK %s", 200, payload)
}
func (o *CreateOrgScaleSetOK) GetPayload() garm_params.ScaleSet {
return o.Payload
}
func (o *CreateOrgScaleSetOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewCreateOrgScaleSetDefault creates a CreateOrgScaleSetDefault with default headers values
func NewCreateOrgScaleSetDefault(code int) *CreateOrgScaleSetDefault {
return &CreateOrgScaleSetDefault{
_statusCode: code,
}
}
/*
CreateOrgScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type CreateOrgScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this create org scale set default response has a 2xx status code
func (o *CreateOrgScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this create org scale set default response has a 3xx status code
func (o *CreateOrgScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this create org scale set default response has a 4xx status code
func (o *CreateOrgScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this create org scale set default response has a 5xx status code
func (o *CreateOrgScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this create org scale set default response a status code equal to that given
func (o *CreateOrgScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the create org scale set default response
func (o *CreateOrgScaleSetDefault) Code() int {
return o._statusCode
}
func (o *CreateOrgScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /organizations/{orgID}/scalesets][%d] CreateOrgScaleSet default %s", o._statusCode, payload)
}
func (o *CreateOrgScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /organizations/{orgID}/scalesets][%d] CreateOrgScaleSet default %s", o._statusCode, payload)
}
func (o *CreateOrgScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *CreateOrgScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package organizations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewListOrgScaleSetsParams creates a new ListOrgScaleSetsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListOrgScaleSetsParams() *ListOrgScaleSetsParams {
return &ListOrgScaleSetsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListOrgScaleSetsParamsWithTimeout creates a new ListOrgScaleSetsParams object
// with the ability to set a timeout on a request.
func NewListOrgScaleSetsParamsWithTimeout(timeout time.Duration) *ListOrgScaleSetsParams {
return &ListOrgScaleSetsParams{
timeout: timeout,
}
}
// NewListOrgScaleSetsParamsWithContext creates a new ListOrgScaleSetsParams object
// with the ability to set a context for a request.
func NewListOrgScaleSetsParamsWithContext(ctx context.Context) *ListOrgScaleSetsParams {
return &ListOrgScaleSetsParams{
Context: ctx,
}
}
// NewListOrgScaleSetsParamsWithHTTPClient creates a new ListOrgScaleSetsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListOrgScaleSetsParamsWithHTTPClient(client *http.Client) *ListOrgScaleSetsParams {
return &ListOrgScaleSetsParams{
HTTPClient: client,
}
}
/*
ListOrgScaleSetsParams contains all the parameters to send to the API endpoint
for the list org scale sets operation.
Typically these are written to a http.Request.
*/
type ListOrgScaleSetsParams struct {
/* OrgID.
Organization ID.
*/
OrgID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list org scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListOrgScaleSetsParams) WithDefaults() *ListOrgScaleSetsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list org scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListOrgScaleSetsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list org scale sets params
func (o *ListOrgScaleSetsParams) WithTimeout(timeout time.Duration) *ListOrgScaleSetsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list org scale sets params
func (o *ListOrgScaleSetsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list org scale sets params
func (o *ListOrgScaleSetsParams) WithContext(ctx context.Context) *ListOrgScaleSetsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list org scale sets params
func (o *ListOrgScaleSetsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list org scale sets params
func (o *ListOrgScaleSetsParams) WithHTTPClient(client *http.Client) *ListOrgScaleSetsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list org scale sets params
func (o *ListOrgScaleSetsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithOrgID adds the orgID to the list org scale sets params
func (o *ListOrgScaleSetsParams) WithOrgID(orgID string) *ListOrgScaleSetsParams {
o.SetOrgID(orgID)
return o
}
// SetOrgID adds the orgId to the list org scale sets params
func (o *ListOrgScaleSetsParams) SetOrgID(orgID string) {
o.OrgID = orgID
}
// WriteToRequest writes these params to a swagger request
func (o *ListOrgScaleSetsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param orgID
if err := r.SetPathParam("orgID", o.OrgID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package organizations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListOrgScaleSetsReader is a Reader for the ListOrgScaleSets structure.
type ListOrgScaleSetsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListOrgScaleSetsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListOrgScaleSetsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListOrgScaleSetsDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewListOrgScaleSetsOK creates a ListOrgScaleSetsOK with default headers values
func NewListOrgScaleSetsOK() *ListOrgScaleSetsOK {
return &ListOrgScaleSetsOK{}
}
/*
ListOrgScaleSetsOK describes a response with status code 200, with default header values.
ScaleSets
*/
type ListOrgScaleSetsOK struct {
Payload garm_params.ScaleSets
}
// IsSuccess returns true when this list org scale sets o k response has a 2xx status code
func (o *ListOrgScaleSetsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list org scale sets o k response has a 3xx status code
func (o *ListOrgScaleSetsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list org scale sets o k response has a 4xx status code
func (o *ListOrgScaleSetsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list org scale sets o k response has a 5xx status code
func (o *ListOrgScaleSetsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list org scale sets o k response a status code equal to that given
func (o *ListOrgScaleSetsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list org scale sets o k response
func (o *ListOrgScaleSetsOK) Code() int {
return 200
}
func (o *ListOrgScaleSetsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /organizations/{orgID}/scalesets][%d] listOrgScaleSetsOK %s", 200, payload)
}
func (o *ListOrgScaleSetsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /organizations/{orgID}/scalesets][%d] listOrgScaleSetsOK %s", 200, payload)
}
func (o *ListOrgScaleSetsOK) GetPayload() garm_params.ScaleSets {
return o.Payload
}
func (o *ListOrgScaleSetsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListOrgScaleSetsDefault creates a ListOrgScaleSetsDefault with default headers values
func NewListOrgScaleSetsDefault(code int) *ListOrgScaleSetsDefault {
return &ListOrgScaleSetsDefault{
_statusCode: code,
}
}
/*
ListOrgScaleSetsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListOrgScaleSetsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list org scale sets default response has a 2xx status code
func (o *ListOrgScaleSetsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list org scale sets default response has a 3xx status code
func (o *ListOrgScaleSetsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list org scale sets default response has a 4xx status code
func (o *ListOrgScaleSetsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list org scale sets default response has a 5xx status code
func (o *ListOrgScaleSetsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list org scale sets default response a status code equal to that given
func (o *ListOrgScaleSetsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list org scale sets default response
func (o *ListOrgScaleSetsDefault) Code() int {
return o._statusCode
}
func (o *ListOrgScaleSetsDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /organizations/{orgID}/scalesets][%d] ListOrgScaleSets default %s", o._statusCode, payload)
}
func (o *ListOrgScaleSetsDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /organizations/{orgID}/scalesets][%d] ListOrgScaleSets default %s", o._statusCode, payload)
}
func (o *ListOrgScaleSetsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListOrgScaleSetsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -58,6 +58,8 @@ type ClientService interface {
CreateOrgPool(params *CreateOrgPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateOrgPoolOK, error)
CreateOrgScaleSet(params *CreateOrgScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateOrgScaleSetOK, error)
DeleteOrg(params *DeleteOrgParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
DeleteOrgPool(params *DeleteOrgPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
@ -74,6 +76,8 @@ type ClientService interface {
ListOrgPools(params *ListOrgPoolsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListOrgPoolsOK, error)
ListOrgScaleSets(params *ListOrgScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListOrgScaleSetsOK, error)
ListOrgs(params *ListOrgsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListOrgsOK, error)
UninstallOrgWebhook(params *UninstallOrgWebhookParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
@ -161,6 +165,44 @@ func (a *Client) CreateOrgPool(params *CreateOrgPoolParams, authInfo runtime.Cli
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
CreateOrgScaleSet creates organization scale set with the parameters given
*/
func (a *Client) CreateOrgScaleSet(params *CreateOrgScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateOrgScaleSetOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewCreateOrgScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "CreateOrgScaleSet",
Method: "POST",
PathPattern: "/organizations/{orgID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &CreateOrgScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*CreateOrgScaleSetOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*CreateOrgScaleSetDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
DeleteOrg deletes organization by ID
*/
@ -455,6 +497,44 @@ func (a *Client) ListOrgPools(params *ListOrgPoolsParams, authInfo runtime.Clien
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListOrgScaleSets lists organization scale sets
*/
func (a *Client) ListOrgScaleSets(params *ListOrgScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListOrgScaleSetsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListOrgScaleSetsParams()
}
op := &runtime.ClientOperation{
ID: "ListOrgScaleSets",
Method: "GET",
PathPattern: "/organizations/{orgID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListOrgScaleSetsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListOrgScaleSetsOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListOrgScaleSetsDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListOrgs lists organizations
*/

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package repositories
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
garm_params "github.com/cloudbase/garm/params"
)
// NewCreateRepoScaleSetParams creates a new CreateRepoScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewCreateRepoScaleSetParams() *CreateRepoScaleSetParams {
return &CreateRepoScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewCreateRepoScaleSetParamsWithTimeout creates a new CreateRepoScaleSetParams object
// with the ability to set a timeout on a request.
func NewCreateRepoScaleSetParamsWithTimeout(timeout time.Duration) *CreateRepoScaleSetParams {
return &CreateRepoScaleSetParams{
timeout: timeout,
}
}
// NewCreateRepoScaleSetParamsWithContext creates a new CreateRepoScaleSetParams object
// with the ability to set a context for a request.
func NewCreateRepoScaleSetParamsWithContext(ctx context.Context) *CreateRepoScaleSetParams {
return &CreateRepoScaleSetParams{
Context: ctx,
}
}
// NewCreateRepoScaleSetParamsWithHTTPClient creates a new CreateRepoScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewCreateRepoScaleSetParamsWithHTTPClient(client *http.Client) *CreateRepoScaleSetParams {
return &CreateRepoScaleSetParams{
HTTPClient: client,
}
}
/*
CreateRepoScaleSetParams contains all the parameters to send to the API endpoint
for the create repo scale set operation.
Typically these are written to a http.Request.
*/
type CreateRepoScaleSetParams struct {
/* Body.
Parameters used when creating the repository scale set.
*/
Body garm_params.CreateScaleSetParams
/* RepoID.
Repository ID.
*/
RepoID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the create repo scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateRepoScaleSetParams) WithDefaults() *CreateRepoScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the create repo scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateRepoScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the create repo scale set params
func (o *CreateRepoScaleSetParams) WithTimeout(timeout time.Duration) *CreateRepoScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the create repo scale set params
func (o *CreateRepoScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the create repo scale set params
func (o *CreateRepoScaleSetParams) WithContext(ctx context.Context) *CreateRepoScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the create repo scale set params
func (o *CreateRepoScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the create repo scale set params
func (o *CreateRepoScaleSetParams) WithHTTPClient(client *http.Client) *CreateRepoScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the create repo scale set params
func (o *CreateRepoScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the create repo scale set params
func (o *CreateRepoScaleSetParams) WithBody(body garm_params.CreateScaleSetParams) *CreateRepoScaleSetParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the create repo scale set params
func (o *CreateRepoScaleSetParams) SetBody(body garm_params.CreateScaleSetParams) {
o.Body = body
}
// WithRepoID adds the repoID to the create repo scale set params
func (o *CreateRepoScaleSetParams) WithRepoID(repoID string) *CreateRepoScaleSetParams {
o.SetRepoID(repoID)
return o
}
// SetRepoID adds the repoId to the create repo scale set params
func (o *CreateRepoScaleSetParams) SetRepoID(repoID string) {
o.RepoID = repoID
}
// WriteToRequest writes these params to a swagger request
func (o *CreateRepoScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param repoID
if err := r.SetPathParam("repoID", o.RepoID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package repositories
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// CreateRepoScaleSetReader is a Reader for the CreateRepoScaleSet structure.
type CreateRepoScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *CreateRepoScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewCreateRepoScaleSetOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewCreateRepoScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewCreateRepoScaleSetOK creates a CreateRepoScaleSetOK with default headers values
func NewCreateRepoScaleSetOK() *CreateRepoScaleSetOK {
return &CreateRepoScaleSetOK{}
}
/*
CreateRepoScaleSetOK describes a response with status code 200, with default header values.
ScaleSet
*/
type CreateRepoScaleSetOK struct {
Payload garm_params.ScaleSet
}
// IsSuccess returns true when this create repo scale set o k response has a 2xx status code
func (o *CreateRepoScaleSetOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this create repo scale set o k response has a 3xx status code
func (o *CreateRepoScaleSetOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this create repo scale set o k response has a 4xx status code
func (o *CreateRepoScaleSetOK) IsClientError() bool {
return false
}
// IsServerError returns true when this create repo scale set o k response has a 5xx status code
func (o *CreateRepoScaleSetOK) IsServerError() bool {
return false
}
// IsCode returns true when this create repo scale set o k response a status code equal to that given
func (o *CreateRepoScaleSetOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the create repo scale set o k response
func (o *CreateRepoScaleSetOK) Code() int {
return 200
}
func (o *CreateRepoScaleSetOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /repositories/{repoID}/scalesets][%d] createRepoScaleSetOK %s", 200, payload)
}
func (o *CreateRepoScaleSetOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /repositories/{repoID}/scalesets][%d] createRepoScaleSetOK %s", 200, payload)
}
func (o *CreateRepoScaleSetOK) GetPayload() garm_params.ScaleSet {
return o.Payload
}
func (o *CreateRepoScaleSetOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewCreateRepoScaleSetDefault creates a CreateRepoScaleSetDefault with default headers values
func NewCreateRepoScaleSetDefault(code int) *CreateRepoScaleSetDefault {
return &CreateRepoScaleSetDefault{
_statusCode: code,
}
}
/*
CreateRepoScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type CreateRepoScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this create repo scale set default response has a 2xx status code
func (o *CreateRepoScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this create repo scale set default response has a 3xx status code
func (o *CreateRepoScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this create repo scale set default response has a 4xx status code
func (o *CreateRepoScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this create repo scale set default response has a 5xx status code
func (o *CreateRepoScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this create repo scale set default response a status code equal to that given
func (o *CreateRepoScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the create repo scale set default response
func (o *CreateRepoScaleSetDefault) Code() int {
return o._statusCode
}
func (o *CreateRepoScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /repositories/{repoID}/scalesets][%d] CreateRepoScaleSet default %s", o._statusCode, payload)
}
func (o *CreateRepoScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /repositories/{repoID}/scalesets][%d] CreateRepoScaleSet default %s", o._statusCode, payload)
}
func (o *CreateRepoScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *CreateRepoScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package repositories
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewListRepoScaleSetsParams creates a new ListRepoScaleSetsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListRepoScaleSetsParams() *ListRepoScaleSetsParams {
return &ListRepoScaleSetsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListRepoScaleSetsParamsWithTimeout creates a new ListRepoScaleSetsParams object
// with the ability to set a timeout on a request.
func NewListRepoScaleSetsParamsWithTimeout(timeout time.Duration) *ListRepoScaleSetsParams {
return &ListRepoScaleSetsParams{
timeout: timeout,
}
}
// NewListRepoScaleSetsParamsWithContext creates a new ListRepoScaleSetsParams object
// with the ability to set a context for a request.
func NewListRepoScaleSetsParamsWithContext(ctx context.Context) *ListRepoScaleSetsParams {
return &ListRepoScaleSetsParams{
Context: ctx,
}
}
// NewListRepoScaleSetsParamsWithHTTPClient creates a new ListRepoScaleSetsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListRepoScaleSetsParamsWithHTTPClient(client *http.Client) *ListRepoScaleSetsParams {
return &ListRepoScaleSetsParams{
HTTPClient: client,
}
}
/*
ListRepoScaleSetsParams contains all the parameters to send to the API endpoint
for the list repo scale sets operation.
Typically these are written to a http.Request.
*/
type ListRepoScaleSetsParams struct {
/* RepoID.
Repository ID.
*/
RepoID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list repo scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListRepoScaleSetsParams) WithDefaults() *ListRepoScaleSetsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list repo scale sets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListRepoScaleSetsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list repo scale sets params
func (o *ListRepoScaleSetsParams) WithTimeout(timeout time.Duration) *ListRepoScaleSetsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list repo scale sets params
func (o *ListRepoScaleSetsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list repo scale sets params
func (o *ListRepoScaleSetsParams) WithContext(ctx context.Context) *ListRepoScaleSetsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list repo scale sets params
func (o *ListRepoScaleSetsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list repo scale sets params
func (o *ListRepoScaleSetsParams) WithHTTPClient(client *http.Client) *ListRepoScaleSetsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list repo scale sets params
func (o *ListRepoScaleSetsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithRepoID adds the repoID to the list repo scale sets params
func (o *ListRepoScaleSetsParams) WithRepoID(repoID string) *ListRepoScaleSetsParams {
o.SetRepoID(repoID)
return o
}
// SetRepoID adds the repoId to the list repo scale sets params
func (o *ListRepoScaleSetsParams) SetRepoID(repoID string) {
o.RepoID = repoID
}
// WriteToRequest writes these params to a swagger request
func (o *ListRepoScaleSetsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param repoID
if err := r.SetPathParam("repoID", o.RepoID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package repositories
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListRepoScaleSetsReader is a Reader for the ListRepoScaleSets structure.
type ListRepoScaleSetsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListRepoScaleSetsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListRepoScaleSetsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListRepoScaleSetsDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewListRepoScaleSetsOK creates a ListRepoScaleSetsOK with default headers values
func NewListRepoScaleSetsOK() *ListRepoScaleSetsOK {
return &ListRepoScaleSetsOK{}
}
/*
ListRepoScaleSetsOK describes a response with status code 200, with default header values.
ScaleSets
*/
type ListRepoScaleSetsOK struct {
Payload garm_params.ScaleSets
}
// IsSuccess returns true when this list repo scale sets o k response has a 2xx status code
func (o *ListRepoScaleSetsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list repo scale sets o k response has a 3xx status code
func (o *ListRepoScaleSetsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list repo scale sets o k response has a 4xx status code
func (o *ListRepoScaleSetsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list repo scale sets o k response has a 5xx status code
func (o *ListRepoScaleSetsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list repo scale sets o k response a status code equal to that given
func (o *ListRepoScaleSetsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list repo scale sets o k response
func (o *ListRepoScaleSetsOK) Code() int {
return 200
}
func (o *ListRepoScaleSetsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /repositories/{repoID}/scalesets][%d] listRepoScaleSetsOK %s", 200, payload)
}
func (o *ListRepoScaleSetsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /repositories/{repoID}/scalesets][%d] listRepoScaleSetsOK %s", 200, payload)
}
func (o *ListRepoScaleSetsOK) GetPayload() garm_params.ScaleSets {
return o.Payload
}
func (o *ListRepoScaleSetsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListRepoScaleSetsDefault creates a ListRepoScaleSetsDefault with default headers values
func NewListRepoScaleSetsDefault(code int) *ListRepoScaleSetsDefault {
return &ListRepoScaleSetsDefault{
_statusCode: code,
}
}
/*
ListRepoScaleSetsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListRepoScaleSetsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list repo scale sets default response has a 2xx status code
func (o *ListRepoScaleSetsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list repo scale sets default response has a 3xx status code
func (o *ListRepoScaleSetsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list repo scale sets default response has a 4xx status code
func (o *ListRepoScaleSetsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list repo scale sets default response has a 5xx status code
func (o *ListRepoScaleSetsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list repo scale sets default response a status code equal to that given
func (o *ListRepoScaleSetsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list repo scale sets default response
func (o *ListRepoScaleSetsDefault) Code() int {
return o._statusCode
}
func (o *ListRepoScaleSetsDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /repositories/{repoID}/scalesets][%d] ListRepoScaleSets default %s", o._statusCode, payload)
}
func (o *ListRepoScaleSetsDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /repositories/{repoID}/scalesets][%d] ListRepoScaleSets default %s", o._statusCode, payload)
}
func (o *ListRepoScaleSetsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListRepoScaleSetsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -58,6 +58,8 @@ type ClientService interface {
CreateRepoPool(params *CreateRepoPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateRepoPoolOK, error)
CreateRepoScaleSet(params *CreateRepoScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateRepoScaleSetOK, error)
DeleteRepo(params *DeleteRepoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
DeleteRepoPool(params *DeleteRepoPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
@ -74,6 +76,8 @@ type ClientService interface {
ListRepoPools(params *ListRepoPoolsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListRepoPoolsOK, error)
ListRepoScaleSets(params *ListRepoScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListRepoScaleSetsOK, error)
ListRepos(params *ListReposParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListReposOK, error)
UninstallRepoWebhook(params *UninstallRepoWebhookParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
@ -161,6 +165,44 @@ func (a *Client) CreateRepoPool(params *CreateRepoPoolParams, authInfo runtime.C
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
CreateRepoScaleSet creates repository scale set with the parameters given
*/
func (a *Client) CreateRepoScaleSet(params *CreateRepoScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateRepoScaleSetOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewCreateRepoScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "CreateRepoScaleSet",
Method: "POST",
PathPattern: "/repositories/{repoID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &CreateRepoScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*CreateRepoScaleSetOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*CreateRepoScaleSetDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
DeleteRepo deletes repository by ID
*/
@ -455,6 +497,44 @@ func (a *Client) ListRepoPools(params *ListRepoPoolsParams, authInfo runtime.Cli
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListRepoScaleSets lists repository scale sets
*/
func (a *Client) ListRepoScaleSets(params *ListRepoScaleSetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListRepoScaleSetsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListRepoScaleSetsParams()
}
op := &runtime.ClientOperation{
ID: "ListRepoScaleSets",
Method: "GET",
PathPattern: "/repositories/{repoID}/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListRepoScaleSetsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListRepoScaleSetsOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListRepoScaleSetsDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListRepos lists repositories
*/

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewDeleteScaleSetParams creates a new DeleteScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewDeleteScaleSetParams() *DeleteScaleSetParams {
return &DeleteScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewDeleteScaleSetParamsWithTimeout creates a new DeleteScaleSetParams object
// with the ability to set a timeout on a request.
func NewDeleteScaleSetParamsWithTimeout(timeout time.Duration) *DeleteScaleSetParams {
return &DeleteScaleSetParams{
timeout: timeout,
}
}
// NewDeleteScaleSetParamsWithContext creates a new DeleteScaleSetParams object
// with the ability to set a context for a request.
func NewDeleteScaleSetParamsWithContext(ctx context.Context) *DeleteScaleSetParams {
return &DeleteScaleSetParams{
Context: ctx,
}
}
// NewDeleteScaleSetParamsWithHTTPClient creates a new DeleteScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewDeleteScaleSetParamsWithHTTPClient(client *http.Client) *DeleteScaleSetParams {
return &DeleteScaleSetParams{
HTTPClient: client,
}
}
/*
DeleteScaleSetParams contains all the parameters to send to the API endpoint
for the delete scale set operation.
Typically these are written to a http.Request.
*/
type DeleteScaleSetParams struct {
/* ScalesetID.
ID of the scale set to delete.
*/
ScalesetID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the delete scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteScaleSetParams) WithDefaults() *DeleteScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the delete scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the delete scale set params
func (o *DeleteScaleSetParams) WithTimeout(timeout time.Duration) *DeleteScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the delete scale set params
func (o *DeleteScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the delete scale set params
func (o *DeleteScaleSetParams) WithContext(ctx context.Context) *DeleteScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the delete scale set params
func (o *DeleteScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the delete scale set params
func (o *DeleteScaleSetParams) WithHTTPClient(client *http.Client) *DeleteScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the delete scale set params
func (o *DeleteScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithScalesetID adds the scalesetID to the delete scale set params
func (o *DeleteScaleSetParams) WithScalesetID(scalesetID string) *DeleteScaleSetParams {
o.SetScalesetID(scalesetID)
return o
}
// SetScalesetID adds the scalesetId to the delete scale set params
func (o *DeleteScaleSetParams) SetScalesetID(scalesetID string) {
o.ScalesetID = scalesetID
}
// WriteToRequest writes these params to a swagger request
func (o *DeleteScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param scalesetID
if err := r.SetPathParam("scalesetID", o.ScalesetID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,106 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
)
// DeleteScaleSetReader is a Reader for the DeleteScaleSet structure.
type DeleteScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *DeleteScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
result := NewDeleteScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
// NewDeleteScaleSetDefault creates a DeleteScaleSetDefault with default headers values
func NewDeleteScaleSetDefault(code int) *DeleteScaleSetDefault {
return &DeleteScaleSetDefault{
_statusCode: code,
}
}
/*
DeleteScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type DeleteScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this delete scale set default response has a 2xx status code
func (o *DeleteScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this delete scale set default response has a 3xx status code
func (o *DeleteScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this delete scale set default response has a 4xx status code
func (o *DeleteScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this delete scale set default response has a 5xx status code
func (o *DeleteScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this delete scale set default response a status code equal to that given
func (o *DeleteScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the delete scale set default response
func (o *DeleteScaleSetDefault) Code() int {
return o._statusCode
}
func (o *DeleteScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[DELETE /scalesets/{scalesetID}][%d] DeleteScaleSet default %s", o._statusCode, payload)
}
func (o *DeleteScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[DELETE /scalesets/{scalesetID}][%d] DeleteScaleSet default %s", o._statusCode, payload)
}
func (o *DeleteScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *DeleteScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewGetScaleSetParams creates a new GetScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetScaleSetParams() *GetScaleSetParams {
return &GetScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetScaleSetParamsWithTimeout creates a new GetScaleSetParams object
// with the ability to set a timeout on a request.
func NewGetScaleSetParamsWithTimeout(timeout time.Duration) *GetScaleSetParams {
return &GetScaleSetParams{
timeout: timeout,
}
}
// NewGetScaleSetParamsWithContext creates a new GetScaleSetParams object
// with the ability to set a context for a request.
func NewGetScaleSetParamsWithContext(ctx context.Context) *GetScaleSetParams {
return &GetScaleSetParams{
Context: ctx,
}
}
// NewGetScaleSetParamsWithHTTPClient creates a new GetScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewGetScaleSetParamsWithHTTPClient(client *http.Client) *GetScaleSetParams {
return &GetScaleSetParams{
HTTPClient: client,
}
}
/*
GetScaleSetParams contains all the parameters to send to the API endpoint
for the get scale set operation.
Typically these are written to a http.Request.
*/
type GetScaleSetParams struct {
/* ScalesetID.
ID of the scale set to fetch.
*/
ScalesetID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetScaleSetParams) WithDefaults() *GetScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get scale set params
func (o *GetScaleSetParams) WithTimeout(timeout time.Duration) *GetScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the get scale set params
func (o *GetScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the get scale set params
func (o *GetScaleSetParams) WithContext(ctx context.Context) *GetScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the get scale set params
func (o *GetScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the get scale set params
func (o *GetScaleSetParams) WithHTTPClient(client *http.Client) *GetScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the get scale set params
func (o *GetScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithScalesetID adds the scalesetID to the get scale set params
func (o *GetScaleSetParams) WithScalesetID(scalesetID string) *GetScaleSetParams {
o.SetScalesetID(scalesetID)
return o
}
// SetScalesetID adds the scalesetId to the get scale set params
func (o *GetScaleSetParams) SetScalesetID(scalesetID string) {
o.ScalesetID = scalesetID
}
// WriteToRequest writes these params to a swagger request
func (o *GetScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param scalesetID
if err := r.SetPathParam("scalesetID", o.ScalesetID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// GetScaleSetReader is a Reader for the GetScaleSet structure.
type GetScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *GetScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewGetScaleSetOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewGetScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewGetScaleSetOK creates a GetScaleSetOK with default headers values
func NewGetScaleSetOK() *GetScaleSetOK {
return &GetScaleSetOK{}
}
/*
GetScaleSetOK describes a response with status code 200, with default header values.
ScaleSet
*/
type GetScaleSetOK struct {
Payload garm_params.ScaleSet
}
// IsSuccess returns true when this get scale set o k response has a 2xx status code
func (o *GetScaleSetOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get scale set o k response has a 3xx status code
func (o *GetScaleSetOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get scale set o k response has a 4xx status code
func (o *GetScaleSetOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get scale set o k response has a 5xx status code
func (o *GetScaleSetOK) IsServerError() bool {
return false
}
// IsCode returns true when this get scale set o k response a status code equal to that given
func (o *GetScaleSetOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the get scale set o k response
func (o *GetScaleSetOK) Code() int {
return 200
}
func (o *GetScaleSetOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}][%d] getScaleSetOK %s", 200, payload)
}
func (o *GetScaleSetOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}][%d] getScaleSetOK %s", 200, payload)
}
func (o *GetScaleSetOK) GetPayload() garm_params.ScaleSet {
return o.Payload
}
func (o *GetScaleSetOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewGetScaleSetDefault creates a GetScaleSetDefault with default headers values
func NewGetScaleSetDefault(code int) *GetScaleSetDefault {
return &GetScaleSetDefault{
_statusCode: code,
}
}
/*
GetScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type GetScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this get scale set default response has a 2xx status code
func (o *GetScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this get scale set default response has a 3xx status code
func (o *GetScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this get scale set default response has a 4xx status code
func (o *GetScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this get scale set default response has a 5xx status code
func (o *GetScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this get scale set default response a status code equal to that given
func (o *GetScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the get scale set default response
func (o *GetScaleSetDefault) Code() int {
return o._statusCode
}
func (o *GetScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}][%d] GetScaleSet default %s", o._statusCode, payload)
}
func (o *GetScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets/{scalesetID}][%d] GetScaleSet default %s", o._statusCode, payload)
}
func (o *GetScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *GetScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,128 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewListScalesetsParams creates a new ListScalesetsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListScalesetsParams() *ListScalesetsParams {
return &ListScalesetsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListScalesetsParamsWithTimeout creates a new ListScalesetsParams object
// with the ability to set a timeout on a request.
func NewListScalesetsParamsWithTimeout(timeout time.Duration) *ListScalesetsParams {
return &ListScalesetsParams{
timeout: timeout,
}
}
// NewListScalesetsParamsWithContext creates a new ListScalesetsParams object
// with the ability to set a context for a request.
func NewListScalesetsParamsWithContext(ctx context.Context) *ListScalesetsParams {
return &ListScalesetsParams{
Context: ctx,
}
}
// NewListScalesetsParamsWithHTTPClient creates a new ListScalesetsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListScalesetsParamsWithHTTPClient(client *http.Client) *ListScalesetsParams {
return &ListScalesetsParams{
HTTPClient: client,
}
}
/*
ListScalesetsParams contains all the parameters to send to the API endpoint
for the list scalesets operation.
Typically these are written to a http.Request.
*/
type ListScalesetsParams struct {
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list scalesets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListScalesetsParams) WithDefaults() *ListScalesetsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list scalesets params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListScalesetsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list scalesets params
func (o *ListScalesetsParams) WithTimeout(timeout time.Duration) *ListScalesetsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list scalesets params
func (o *ListScalesetsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list scalesets params
func (o *ListScalesetsParams) WithContext(ctx context.Context) *ListScalesetsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list scalesets params
func (o *ListScalesetsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list scalesets params
func (o *ListScalesetsParams) WithHTTPClient(client *http.Client) *ListScalesetsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list scalesets params
func (o *ListScalesetsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WriteToRequest writes these params to a swagger request
func (o *ListScalesetsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListScalesetsReader is a Reader for the ListScalesets structure.
type ListScalesetsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListScalesetsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListScalesetsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListScalesetsDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewListScalesetsOK creates a ListScalesetsOK with default headers values
func NewListScalesetsOK() *ListScalesetsOK {
return &ListScalesetsOK{}
}
/*
ListScalesetsOK describes a response with status code 200, with default header values.
ScaleSets
*/
type ListScalesetsOK struct {
Payload garm_params.ScaleSets
}
// IsSuccess returns true when this list scalesets o k response has a 2xx status code
func (o *ListScalesetsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list scalesets o k response has a 3xx status code
func (o *ListScalesetsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list scalesets o k response has a 4xx status code
func (o *ListScalesetsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list scalesets o k response has a 5xx status code
func (o *ListScalesetsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list scalesets o k response a status code equal to that given
func (o *ListScalesetsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list scalesets o k response
func (o *ListScalesetsOK) Code() int {
return 200
}
func (o *ListScalesetsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets][%d] listScalesetsOK %s", 200, payload)
}
func (o *ListScalesetsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets][%d] listScalesetsOK %s", 200, payload)
}
func (o *ListScalesetsOK) GetPayload() garm_params.ScaleSets {
return o.Payload
}
func (o *ListScalesetsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListScalesetsDefault creates a ListScalesetsDefault with default headers values
func NewListScalesetsDefault(code int) *ListScalesetsDefault {
return &ListScalesetsDefault{
_statusCode: code,
}
}
/*
ListScalesetsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListScalesetsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list scalesets default response has a 2xx status code
func (o *ListScalesetsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list scalesets default response has a 3xx status code
func (o *ListScalesetsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list scalesets default response has a 4xx status code
func (o *ListScalesetsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list scalesets default response has a 5xx status code
func (o *ListScalesetsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list scalesets default response a status code equal to that given
func (o *ListScalesetsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list scalesets default response
func (o *ListScalesetsDefault) Code() int {
return o._statusCode
}
func (o *ListScalesetsDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets][%d] ListScalesets default %s", o._statusCode, payload)
}
func (o *ListScalesetsDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /scalesets][%d] ListScalesets default %s", o._statusCode, payload)
}
func (o *ListScalesetsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListScalesetsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,217 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// New creates a new scalesets API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
// New creates a new scalesets API client with basic auth credentials.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - user: user for basic authentication header.
// - password: password for basic authentication header.
func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
return &Client{transport: transport, formats: strfmt.Default}
}
// New creates a new scalesets API client with a bearer token for authentication.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - bearerToken: bearer token for Bearer authentication header.
func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BearerToken(bearerToken)
return &Client{transport: transport, formats: strfmt.Default}
}
/*
Client for scalesets API
*/
type Client struct {
transport runtime.ClientTransport
formats strfmt.Registry
}
// ClientOption may be used to customize the behavior of Client methods.
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
DeleteScaleSet(params *DeleteScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
GetScaleSet(params *GetScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetScaleSetOK, error)
ListScalesets(params *ListScalesetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListScalesetsOK, error)
UpdateScaleSet(params *UpdateScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateScaleSetOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
DeleteScaleSet deletes scale set by ID
*/
func (a *Client) DeleteScaleSet(params *DeleteScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error {
// TODO: Validate the params before sending
if params == nil {
params = NewDeleteScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "DeleteScaleSet",
Method: "DELETE",
PathPattern: "/scalesets/{scalesetID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &DeleteScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
_, err := a.transport.Submit(op)
if err != nil {
return err
}
return nil
}
/*
GetScaleSet gets scale set by ID
*/
func (a *Client) GetScaleSet(params *GetScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetScaleSetOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "GetScaleSet",
Method: "GET",
PathPattern: "/scalesets/{scalesetID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &GetScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*GetScaleSetOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*GetScaleSetDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListScalesets lists all scalesets
*/
func (a *Client) ListScalesets(params *ListScalesetsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListScalesetsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListScalesetsParams()
}
op := &runtime.ClientOperation{
ID: "ListScalesets",
Method: "GET",
PathPattern: "/scalesets",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListScalesetsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListScalesetsOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListScalesetsDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
UpdateScaleSet updates scale set by ID
*/
func (a *Client) UpdateScaleSet(params *UpdateScaleSetParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateScaleSetOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewUpdateScaleSetParams()
}
op := &runtime.ClientOperation{
ID: "UpdateScaleSet",
Method: "PUT",
PathPattern: "/scalesets/{scalesetID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &UpdateScaleSetReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*UpdateScaleSetOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*UpdateScaleSetDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
}

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
garm_params "github.com/cloudbase/garm/params"
)
// NewUpdateScaleSetParams creates a new UpdateScaleSetParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewUpdateScaleSetParams() *UpdateScaleSetParams {
return &UpdateScaleSetParams{
timeout: cr.DefaultTimeout,
}
}
// NewUpdateScaleSetParamsWithTimeout creates a new UpdateScaleSetParams object
// with the ability to set a timeout on a request.
func NewUpdateScaleSetParamsWithTimeout(timeout time.Duration) *UpdateScaleSetParams {
return &UpdateScaleSetParams{
timeout: timeout,
}
}
// NewUpdateScaleSetParamsWithContext creates a new UpdateScaleSetParams object
// with the ability to set a context for a request.
func NewUpdateScaleSetParamsWithContext(ctx context.Context) *UpdateScaleSetParams {
return &UpdateScaleSetParams{
Context: ctx,
}
}
// NewUpdateScaleSetParamsWithHTTPClient creates a new UpdateScaleSetParams object
// with the ability to set a custom HTTPClient for a request.
func NewUpdateScaleSetParamsWithHTTPClient(client *http.Client) *UpdateScaleSetParams {
return &UpdateScaleSetParams{
HTTPClient: client,
}
}
/*
UpdateScaleSetParams contains all the parameters to send to the API endpoint
for the update scale set operation.
Typically these are written to a http.Request.
*/
type UpdateScaleSetParams struct {
/* Body.
Parameters to update the scale set with.
*/
Body garm_params.UpdateScaleSetParams
/* ScalesetID.
ID of the scale set to update.
*/
ScalesetID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the update scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateScaleSetParams) WithDefaults() *UpdateScaleSetParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the update scale set params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateScaleSetParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the update scale set params
func (o *UpdateScaleSetParams) WithTimeout(timeout time.Duration) *UpdateScaleSetParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the update scale set params
func (o *UpdateScaleSetParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the update scale set params
func (o *UpdateScaleSetParams) WithContext(ctx context.Context) *UpdateScaleSetParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the update scale set params
func (o *UpdateScaleSetParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the update scale set params
func (o *UpdateScaleSetParams) WithHTTPClient(client *http.Client) *UpdateScaleSetParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the update scale set params
func (o *UpdateScaleSetParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the update scale set params
func (o *UpdateScaleSetParams) WithBody(body garm_params.UpdateScaleSetParams) *UpdateScaleSetParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the update scale set params
func (o *UpdateScaleSetParams) SetBody(body garm_params.UpdateScaleSetParams) {
o.Body = body
}
// WithScalesetID adds the scalesetID to the update scale set params
func (o *UpdateScaleSetParams) WithScalesetID(scalesetID string) *UpdateScaleSetParams {
o.SetScalesetID(scalesetID)
return o
}
// SetScalesetID adds the scalesetId to the update scale set params
func (o *UpdateScaleSetParams) SetScalesetID(scalesetID string) {
o.ScalesetID = scalesetID
}
// WriteToRequest writes these params to a swagger request
func (o *UpdateScaleSetParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param scalesetID
if err := r.SetPathParam("scalesetID", o.ScalesetID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package scalesets
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// UpdateScaleSetReader is a Reader for the UpdateScaleSet structure.
type UpdateScaleSetReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *UpdateScaleSetReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewUpdateScaleSetOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewUpdateScaleSetDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewUpdateScaleSetOK creates a UpdateScaleSetOK with default headers values
func NewUpdateScaleSetOK() *UpdateScaleSetOK {
return &UpdateScaleSetOK{}
}
/*
UpdateScaleSetOK describes a response with status code 200, with default header values.
ScaleSet
*/
type UpdateScaleSetOK struct {
Payload garm_params.ScaleSet
}
// IsSuccess returns true when this update scale set o k response has a 2xx status code
func (o *UpdateScaleSetOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this update scale set o k response has a 3xx status code
func (o *UpdateScaleSetOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this update scale set o k response has a 4xx status code
func (o *UpdateScaleSetOK) IsClientError() bool {
return false
}
// IsServerError returns true when this update scale set o k response has a 5xx status code
func (o *UpdateScaleSetOK) IsServerError() bool {
return false
}
// IsCode returns true when this update scale set o k response a status code equal to that given
func (o *UpdateScaleSetOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the update scale set o k response
func (o *UpdateScaleSetOK) Code() int {
return 200
}
func (o *UpdateScaleSetOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /scalesets/{scalesetID}][%d] updateScaleSetOK %s", 200, payload)
}
func (o *UpdateScaleSetOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /scalesets/{scalesetID}][%d] updateScaleSetOK %s", 200, payload)
}
func (o *UpdateScaleSetOK) GetPayload() garm_params.ScaleSet {
return o.Payload
}
func (o *UpdateScaleSetOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewUpdateScaleSetDefault creates a UpdateScaleSetDefault with default headers values
func NewUpdateScaleSetDefault(code int) *UpdateScaleSetDefault {
return &UpdateScaleSetDefault{
_statusCode: code,
}
}
/*
UpdateScaleSetDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type UpdateScaleSetDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this update scale set default response has a 2xx status code
func (o *UpdateScaleSetDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this update scale set default response has a 3xx status code
func (o *UpdateScaleSetDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this update scale set default response has a 4xx status code
func (o *UpdateScaleSetDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this update scale set default response has a 5xx status code
func (o *UpdateScaleSetDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this update scale set default response a status code equal to that given
func (o *UpdateScaleSetDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the update scale set default response
func (o *UpdateScaleSetDefault) Code() int {
return o._statusCode
}
func (o *UpdateScaleSetDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /scalesets/{scalesetID}][%d] UpdateScaleSet default %s", o._statusCode, payload)
}
func (o *UpdateScaleSetDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /scalesets/{scalesetID}][%d] UpdateScaleSet default %s", o._statusCode, payload)
}
func (o *UpdateScaleSetDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *UpdateScaleSetDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -379,8 +379,6 @@ func formatOneOrganization(org params.Organization) {
t.AppendRow(table.Row{"Endpoint", org.Endpoint.Name})
t.AppendRow(table.Row{"Pool balancer type", org.GetBalancerType()})
t.AppendRow(table.Row{"Credentials", org.CredentialsName})
t.AppendRow(table.Row{"Created at", org.CreatedAt})
t.AppendRow(table.Row{"Updated at", org.UpdatedAt})
t.AppendRow(table.Row{"Pool manager running", org.PoolManagerStatus.IsRunning})
if !org.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", org.PoolManagerStatus.FailureReason})

View file

@ -493,13 +493,13 @@ func formatPools(pools []params.Pool) {
switch {
case pool.RepoID != "" && pool.RepoName != "":
belongsTo = pool.RepoName
level = "repo"
level = entityTypeRepo
case pool.OrgID != "" && pool.OrgName != "":
belongsTo = pool.OrgName
level = "org"
level = entityTypeOrg
case pool.EnterpriseID != "" && pool.EnterpriseName != "":
belongsTo = pool.EnterpriseName
level = "enterprise"
level = entityTypeEnterprise
}
row := table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, pool.Enabled}
if long {
@ -532,13 +532,13 @@ func formatOnePool(pool params.Pool) {
switch {
case pool.RepoID != "" && pool.RepoName != "":
belongsTo = pool.RepoName
level = "repo"
level = entityTypeRepo
case pool.OrgID != "" && pool.OrgName != "":
belongsTo = pool.OrgName
level = "org"
level = entityTypeOrg
case pool.EnterpriseID != "" && pool.EnterpriseName != "":
belongsTo = pool.EnterpriseName
level = "enterprise"
level = entityTypeEnterprise
}
t.AppendHeader(header)

View file

@ -31,6 +31,12 @@ import (
"github.com/cloudbase/garm/params"
)
const (
entityTypeOrg string = "org"
entityTypeRepo string = "repo"
entityTypeEnterprise string = "enterprise"
)
var (
cfg *config.Config
mgr config.Manager

View file

@ -228,14 +228,14 @@ func formatInstances(param []params.Instance, detailed bool) {
return
}
t := table.NewWriter()
header := table.Row{"Nr", "Name", "Status", "Runner Status", "Pool ID"}
header := table.Row{"Nr", "Name", "Status", "Runner Status", "Pool ID", "Scalse Set ID"}
if detailed {
header = append(header, "Created At", "Updated At", "Job Name", "Started At", "Run ID", "Repository")
}
t.AppendHeader(header)
for idx, inst := range param {
row := table.Row{idx + 1, inst.Name, inst.Status, inst.RunnerStatus, inst.PoolID}
row := table.Row{idx + 1, inst.Name, inst.Status, inst.RunnerStatus, inst.PoolID, inst.ScaleSetID}
if detailed {
row = append(row, inst.CreatedAt, inst.UpdatedAt)
if inst.Job != nil {
@ -270,7 +270,11 @@ func formatSingleInstance(instance params.Instance) {
t.AppendRow(table.Row{"OS Version", instance.OSVersion}, table.RowConfig{AutoMerge: false})
t.AppendRow(table.Row{"Status", instance.Status}, table.RowConfig{AutoMerge: false})
t.AppendRow(table.Row{"Runner Status", instance.RunnerStatus}, table.RowConfig{AutoMerge: false})
t.AppendRow(table.Row{"Pool ID", instance.PoolID}, table.RowConfig{AutoMerge: false})
if instance.PoolID != "" {
t.AppendRow(table.Row{"Pool ID", instance.PoolID}, table.RowConfig{AutoMerge: false})
} else if instance.ScaleSetID != 0 {
t.AppendRow(table.Row{"Scale Set ID", instance.ScaleSetID}, table.RowConfig{AutoMerge: false})
}
if len(instance.Addresses) > 0 {
for _, addr := range instance.Addresses {

View file

@ -0,0 +1,518 @@
// 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 cmd
import (
"fmt"
"os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
commonParams "github.com/cloudbase/garm-provider-common/params"
apiClientEnterprises "github.com/cloudbase/garm/client/enterprises"
apiClientOrgs "github.com/cloudbase/garm/client/organizations"
apiClientRepos "github.com/cloudbase/garm/client/repositories"
apiClientScaleSets "github.com/cloudbase/garm/client/scalesets"
"github.com/cloudbase/garm/cmd/garm-cli/common"
"github.com/cloudbase/garm/params"
)
var (
scalesetProvider string
scalesetMaxRunners uint
scalesetMinIdleRunners uint
scalesetRunnerPrefix string
scalesetName string
scalesetImage string
scalesetFlavor string
scalesetOSType string
scalesetOSArch string
scalesetEnabled bool
scalesetRunnerBootstrapTimeout uint
scalesetRepository string
scalesetOrganization string
scalesetEnterprise string
scalesetExtraSpecsFile string
scalesetExtraSpecs string
scalesetAll bool
scalesetGitHubRunnerGroup string
)
type scalesetPayloadGetter interface {
GetPayload() params.ScaleSet
}
type scalesetsPayloadGetter interface {
GetPayload() params.ScaleSets
}
// scalesetCmd represents the scale set command
var scalesetCmd = &cobra.Command{
Use: "scaleset",
SilenceUsage: true,
Short: "List scale sets",
Long: `Query information or perform operations on scale sets.`,
Run: nil,
}
var scalesetListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List scale sets",
Long: `List scale sets of repositories, orgs or all of the above.
This command will list scale sets from one repo, one org or all scale sets
on the system. The list flags are mutually exclusive. You must however
specify one of them.
Example:
List scalesets from one repo:
garm-cli scaleset list --repo=05e7eac6-4705-486d-89c9-0170bbb576af
List scalesets from one org:
garm-cli scaleset list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6
List scalesets from one enterprise:
garm-cli scaleset list --enterprise=a8ee4c66-e762-4cbe-a35d-175dba2c9e62
List all scalesets from all repos, orgs and enterprises:
garm-cli scaleset list --all
`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
var response scalesetsPayloadGetter
var err error
switch len(args) {
case 0:
if cmd.Flags().Changed("repo") {
listRepoScaleSetsReq := apiClientRepos.NewListRepoScaleSetsParams()
listRepoScaleSetsReq.RepoID = scalesetRepository
response, err = apiCli.Repositories.ListRepoScaleSets(listRepoScaleSetsReq, authToken)
} else if cmd.Flags().Changed("org") {
listOrgScaleSetsReq := apiClientOrgs.NewListOrgScaleSetsParams()
listOrgScaleSetsReq.OrgID = scalesetOrganization
response, err = apiCli.Organizations.ListOrgScaleSets(listOrgScaleSetsReq, authToken)
} else if cmd.Flags().Changed("enterprise") {
listEnterpriseScaleSetsReq := apiClientEnterprises.NewListEnterpriseScaleSetsParams()
listEnterpriseScaleSetsReq.EnterpriseID = scalesetEnterprise
response, err = apiCli.Enterprises.ListEnterpriseScaleSets(listEnterpriseScaleSetsReq, authToken)
} else if cmd.Flags().Changed("all") {
listScaleSetsReq := apiClientScaleSets.NewListScalesetsParams()
response, err = apiCli.Scalesets.ListScalesets(listScaleSetsReq, authToken)
} else {
cmd.Help() //nolint
os.Exit(0)
}
default:
cmd.Help() //nolint
os.Exit(0)
}
if err != nil {
return err
}
formatScaleSets(response.GetPayload())
return nil
},
}
var scaleSetShowCmd = &cobra.Command{
Use: "show",
Short: "Show details for a scale set",
Long: `Displays a detailed view of a single scale set.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a scale set ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
getScaleSetReq := apiClientScaleSets.NewGetScaleSetParams()
getScaleSetReq.ScalesetID = args[0]
response, err := apiCli.Scalesets.GetScaleSet(getScaleSetReq, authToken)
if err != nil {
return err
}
formatOneScaleSet(response.Payload)
return nil
},
}
var scaleSetDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Delete scale set by ID",
Long: `Delete one scale set by referencing it's ID, regardless of repo or org.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a scale set ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
deleteScaleSetReq := apiClientScaleSets.NewDeleteScaleSetParams()
deleteScaleSetReq.ScalesetID = args[0]
if err := apiCli.Scalesets.DeleteScaleSet(deleteScaleSetReq, authToken); err != nil {
return err
}
return nil
},
}
var scaleSetAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"create"},
Short: "Add scale set",
Long: `Add a new scale set.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
newScaleSetParams := params.CreateScaleSetParams{
RunnerPrefix: params.RunnerPrefix{
Prefix: scalesetRunnerPrefix,
},
ProviderName: scalesetProvider,
Name: scalesetName,
MaxRunners: scalesetMaxRunners,
MinIdleRunners: scalesetMinIdleRunners,
Image: scalesetImage,
Flavor: scalesetFlavor,
OSType: commonParams.OSType(scalesetOSType),
OSArch: commonParams.OSArch(scalesetOSArch),
Enabled: scalesetEnabled,
RunnerBootstrapTimeout: scalesetRunnerBootstrapTimeout,
GitHubRunnerGroup: scalesetGitHubRunnerGroup,
}
if cmd.Flags().Changed("extra-specs") {
data, err := asRawMessage([]byte(scalesetExtraSpecs))
if err != nil {
return err
}
newScaleSetParams.ExtraSpecs = data
}
if scalesetExtraSpecsFile != "" {
data, err := extraSpecsFromFile(scalesetExtraSpecsFile)
if err != nil {
return err
}
newScaleSetParams.ExtraSpecs = data
}
if err := newScaleSetParams.Validate(); err != nil {
return err
}
var err error
var response scalesetPayloadGetter
if cmd.Flags().Changed("repo") {
newRepoScaleSetReq := apiClientRepos.NewCreateRepoScaleSetParams()
newRepoScaleSetReq.RepoID = scalesetRepository
newRepoScaleSetReq.Body = newScaleSetParams
response, err = apiCli.Repositories.CreateRepoScaleSet(newRepoScaleSetReq, authToken)
} else if cmd.Flags().Changed("org") {
newOrgScaleSetReq := apiClientOrgs.NewCreateOrgScaleSetParams()
newOrgScaleSetReq.OrgID = scalesetOrganization
newOrgScaleSetReq.Body = newScaleSetParams
response, err = apiCli.Organizations.CreateOrgScaleSet(newOrgScaleSetReq, authToken)
} else if cmd.Flags().Changed("enterprise") {
newEnterpriseScaleSetReq := apiClientEnterprises.NewCreateEnterpriseScaleSetParams()
newEnterpriseScaleSetReq.EnterpriseID = scalesetEnterprise
newEnterpriseScaleSetReq.Body = newScaleSetParams
response, err = apiCli.Enterprises.CreateEnterpriseScaleSet(newEnterpriseScaleSetReq, authToken)
} else {
cmd.Help() //nolint
os.Exit(0)
}
if err != nil {
return err
}
formatOneScaleSet(response.GetPayload())
return nil
},
}
var scaleSetUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update one scale set",
Long: `Updates scale set characteristics.
This command updates the scale set characteristics. Runners already created prior to updating
the scale set, will not be recreated. If they no longer suit your needs, you will need to
explicitly remove them using the runner delete command.
`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("command requires a scale set ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
updateScaleSetReq := apiClientScaleSets.NewUpdateScaleSetParams()
scaleSetUpdateParams := params.UpdateScaleSetParams{}
if cmd.Flags().Changed("image") {
scaleSetUpdateParams.Image = scalesetImage
}
if cmd.Flags().Changed("name") {
scaleSetUpdateParams.Name = scalesetName
}
if cmd.Flags().Changed("flavor") {
scaleSetUpdateParams.Flavor = scalesetFlavor
}
if cmd.Flags().Changed("os-type") {
scaleSetUpdateParams.OSType = commonParams.OSType(scalesetOSType)
}
if cmd.Flags().Changed("os-arch") {
scaleSetUpdateParams.OSArch = commonParams.OSArch(scalesetOSArch)
}
if cmd.Flags().Changed("max-runners") {
scaleSetUpdateParams.MaxRunners = &scalesetMaxRunners
}
if cmd.Flags().Changed("min-idle-runners") {
scaleSetUpdateParams.MinIdleRunners = &scalesetMinIdleRunners
}
if cmd.Flags().Changed("runner-prefix") {
scaleSetUpdateParams.RunnerPrefix = params.RunnerPrefix{
Prefix: scalesetRunnerPrefix,
}
}
if cmd.Flags().Changed("runner-group") {
scaleSetUpdateParams.GitHubRunnerGroup = &scalesetGitHubRunnerGroup
}
if cmd.Flags().Changed("enabled") {
scaleSetUpdateParams.Enabled = &scalesetEnabled
}
if cmd.Flags().Changed("runner-bootstrap-timeout") {
scaleSetUpdateParams.RunnerBootstrapTimeout = &scalesetRunnerBootstrapTimeout
}
if cmd.Flags().Changed("extra-specs") {
data, err := asRawMessage([]byte(scalesetExtraSpecs))
if err != nil {
return err
}
scaleSetUpdateParams.ExtraSpecs = data
}
if scalesetExtraSpecsFile != "" {
data, err := extraSpecsFromFile(scalesetExtraSpecsFile)
if err != nil {
return err
}
scaleSetUpdateParams.ExtraSpecs = data
}
updateScaleSetReq.ScalesetID = args[0]
updateScaleSetReq.Body = scaleSetUpdateParams
response, err := apiCli.Scalesets.UpdateScaleSet(updateScaleSetReq, authToken)
if err != nil {
return err
}
formatOneScaleSet(response.Payload)
return nil
},
}
func init() {
scalesetListCmd.Flags().StringVarP(&scalesetRepository, "repo", "r", "", "List all scale sets within this repository.")
scalesetListCmd.Flags().StringVarP(&scalesetOrganization, "org", "o", "", "List all scale sets within this organization.")
scalesetListCmd.Flags().StringVarP(&scalesetEnterprise, "enterprise", "e", "", "List all scale sets within this enterprise.")
scalesetListCmd.Flags().BoolVarP(&scalesetAll, "all", "a", false, "List all scale sets, regardless of org or repo.")
scalesetListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all", "enterprise")
scaleSetUpdateCmd.Flags().StringVar(&scalesetImage, "image", "", "The provider-specific image name to use for runners in this scale set.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetFlavor, "flavor", "", "The flavor to use for the runners in this scale set.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetName, "name", "", "The name of the scale set. This option is mandatory.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
scaleSetUpdateCmd.Flags().StringVar(&scalesetOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
scaleSetUpdateCmd.Flags().StringVar(&scalesetRunnerPrefix, "runner-prefix", "", "The name prefix to use for runners in this scale set.")
scaleSetUpdateCmd.Flags().UintVar(&scalesetMaxRunners, "max-runners", 5, "The maximum number of runner this scale set will create.")
scaleSetUpdateCmd.Flags().UintVar(&scalesetMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this scale set will be added.")
scaleSetUpdateCmd.Flags().BoolVar(&scalesetEnabled, "enabled", false, "Enable this scale set.")
scaleSetUpdateCmd.Flags().UintVar(&scalesetRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the scale set.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetExtraSpecs, "extra-specs", "", "A valid json which will be passed to the IaaS provider managing the scale set.")
scaleSetUpdateCmd.MarkFlagsMutuallyExclusive("extra-specs-file", "extra-specs")
scaleSetAddCmd.Flags().StringVar(&scalesetProvider, "provider-name", "", "The name of the provider where runners will be created.")
scaleSetAddCmd.Flags().StringVar(&scalesetImage, "image", "", "The provider-specific image name to use for runners in this scale set.")
scaleSetAddCmd.Flags().StringVar(&scalesetName, "name", "", "The name of the scale set. This option is mandatory.")
scaleSetAddCmd.Flags().StringVar(&scalesetFlavor, "flavor", "", "The flavor to use for this runner.")
scaleSetAddCmd.Flags().StringVar(&scalesetRunnerPrefix, "runner-prefix", "", "The name prefix to use for runners in this scale set.")
scaleSetAddCmd.Flags().StringVar(&scalesetOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
scaleSetAddCmd.Flags().StringVar(&scalesetOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
scaleSetAddCmd.Flags().StringVar(&scalesetExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the scale set.")
scaleSetAddCmd.Flags().StringVar(&scalesetExtraSpecs, "extra-specs", "", "A valid json which will be passed to the IaaS provider managing the scale set.")
scaleSetAddCmd.Flags().StringVar(&scalesetGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this scale set will be added.")
scaleSetAddCmd.Flags().UintVar(&scalesetMaxRunners, "max-runners", 5, "The maximum number of runner this scale set will create.")
scaleSetAddCmd.Flags().UintVar(&scalesetRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
scaleSetAddCmd.Flags().UintVar(&scalesetMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
scaleSetAddCmd.Flags().BoolVar(&scalesetEnabled, "enabled", false, "Enable this scale set.")
scaleSetAddCmd.MarkFlagRequired("provider-name") //nolint
scaleSetAddCmd.MarkFlagRequired("name") //nolint
scaleSetAddCmd.MarkFlagRequired("image") //nolint
scaleSetAddCmd.MarkFlagRequired("flavor") //nolint
scaleSetAddCmd.Flags().StringVarP(&scalesetRepository, "repo", "r", "", "Add the new scale set within this repository.")
scaleSetAddCmd.Flags().StringVarP(&scalesetOrganization, "org", "o", "", "Add the new scale set within this organization.")
scaleSetAddCmd.Flags().StringVarP(&scalesetEnterprise, "enterprise", "e", "", "Add the new scale set within this enterprise.")
scaleSetAddCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise")
scaleSetAddCmd.MarkFlagsMutuallyExclusive("extra-specs-file", "extra-specs")
scalesetCmd.AddCommand(
scalesetListCmd,
scaleSetShowCmd,
scaleSetDeleteCmd,
scaleSetUpdateCmd,
scaleSetAddCmd,
)
rootCmd.AddCommand(scalesetCmd)
}
func formatScaleSets(scaleSets []params.ScaleSet) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(scaleSets)
return
}
t := table.NewWriter()
header := table.Row{"ID", "Scale Set Name", "Image", "Flavor", "Belongs to", "Level", "Enabled", "Runner Prefix", "Provider"}
t.AppendHeader(header)
for _, scaleSet := range scaleSets {
var belongsTo string
var level string
switch {
case scaleSet.RepoID != "" && scaleSet.RepoName != "":
belongsTo = scaleSet.RepoName
level = entityTypeRepo
case scaleSet.OrgID != "" && scaleSet.OrgName != "":
belongsTo = scaleSet.OrgName
level = entityTypeOrg
case scaleSet.EnterpriseID != "" && scaleSet.EnterpriseName != "":
belongsTo = scaleSet.EnterpriseName
level = entityTypeEnterprise
}
t.AppendRow(table.Row{scaleSet.ID, scaleSet.Name, scaleSet.Image, scaleSet.Flavor, belongsTo, level, scaleSet.Enabled, scaleSet.GetRunnerPrefix(), scaleSet.ProviderName})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneScaleSet(scaleSet params.ScaleSet) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(scaleSet)
return
}
t := table.NewWriter()
rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
header := table.Row{"Field", "Value"}
var belongsTo string
var level string
switch {
case scaleSet.RepoID != "" && scaleSet.RepoName != "":
belongsTo = scaleSet.RepoName
level = entityTypeRepo
case scaleSet.OrgID != "" && scaleSet.OrgName != "":
belongsTo = scaleSet.OrgName
level = entityTypeOrg
case scaleSet.EnterpriseID != "" && scaleSet.EnterpriseName != "":
belongsTo = scaleSet.EnterpriseName
level = entityTypeEnterprise
}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", scaleSet.ID})
t.AppendRow(table.Row{"Scale Set ID", scaleSet.ScaleSetID})
t.AppendRow(table.Row{"Scale Name", scaleSet.Name})
t.AppendRow(table.Row{"Provider Name", scaleSet.ProviderName})
t.AppendRow(table.Row{"Image", scaleSet.Image})
t.AppendRow(table.Row{"Flavor", scaleSet.Flavor})
t.AppendRow(table.Row{"OS Type", scaleSet.OSType})
t.AppendRow(table.Row{"OS Architecture", scaleSet.OSArch})
t.AppendRow(table.Row{"Max Runners", scaleSet.MaxRunners})
t.AppendRow(table.Row{"Min Idle Runners", scaleSet.MinIdleRunners})
t.AppendRow(table.Row{"Runner Bootstrap Timeout", scaleSet.RunnerBootstrapTimeout})
t.AppendRow(table.Row{"Belongs to", belongsTo})
t.AppendRow(table.Row{"Level", level})
t.AppendRow(table.Row{"Enabled", scaleSet.Enabled})
t.AppendRow(table.Row{"Runner Prefix", scaleSet.GetRunnerPrefix()})
t.AppendRow(table.Row{"Extra specs", string(scaleSet.ExtraSpecs)})
t.AppendRow(table.Row{"GitHub Runner Group", scaleSet.GitHubRunnerGroup})
if len(scaleSet.Instances) > 0 {
for _, instance := range scaleSet.Instances {
t.AppendRow(table.Row{"Instances", fmt.Sprintf("%s (%s)", instance.Name, instance.ID)}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())
}

View file

@ -25,6 +25,7 @@ import (
"net/http"
"os"
"os/signal"
"runtime"
"syscall"
"time"
@ -41,13 +42,18 @@ import (
"github.com/cloudbase/garm/database"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/database/watcher"
"github.com/cloudbase/garm/locking"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner" //nolint:typecheck
runnerMetrics "github.com/cloudbase/garm/runner/metrics"
"github.com/cloudbase/garm/runner/providers"
garmUtil "github.com/cloudbase/garm/util"
"github.com/cloudbase/garm/util/appdefaults"
"github.com/cloudbase/garm/websocket"
"github.com/cloudbase/garm/workers/credentials"
"github.com/cloudbase/garm/workers/entity"
"github.com/cloudbase/garm/workers/provider"
)
var (
@ -60,16 +66,17 @@ var signals = []os.Signal{
syscall.SIGTERM,
}
func maybeInitController(db common.Store) error {
if _, err := db.ControllerInfo(); err == nil {
return nil
func maybeInitController(db common.Store) (params.ControllerInfo, error) {
if info, err := db.ControllerInfo(); err == nil {
return info, nil
}
if _, err := db.InitController(); err != nil {
return errors.Wrap(err, "initializing controller")
info, err := db.InitController()
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "initializing controller")
}
return nil
return info, nil
}
func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub) {
@ -174,6 +181,7 @@ func maybeUpdateURLsFromConfig(cfg config.Config, store common.Store) error {
return nil
}
//gocyclo:ignore
func main() {
flag.Parse()
if *version {
@ -210,14 +218,60 @@ func main() {
log.Fatal(err)
}
if err := maybeInitController(db); err != nil {
controllerInfo, err := maybeInitController(db)
if err != nil {
log.Fatal(err)
}
// Local locker for now. Will be configurable in the future,
// as we add scale-out capability to GARM.
lock, err := locking.NewLocalLocker(ctx, db)
if err != nil {
log.Fatalf("failed to create locker: %q", err)
}
if err := locking.RegisterLocker(lock); err != nil {
log.Fatalf("failed to register locker: %q", err)
}
if err := maybeUpdateURLsFromConfig(*cfg, db); err != nil {
log.Fatal(err)
}
credsWorker, err := credentials.NewWorker(ctx, db)
if err != nil {
log.Fatalf("failed to create credentials worker: %+v", err)
}
if err := credsWorker.Start(); err != nil {
log.Fatalf("failed to start credentials worker: %+v", err)
}
providers, err := providers.LoadProvidersFromConfig(ctx, *cfg, controllerInfo.ControllerID.String())
if err != nil {
log.Fatalf("loading providers: %+v", err)
}
entityController, err := entity.NewController(ctx, db, providers)
if err != nil {
log.Fatalf("failed to create entity controller: %+v", err)
}
if err := entityController.Start(); err != nil {
log.Fatalf("failed to start entity controller: %+v", err)
}
instanceTokenGetter, err := auth.NewInstanceTokenGetter(cfg.JWTAuth.Secret)
if err != nil {
log.Fatalf("failed to create instance token getter: %+v", err)
}
providerWorker, err := provider.NewWorker(ctx, db, providers, instanceTokenGetter)
if err != nil {
log.Fatalf("failed to create provider worker: %+v", err)
}
if err := providerWorker.Start(); err != nil {
log.Fatalf("failed to start provider worker: %+v", err)
}
runner, err := runner.NewRunner(ctx, *cfg, db)
if err != nil {
log.Fatalf("failed to create controller: %+v", err)
@ -276,6 +330,8 @@ func main() {
}
if cfg.Default.DebugServer {
runtime.SetBlockProfileRate(1)
runtime.SetMutexProfileFraction(1)
slog.InfoContext(ctx, "setting up debug routes")
router = routers.WithDebugServer(router)
}
@ -314,6 +370,20 @@ func main() {
<-ctx.Done()
if err := credsWorker.Stop(); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop credentials worker")
}
slog.InfoContext(ctx, "shutting down entity controller")
if err := entityController.Stop(); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop entity controller")
}
slog.InfoContext(ctx, "shutting down provider worker")
if err := providerWorker.Stop(); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop provider worker")
}
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@ -14,6 +14,24 @@ type Store struct {
mock.Mock
}
// AddEntityEvent provides a mock function with given fields: ctx, entity, event, eventLevel, statusMessage, maxEvents
func (_m *Store) AddEntityEvent(ctx context.Context, entity params.GithubEntity, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
ret := _m.Called(ctx, entity, event, eventLevel, statusMessage, maxEvents)
if len(ret) == 0 {
panic("no return value specified for AddEntityEvent")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity, params.EventType, params.EventLevel, string, int) error); ok {
r0 = rf(ctx, entity, event, eventLevel, statusMessage, maxEvents)
} else {
r0 = ret.Error(0)
}
return r0
}
// AddInstanceEvent provides a mock function with given fields: ctx, instanceName, event, eventLevel, eventMessage
func (_m *Store) AddInstanceEvent(ctx context.Context, instanceName string, event params.EventType, eventLevel params.EventLevel, eventMessage string) error {
ret := _m.Called(ctx, instanceName, event, eventLevel, eventMessage)
@ -50,7 +68,7 @@ func (_m *Store) BreakLockJobIsQueued(ctx context.Context, jobID int64) error {
return r0
}
// ControllerInfo provides a mock function with given fields:
// ControllerInfo provides a mock function with no fields
func (_m *Store) ControllerInfo() (params.ControllerInfo, error) {
ret := _m.Called()
@ -134,6 +152,34 @@ func (_m *Store) CreateEntityPool(ctx context.Context, entity params.GithubEntit
return r0, r1
}
// CreateEntityScaleSet provides a mock function with given fields: _a0, entity, param
func (_m *Store) CreateEntityScaleSet(_a0 context.Context, entity params.GithubEntity, param params.CreateScaleSetParams) (params.ScaleSet, error) {
ret := _m.Called(_a0, entity, param)
if len(ret) == 0 {
panic("no return value specified for CreateEntityScaleSet")
}
var r0 params.ScaleSet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity, params.CreateScaleSetParams) (params.ScaleSet, error)); ok {
return rf(_a0, entity, param)
}
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity, params.CreateScaleSetParams) params.ScaleSet); ok {
r0 = rf(_a0, entity, param)
} else {
r0 = ret.Get(0).(params.ScaleSet)
}
if rf, ok := ret.Get(1).(func(context.Context, params.GithubEntity, params.CreateScaleSetParams) error); ok {
r1 = rf(_a0, entity, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateGithubCredentials provides a mock function with given fields: ctx, param
func (_m *Store) CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (params.GithubCredentials, error) {
ret := _m.Called(ctx, param)
@ -302,6 +348,34 @@ func (_m *Store) CreateRepository(ctx context.Context, owner string, name string
return r0, r1
}
// CreateScaleSetInstance provides a mock function with given fields: _a0, scaleSetID, param
func (_m *Store) CreateScaleSetInstance(_a0 context.Context, scaleSetID uint, param params.CreateInstanceParams) (params.Instance, error) {
ret := _m.Called(_a0, scaleSetID, param)
if len(ret) == 0 {
panic("no return value specified for CreateScaleSetInstance")
}
var r0 params.Instance
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint, params.CreateInstanceParams) (params.Instance, error)); ok {
return rf(_a0, scaleSetID, param)
}
if rf, ok := ret.Get(0).(func(context.Context, uint, params.CreateInstanceParams) params.Instance); ok {
r0 = rf(_a0, scaleSetID, param)
} else {
r0 = ret.Get(0).(params.Instance)
}
if rf, ok := ret.Get(1).(func(context.Context, uint, params.CreateInstanceParams) error); ok {
r1 = rf(_a0, scaleSetID, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateUser provides a mock function with given fields: ctx, user
func (_m *Store) CreateUser(ctx context.Context, user params.NewUserParams) (params.User, error) {
ret := _m.Called(ctx, user)
@ -438,6 +512,24 @@ func (_m *Store) DeleteInstance(ctx context.Context, poolID string, instanceName
return r0
}
// DeleteInstanceByName provides a mock function with given fields: ctx, instanceName
func (_m *Store) DeleteInstanceByName(ctx context.Context, instanceName string) error {
ret := _m.Called(ctx, instanceName)
if len(ret) == 0 {
panic("no return value specified for DeleteInstanceByName")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instanceName)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteJob provides a mock function with given fields: ctx, jobID
func (_m *Store) DeleteJob(ctx context.Context, jobID int64) error {
ret := _m.Called(ctx, jobID)
@ -510,6 +602,24 @@ func (_m *Store) DeleteRepository(ctx context.Context, repoID string) error {
return r0
}
// DeleteScaleSetByID provides a mock function with given fields: ctx, scaleSetID
func (_m *Store) DeleteScaleSetByID(ctx context.Context, scaleSetID uint) error {
ret := _m.Called(ctx, scaleSetID)
if len(ret) == 0 {
panic("no return value specified for DeleteScaleSetByID")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, uint) error); ok {
r0 = rf(ctx, scaleSetID)
} else {
r0 = ret.Error(0)
}
return r0
}
// FindPoolsMatchingAllTags provides a mock function with given fields: ctx, entityType, entityID, tags
func (_m *Store) FindPoolsMatchingAllTags(ctx context.Context, entityType params.GithubEntityType, entityID string, tags []string) ([]params.Pool, error) {
ret := _m.Called(ctx, entityType, entityID, tags)
@ -736,6 +846,34 @@ func (_m *Store) GetGithubEndpoint(ctx context.Context, name string) (params.Git
return r0, r1
}
// GetGithubEntity provides a mock function with given fields: _a0, entityType, entityID
func (_m *Store) GetGithubEntity(_a0 context.Context, entityType params.GithubEntityType, entityID string) (params.GithubEntity, error) {
ret := _m.Called(_a0, entityType, entityID)
if len(ret) == 0 {
panic("no return value specified for GetGithubEntity")
}
var r0 params.GithubEntity
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntityType, string) (params.GithubEntity, error)); ok {
return rf(_a0, entityType, entityID)
}
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntityType, string) params.GithubEntity); ok {
r0 = rf(_a0, entityType, entityID)
} else {
r0 = ret.Get(0).(params.GithubEntity)
}
if rf, ok := ret.Get(1).(func(context.Context, params.GithubEntityType, string) error); ok {
r1 = rf(_a0, entityType, entityID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetInstanceByName provides a mock function with given fields: ctx, instanceName
func (_m *Store) GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error) {
ret := _m.Called(ctx, instanceName)
@ -960,6 +1098,34 @@ func (_m *Store) GetRepositoryByID(ctx context.Context, repoID string) (params.R
return r0, r1
}
// GetScaleSetByID provides a mock function with given fields: ctx, scaleSet
func (_m *Store) GetScaleSetByID(ctx context.Context, scaleSet uint) (params.ScaleSet, error) {
ret := _m.Called(ctx, scaleSet)
if len(ret) == 0 {
panic("no return value specified for GetScaleSetByID")
}
var r0 params.ScaleSet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint) (params.ScaleSet, error)); ok {
return rf(ctx, scaleSet)
}
if rf, ok := ret.Get(0).(func(context.Context, uint) params.ScaleSet); ok {
r0 = rf(ctx, scaleSet)
} else {
r0 = ret.Get(0).(params.ScaleSet)
}
if rf, ok := ret.Get(1).(func(context.Context, uint) error); ok {
r1 = rf(ctx, scaleSet)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUser provides a mock function with given fields: ctx, user
func (_m *Store) GetUser(ctx context.Context, user string) (params.User, error) {
ret := _m.Called(ctx, user)
@ -1034,7 +1200,7 @@ func (_m *Store) HasAdminUser(ctx context.Context) bool {
return r0
}
// InitController provides a mock function with given fields:
// InitController provides a mock function with no fields
func (_m *Store) InitController() (params.ControllerInfo, error) {
ret := _m.Called()
@ -1152,6 +1318,36 @@ func (_m *Store) ListAllPools(ctx context.Context) ([]params.Pool, error) {
return r0, r1
}
// ListAllScaleSets provides a mock function with given fields: ctx
func (_m *Store) ListAllScaleSets(ctx context.Context) ([]params.ScaleSet, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListAllScaleSets")
}
var r0 []params.ScaleSet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.ScaleSet, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.ScaleSet); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.ScaleSet)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListEnterprises provides a mock function with given fields: ctx
func (_m *Store) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
ret := _m.Called(ctx)
@ -1272,6 +1468,36 @@ func (_m *Store) ListEntityPools(ctx context.Context, entity params.GithubEntity
return r0, r1
}
// ListEntityScaleSets provides a mock function with given fields: _a0, entity
func (_m *Store) ListEntityScaleSets(_a0 context.Context, entity params.GithubEntity) ([]params.ScaleSet, error) {
ret := _m.Called(_a0, entity)
if len(ret) == 0 {
panic("no return value specified for ListEntityScaleSets")
}
var r0 []params.ScaleSet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity) ([]params.ScaleSet, error)); ok {
return rf(_a0, entity)
}
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity) []params.ScaleSet); ok {
r0 = rf(_a0, entity)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.ScaleSet)
}
}
if rf, ok := ret.Get(1).(func(context.Context, params.GithubEntity) error); ok {
r1 = rf(_a0, entity)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListGithubCredentials provides a mock function with given fields: ctx
func (_m *Store) ListGithubCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
ret := _m.Called(ctx)
@ -1452,6 +1678,36 @@ func (_m *Store) ListRepositories(ctx context.Context) ([]params.Repository, err
return r0, r1
}
// ListScaleSetInstances provides a mock function with given fields: _a0, scalesetID
func (_m *Store) ListScaleSetInstances(_a0 context.Context, scalesetID uint) ([]params.Instance, error) {
ret := _m.Called(_a0, scalesetID)
if len(ret) == 0 {
panic("no return value specified for ListScaleSetInstances")
}
var r0 []params.Instance
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint) ([]params.Instance, error)); ok {
return rf(_a0, scalesetID)
}
if rf, ok := ret.Get(0).(func(context.Context, uint) []params.Instance); ok {
r0 = rf(_a0, scalesetID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Instance)
}
}
if rf, ok := ret.Get(1).(func(context.Context, uint) error); ok {
r1 = rf(_a0, scalesetID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LockJob provides a mock function with given fields: ctx, jobID, entityID
func (_m *Store) LockJob(ctx context.Context, jobID int64, entityID string) error {
ret := _m.Called(ctx, jobID, entityID)
@ -1498,6 +1754,42 @@ func (_m *Store) PoolInstanceCount(ctx context.Context, poolID string) (int64, e
return r0, r1
}
// SetScaleSetDesiredRunnerCount provides a mock function with given fields: ctx, scaleSetID, desiredRunnerCount
func (_m *Store) SetScaleSetDesiredRunnerCount(ctx context.Context, scaleSetID uint, desiredRunnerCount int) error {
ret := _m.Called(ctx, scaleSetID, desiredRunnerCount)
if len(ret) == 0 {
panic("no return value specified for SetScaleSetDesiredRunnerCount")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, uint, int) error); ok {
r0 = rf(ctx, scaleSetID, desiredRunnerCount)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetScaleSetLastMessageID provides a mock function with given fields: ctx, scaleSetID, lastMessageID
func (_m *Store) SetScaleSetLastMessageID(ctx context.Context, scaleSetID uint, lastMessageID int64) error {
ret := _m.Called(ctx, scaleSetID, lastMessageID)
if len(ret) == 0 {
panic("no return value specified for SetScaleSetLastMessageID")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, uint, int64) error); ok {
r0 = rf(ctx, scaleSetID, lastMessageID)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnlockJob provides a mock function with given fields: ctx, jobID, entityID
func (_m *Store) UnlockJob(ctx context.Context, jobID int64, entityID string) error {
ret := _m.Called(ctx, jobID, entityID)
@ -1600,6 +1892,34 @@ func (_m *Store) UpdateEntityPool(ctx context.Context, entity params.GithubEntit
return r0, r1
}
// UpdateEntityScaleSet provides a mock function with given fields: _a0, entity, scaleSetID, param, callback
func (_m *Store) UpdateEntityScaleSet(_a0 context.Context, entity params.GithubEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(params.ScaleSet, params.ScaleSet) error) (params.ScaleSet, error) {
ret := _m.Called(_a0, entity, scaleSetID, param, callback)
if len(ret) == 0 {
panic("no return value specified for UpdateEntityScaleSet")
}
var r0 params.ScaleSet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity, uint, params.UpdateScaleSetParams, func(params.ScaleSet, params.ScaleSet) error) (params.ScaleSet, error)); ok {
return rf(_a0, entity, scaleSetID, param, callback)
}
if rf, ok := ret.Get(0).(func(context.Context, params.GithubEntity, uint, params.UpdateScaleSetParams, func(params.ScaleSet, params.ScaleSet) error) params.ScaleSet); ok {
r0 = rf(_a0, entity, scaleSetID, param, callback)
} else {
r0 = ret.Get(0).(params.ScaleSet)
}
if rf, ok := ret.Get(1).(func(context.Context, params.GithubEntity, uint, params.UpdateScaleSetParams, func(params.ScaleSet, params.ScaleSet) error) error); ok {
r1 = rf(_a0, entity, scaleSetID, param, callback)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGithubCredentials provides a mock function with given fields: ctx, id, param
func (_m *Store) UpdateGithubCredentials(ctx context.Context, id uint, param params.UpdateGithubCredentialsParams) (params.GithubCredentials, error) {
ret := _m.Called(ctx, id, param)

View file

@ -92,6 +92,7 @@ type UserStore interface {
type InstanceStore interface {
CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error)
DeleteInstance(ctx context.Context, poolID string, instanceName string) error
DeleteInstanceByName(ctx context.Context, instanceName string) error
UpdateInstance(ctx context.Context, instanceName string, param params.UpdateInstanceParams) (params.Instance, error)
// Probably a bad idea without some king of filter or at least pagination
@ -135,6 +136,22 @@ type ControllerStore interface {
UpdateController(info params.UpdateControllerParams) (params.ControllerInfo, error)
}
type ScaleSetsStore interface {
ListAllScaleSets(ctx context.Context) ([]params.ScaleSet, error)
CreateEntityScaleSet(_ context.Context, entity params.GithubEntity, param params.CreateScaleSetParams) (scaleSet params.ScaleSet, err error)
ListEntityScaleSets(_ context.Context, entity params.GithubEntity) ([]params.ScaleSet, error)
UpdateEntityScaleSet(_ context.Context, entity params.GithubEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(old, newSet params.ScaleSet) error) (updatedScaleSet params.ScaleSet, err error)
GetScaleSetByID(ctx context.Context, scaleSet uint) (params.ScaleSet, error)
DeleteScaleSetByID(ctx context.Context, scaleSetID uint) (err error)
SetScaleSetLastMessageID(ctx context.Context, scaleSetID uint, lastMessageID int64) error
SetScaleSetDesiredRunnerCount(ctx context.Context, scaleSetID uint, desiredRunnerCount int) error
}
type ScaleSetInstanceStore interface {
ListScaleSetInstances(_ context.Context, scalesetID uint) ([]params.Instance, error)
CreateScaleSetInstance(_ context.Context, scaleSetID uint, param params.CreateInstanceParams) (instance params.Instance, err error)
}
//go:generate mockery --name=Store
type Store interface {
RepoStore
@ -148,7 +165,11 @@ type Store interface {
GithubCredentialsStore
ControllerStore
EntityPoolStore
ScaleSetsStore
ScaleSetInstanceStore
ControllerInfo() (params.ControllerInfo, error)
InitController() (params.ControllerInfo, error)
GetGithubEntity(_ context.Context, entityType params.GithubEntityType, entityID string) (params.GithubEntity, error)
AddEntityEvent(ctx context.Context, entity params.GithubEntity, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error
}

View file

@ -19,6 +19,7 @@ const (
ControllerEntityType DatabaseEntityType = "controller"
GithubCredentialsEntityType DatabaseEntityType = "github_credentials" // #nosec G101
GithubEndpointEntityType DatabaseEntityType = "github_endpoint"
ScaleSetEntityType DatabaseEntityType = "scaleset"
)
const (

View file

@ -69,6 +69,5 @@ func (s *CtrlTestSuite) TestInitControllerAlreadyInitialized() {
}
func TestCtrlTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(CtrlTestSuite))
}

View file

@ -96,7 +96,7 @@ func (s *sqlDatabase) GetEnterprise(ctx context.Context, name, endpointName stri
}
func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Pools", "Credentials", "Endpoint")
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint")
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}

View file

@ -782,6 +782,5 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
}
func TestEnterpriseTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(EnterpriseTestSuite))
}

View file

@ -738,7 +738,6 @@ func (s *GithubTestSuite) TestAdminUserCanUpdateAnyGithubCredentials() {
}
func TestGithubTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(GithubTestSuite))
}

View file

@ -97,6 +97,8 @@ func (s *sqlDatabase) getPoolInstanceByName(poolID string, instanceName string)
}
return Instance{}, errors.Wrap(q.Error, "fetching pool instance by name")
}
instance.Pool = pool
return instance, nil
}
@ -134,7 +136,7 @@ func (s *sqlDatabase) GetPoolInstanceByName(_ context.Context, poolID string, in
}
func (s *sqlDatabase) GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error) {
instance, err := s.getInstanceByName(ctx, instanceName, "StatusMessages")
instance, err := s.getInstanceByName(ctx, instanceName, "StatusMessages", "Pool", "ScaleSet")
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
}
@ -145,6 +147,9 @@ func (s *sqlDatabase) GetInstanceByName(ctx context.Context, instanceName string
func (s *sqlDatabase) DeleteInstance(_ context.Context, poolID string, instanceName string) (err error) {
instance, err := s.getPoolInstanceByName(poolID, instanceName)
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "deleting instance")
}
@ -154,13 +159,62 @@ func (s *sqlDatabase) DeleteInstance(_ context.Context, poolID string, instanceN
if instance.ProviderID != nil {
providerID = *instance.ProviderID
}
if notifyErr := s.sendNotify(common.InstanceEntityType, common.DeleteOperation, params.Instance{
instanceNotif := params.Instance{
ID: instance.ID.String(),
Name: instance.Name,
ProviderID: providerID,
AgentID: instance.AgentID,
PoolID: instance.PoolID.String(),
}); notifyErr != nil {
}
switch {
case instance.PoolID != nil:
instanceNotif.PoolID = instance.PoolID.String()
case instance.ScaleSetFkID != nil:
instanceNotif.ScaleSetID = *instance.ScaleSetFkID
}
if notifyErr := s.sendNotify(common.InstanceEntityType, common.DeleteOperation, instanceNotif); notifyErr != nil {
slog.With(slog.Any("error", notifyErr)).Error("failed to send notify")
}
}
}()
if q := s.conn.Unscoped().Delete(&instance); q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(q.Error, "deleting instance")
}
return nil
}
func (s *sqlDatabase) DeleteInstanceByName(ctx context.Context, instanceName string) error {
instance, err := s.getInstanceByName(ctx, instanceName, "Pool", "ScaleSet")
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "deleting instance")
}
defer func() {
if err == nil {
var providerID string
if instance.ProviderID != nil {
providerID = *instance.ProviderID
}
payload := params.Instance{
ID: instance.ID.String(),
Name: instance.Name,
ProviderID: providerID,
AgentID: instance.AgentID,
}
if instance.PoolID != nil {
payload.PoolID = instance.PoolID.String()
}
if instance.ScaleSetFkID != nil {
payload.ScaleSetID = *instance.ScaleSetFkID
}
if notifyErr := s.sendNotify(common.InstanceEntityType, common.DeleteOperation, payload); notifyErr != nil {
slog.With(slog.Any("error", notifyErr)).Error("failed to send notify")
}
}
@ -194,7 +248,7 @@ func (s *sqlDatabase) AddInstanceEvent(ctx context.Context, instanceName string,
}
func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, param params.UpdateInstanceParams) (params.Instance, error) {
instance, err := s.getInstanceByName(ctx, instanceName)
instance, err := s.getInstanceByName(ctx, instanceName, "Pool", "ScaleSet")
if err != nil {
return params.Instance{}, errors.Wrap(err, "updating instance")
}

View file

@ -119,6 +119,12 @@ func (s *InstancesTestSuite) SetupTest() {
CallbackURL: "https://garm.example.com/",
Status: commonParams.InstanceRunning,
RunnerStatus: params.RunnerIdle,
JitConfiguration: map[string]string{
"secret": fmt.Sprintf("secret-%d", i),
},
AditionalLabels: []string{
fmt.Sprintf("label-%d", i),
},
},
)
if err != nil {
@ -277,6 +283,23 @@ func (s *InstancesTestSuite) TestDeleteInstance() {
_, err = s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
err = s.Store.DeleteInstance(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Nil(err)
}
func (s *InstancesTestSuite) TestDeleteInstanceByName() {
storeInstance := s.Fixtures.Instances[0]
err := s.Store.DeleteInstanceByName(s.adminCtx, storeInstance.Name)
s.Require().Nil(err)
_, err = s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
err = s.Store.DeleteInstanceByName(s.adminCtx, storeInstance.Name)
s.Require().Nil(err)
}
func (s *InstancesTestSuite) TestDeleteInstanceInvalidPoolID() {
@ -568,6 +591,5 @@ func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
}
func TestInstTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(InstancesTestSuite))
}

View file

@ -86,6 +86,69 @@ type Pool struct {
Priority uint `gorm:"index:idx_pool_priority"`
}
// ScaleSet represents a github scale set. Scale sets are almost identical to pools with a few
// notable exceptions:
// - Labels are no longer relevant
// - Workflows will use the scaleset name to target runners.
// - A scale set is a stand alone unit. If a workflow targets a scale set, no other runner will pick up that job.
type ScaleSet struct {
gorm.Model
// ScaleSetID is the github ID of the scale set. This field may not be set if
// the scale set was ceated in GARM but has not yet been created in GitHub.
// The scale set ID is also not globally unique. It is only unique within the context
// of an entity.
ScaleSetID int `gorm:"index:idx_scale_set"`
Name string `gorm:"unique_index:idx_name"`
GitHubRunnerGroup string `gorm:"unique_index:idx_name"`
DisableUpdate bool
// State stores the provisioning state of the scale set in GitHub
State params.ScaleSetState
// ExtendedState stores a more detailed message regarding the State.
// If an error occurs, the reason for the error will be stored here.
ExtendedState string
ProviderName string
RunnerPrefix string
MaxRunners uint
MinIdleRunners uint
RunnerBootstrapTimeout uint
Image string
Flavor string
OSType commonParams.OSType
OSArch commonParams.OSArch
Enabled bool
LastMessageID int64
DesiredRunnerCount int
// ExtraSpecs is an opaque json that gets sent to the provider
// as part of the bootstrap params for instances. It can contain
// any kind of data needed by providers.
ExtraSpecs datatypes.JSON
RepoID *uuid.UUID `gorm:"index"`
Repository Repository `gorm:"foreignKey:RepoID;"`
OrgID *uuid.UUID `gorm:"index"`
Organization Organization `gorm:"foreignKey:OrgID"`
EnterpriseID *uuid.UUID `gorm:"index"`
Enterprise Enterprise `gorm:"foreignKey:EnterpriseID"`
Instances []Instance `gorm:"foreignKey:ScaleSetFkID"`
}
type RepositoryEvent struct {
gorm.Model
EventType params.EventType
EventLevel params.EventLevel
Message string `gorm:"type:text"`
RepoID uuid.UUID `gorm:"index:idx_repo_event"`
Repo Repository `gorm:"foreignKey:RepoID"`
}
type Repository struct {
Base
@ -98,13 +161,26 @@ type Repository struct {
Name string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:RepoID"`
ScaleSets []ScaleSet `gorm:"foreignKey:RepoID"`
Jobs []WorkflowJob `gorm:"foreignKey:RepoID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []RepositoryEvent `gorm:"foreignKey:RepoID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type OrganizationEvent struct {
gorm.Model
EventType params.EventType
EventLevel params.EventLevel
Message string `gorm:"type:text"`
OrgID uuid.UUID `gorm:"index:idx_org_event"`
Org Organization `gorm:"foreignKey:OrgID"`
}
type Organization struct {
Base
@ -116,11 +192,25 @@ type Organization struct {
Name string `gorm:"index:idx_org_name_nocase,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:OrgID"`
ScaleSet []ScaleSet `gorm:"foreignKey:OrgID"`
Jobs []WorkflowJob `gorm:"foreignKey:OrgID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index:idx_org_name_nocase,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []OrganizationEvent `gorm:"foreignKey:OrgID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type EnterpriseEvent struct {
gorm.Model
EventType params.EventType
EventLevel params.EventLevel
Message string `gorm:"type:text"`
EnterpriseID uuid.UUID `gorm:"index:idx_enterprise_event"`
Enterprise Enterprise `gorm:"foreignKey:EnterpriseID"`
}
type Enterprise struct {
@ -134,11 +224,14 @@ type Enterprise struct {
Name string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:EnterpriseID"`
ScaleSet []ScaleSet `gorm:"foreignKey:EnterpriseID"`
Jobs []WorkflowJob `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []EnterpriseEvent `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type Address struct {
@ -184,9 +277,12 @@ type Instance struct {
GitHubRunnerGroup string
AditionalLabels datatypes.JSON
PoolID uuid.UUID
PoolID *uuid.UUID
Pool Pool `gorm:"foreignKey:PoolID"`
ScaleSetFkID *uint
ScaleSet ScaleSet `gorm:"foreignKey:ScaleSetFkID"`
StatusMessages []InstanceStatusUpdate `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Job *WorkflowJob `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`

View file

@ -216,7 +216,7 @@ func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, para
}
func (s *sqlDatabase) GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error) {
org, err := s.getOrgByID(ctx, s.conn, orgID, "Pools", "Credentials", "Endpoint")
org, err := s.getOrgByID(ctx, s.conn, orgID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint")
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}

View file

@ -787,6 +787,5 @@ func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
}
func TestOrgTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(OrgTestSuite))
}

View file

@ -101,13 +101,13 @@ func (s *sqlDatabase) getEntityPool(tx *gorm.DB, entityType params.GithubEntityT
switch entityType {
case params.GithubEntityTypeRepository:
fieldName = entityTypeRepoName
entityField = "Repository"
entityField = repositoryFieldName
case params.GithubEntityTypeOrganization:
fieldName = entityTypeOrgName
entityField = "Organization"
entityField = organizationFieldName
case params.GithubEntityTypeEnterprise:
fieldName = entityTypeEnterpriseName
entityField = "Enterprise"
entityField = enterpriseFieldName
default:
return Pool{}, fmt.Errorf("invalid entityType: %v", entityType)
}
@ -427,7 +427,10 @@ func (s *sqlDatabase) ListEntityInstances(_ context.Context, entity params.Githu
}
ret := []params.Instance{}
for _, pool := range pools {
for _, instance := range pool.Instances {
instances := pool.Instances
pool.Instances = nil
for _, instance := range instances {
instance.Pool = pool
paramsInstance, err := s.sqlToParamsInstance(instance)
if err != nil {
return nil, errors.Wrap(err, "fetching instance")

View file

@ -16,6 +16,7 @@ package sql
import (
"context"
"encoding/json"
"flag"
"fmt"
"regexp"
@ -27,7 +28,9 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/database/watcher"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
@ -40,7 +43,9 @@ type PoolsTestFixtures struct {
type PoolsTestSuite struct {
suite.Suite
Store dbCommon.Store
Store dbCommon.Store
ctx context.Context
StoreSQLMocked *sqlDatabase
Fixtures *PoolsTestFixtures
adminCtx context.Context
@ -53,13 +58,21 @@ func (s *PoolsTestSuite) assertSQLMockExpectations() {
}
}
func (s *PoolsTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *PoolsTestSuite) SetupTest() {
// create testing sqlite database
ctx := context.Background()
watcher.InitWatcher(ctx)
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
s.ctx = garmTesting.ImpersonateAdminContext(ctx, s.Store, s.T())
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
@ -194,7 +207,132 @@ func (s *PoolsTestSuite) TestDeletePoolByIDDBRemoveErr() {
s.Require().Equal("removing pool: mocked removing pool error", err.Error())
}
func (s *PoolsTestSuite) TestEntityPoolOperations() {
ep := garmTesting.CreateDefaultGithubEndpoint(s.ctx, s.Store, s.T())
creds := garmTesting.CreateTestGithubCredentials(s.ctx, "test-creds", s.Store, s.T(), ep)
s.T().Cleanup(func() { s.Store.DeleteGithubCredentials(s.ctx, creds.ID) })
repo, err := s.Store.CreateRepository(s.ctx, "test-owner", "test-repo", creds.Name, "test-secret", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotEmpty(repo.ID)
s.T().Cleanup(func() { s.Store.DeleteRepository(s.ctx, repo.ID) })
entity, err := repo.GetEntity()
s.Require().NoError(err)
createPoolParams := params.CreatePoolParams{
ProviderName: "test-provider",
Image: "test-image",
Flavor: "test-flavor",
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Tags: []string{"test-tag"},
}
pool, err := s.Store.CreateEntityPool(s.ctx, entity, createPoolParams)
s.Require().NoError(err)
s.Require().NotEmpty(pool.ID)
s.T().Cleanup(func() { s.Store.DeleteEntityPool(s.ctx, entity, pool.ID) })
entityPool, err := s.Store.GetEntityPool(s.ctx, entity, pool.ID)
s.Require().NoError(err)
s.Require().Equal(pool.ID, entityPool.ID)
s.Require().Equal(pool.ProviderName, entityPool.ProviderName)
updatePoolParams := params.UpdatePoolParams{
Enabled: garmTesting.Ptr(true),
Flavor: "new-flavor",
Image: "new-image",
RunnerPrefix: params.RunnerPrefix{
Prefix: "new-prefix",
},
MaxRunners: garmTesting.Ptr(uint(100)),
MinIdleRunners: garmTesting.Ptr(uint(50)),
OSType: commonParams.Windows,
OSArch: commonParams.Amd64,
Tags: []string{"new-tag"},
RunnerBootstrapTimeout: garmTesting.Ptr(uint(10)),
ExtraSpecs: json.RawMessage(`{"extra": "specs"}`),
GitHubRunnerGroup: garmTesting.Ptr("new-group"),
Priority: garmTesting.Ptr(uint(1)),
}
pool, err = s.Store.UpdateEntityPool(s.ctx, entity, pool.ID, updatePoolParams)
s.Require().NoError(err)
s.Require().Equal(*updatePoolParams.Enabled, pool.Enabled)
s.Require().Equal(updatePoolParams.Flavor, pool.Flavor)
s.Require().Equal(updatePoolParams.Image, pool.Image)
s.Require().Equal(updatePoolParams.RunnerPrefix.Prefix, pool.RunnerPrefix.Prefix)
s.Require().Equal(*updatePoolParams.MaxRunners, pool.MaxRunners)
s.Require().Equal(*updatePoolParams.MinIdleRunners, pool.MinIdleRunners)
s.Require().Equal(updatePoolParams.OSType, pool.OSType)
s.Require().Equal(updatePoolParams.OSArch, pool.OSArch)
s.Require().Equal(*updatePoolParams.RunnerBootstrapTimeout, pool.RunnerBootstrapTimeout)
s.Require().Equal(updatePoolParams.ExtraSpecs, pool.ExtraSpecs)
s.Require().Equal(*updatePoolParams.GitHubRunnerGroup, pool.GitHubRunnerGroup)
s.Require().Equal(*updatePoolParams.Priority, pool.Priority)
entityPools, err := s.Store.ListEntityPools(s.ctx, entity)
s.Require().NoError(err)
s.Require().Len(entityPools, 1)
s.Require().Equal(pool.ID, entityPools[0].ID)
tagsToMatch := []string{"new-tag"}
pools, err := s.Store.FindPoolsMatchingAllTags(s.ctx, entity.EntityType, entity.ID, tagsToMatch)
s.Require().NoError(err)
s.Require().Len(pools, 1)
s.Require().Equal(pool.ID, pools[0].ID)
invalidTagsToMatch := []string{"invalid-tag"}
pools, err = s.Store.FindPoolsMatchingAllTags(s.ctx, entity.EntityType, entity.ID, invalidTagsToMatch)
s.Require().NoError(err)
s.Require().Len(pools, 0)
}
func (s *PoolsTestSuite) TestListEntityInstances() {
ep := garmTesting.CreateDefaultGithubEndpoint(s.ctx, s.Store, s.T())
creds := garmTesting.CreateTestGithubCredentials(s.ctx, "test-creds", s.Store, s.T(), ep)
s.T().Cleanup(func() { s.Store.DeleteGithubCredentials(s.ctx, creds.ID) })
repo, err := s.Store.CreateRepository(s.ctx, "test-owner", "test-repo", creds.Name, "test-secret", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotEmpty(repo.ID)
s.T().Cleanup(func() { s.Store.DeleteRepository(s.ctx, repo.ID) })
entity, err := repo.GetEntity()
s.Require().NoError(err)
createPoolParams := params.CreatePoolParams{
ProviderName: "test-provider",
Image: "test-image",
Flavor: "test-flavor",
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Tags: []string{"test-tag"},
}
pool, err := s.Store.CreateEntityPool(s.ctx, entity, createPoolParams)
s.Require().NoError(err)
s.Require().NotEmpty(pool.ID)
s.T().Cleanup(func() { s.Store.DeleteEntityPool(s.ctx, entity, pool.ID) })
createInstanceParams := params.CreateInstanceParams{
Name: "test-instance",
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Status: commonParams.InstanceCreating,
}
instance, err := s.Store.CreateInstance(s.ctx, pool.ID, createInstanceParams)
s.Require().NoError(err)
s.Require().NotEmpty(instance.ID)
s.T().Cleanup(func() { s.Store.DeleteInstance(s.ctx, pool.ID, instance.ID) })
instances, err := s.Store.ListEntityInstances(s.ctx, entity)
s.Require().NoError(err)
s.Require().Len(instances, 1)
s.Require().Equal(instance.ID, instances[0].ID)
s.Require().Equal(instance.Name, instances[0].Name)
s.Require().Equal(instance.ProviderName, pool.ProviderName)
}
func TestPoolsTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(PoolsTestSuite))
}

View file

@ -216,7 +216,7 @@ func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param
}
func (s *sqlDatabase) GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error) {
repo, err := s.getRepoByID(ctx, s.conn, repoID, "Pools", "Credentials", "Endpoint")
repo, err := s.getRepoByID(ctx, s.conn, repoID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint")
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}

View file

@ -94,6 +94,9 @@ func (s *RepoTestSuite) assertSQLMockExpectations() {
func (s *RepoTestSuite) SetupTest() {
// create testing sqlite database
ctx := context.Background()
watcher.InitWatcher(ctx)
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
@ -191,6 +194,10 @@ func (s *RepoTestSuite) SetupTest() {
s.Fixtures = fixtures
}
func (s *RepoTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *RepoTestSuite) TestCreateRepository() {
// call tested function
repo, err := s.Store.CreateRepository(
@ -831,7 +838,5 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
}
func TestRepoTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(RepoTestSuite))
}

View file

@ -0,0 +1,70 @@
package sql
import (
"context"
"github.com/pkg/errors"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
)
func (s *sqlDatabase) CreateScaleSetInstance(_ context.Context, scaleSetID uint, param params.CreateInstanceParams) (instance params.Instance, err error) {
scaleSet, err := s.getScaleSetByID(s.conn, scaleSetID)
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching scale set")
}
defer func() {
if err == nil {
s.sendNotify(common.InstanceEntityType, common.CreateOperation, instance)
}
}()
var secret []byte
if len(param.JitConfiguration) > 0 {
secret, err = s.marshalAndSeal(param.JitConfiguration)
if err != nil {
return params.Instance{}, errors.Wrap(err, "marshalling jit config")
}
}
newInstance := Instance{
ScaleSet: scaleSet,
Name: param.Name,
Status: param.Status,
RunnerStatus: param.RunnerStatus,
OSType: param.OSType,
OSArch: param.OSArch,
CallbackURL: param.CallbackURL,
MetadataURL: param.MetadataURL,
GitHubRunnerGroup: param.GitHubRunnerGroup,
JitConfiguration: secret,
AgentID: param.AgentID,
}
q := s.conn.Create(&newInstance)
if q.Error != nil {
return params.Instance{}, errors.Wrap(q.Error, "creating instance")
}
return s.sqlToParamsInstance(newInstance)
}
func (s *sqlDatabase) ListScaleSetInstances(_ context.Context, scalesetID uint) ([]params.Instance, error) {
var instances []Instance
query := s.conn.Model(&Instance{}).Preload("Job").Where("scale_set_fk_id = ?", scalesetID)
if err := query.Find(&instances); err.Error != nil {
return nil, errors.Wrap(err.Error, "fetching instances")
}
var err error
ret := make([]params.Instance, len(instances))
for idx, inst := range instances {
ret[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return nil, errors.Wrap(err, "converting instance")
}
}
return ret, nil
}

436
database/sql/scalesets.go Normal file
View file

@ -0,0 +1,436 @@
// Copyright 2024 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 sql
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
)
func (s *sqlDatabase) ListAllScaleSets(_ context.Context) ([]params.ScaleSet, error) {
var scaleSets []ScaleSet
q := s.conn.Model(&ScaleSet{}).
Preload("Organization").
Preload("Repository").
Preload("Enterprise").
Omit("extra_specs").
Omit("status_messages").
Find(&scaleSets)
if q.Error != nil {
return nil, errors.Wrap(q.Error, "fetching all scale sets")
}
ret := make([]params.ScaleSet, len(scaleSets))
var err error
for idx, val := range scaleSets {
ret[idx], err = s.sqlToCommonScaleSet(val)
if err != nil {
return nil, errors.Wrap(err, "converting scale sets")
}
}
return ret, nil
}
func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.GithubEntity, param params.CreateScaleSetParams) (scaleSet params.ScaleSet, err error) {
if err := param.Validate(); err != nil {
return params.ScaleSet{}, fmt.Errorf("failed to validate create params: %w", err)
}
defer func() {
if err == nil {
s.sendNotify(common.ScaleSetEntityType, common.CreateOperation, scaleSet)
}
}()
newScaleSet := ScaleSet{
Name: param.Name,
ScaleSetID: param.ScaleSetID,
DisableUpdate: param.DisableUpdate,
ProviderName: param.ProviderName,
RunnerPrefix: param.GetRunnerPrefix(),
MaxRunners: param.MaxRunners,
MinIdleRunners: param.MinIdleRunners,
RunnerBootstrapTimeout: param.RunnerBootstrapTimeout,
Image: param.Image,
Flavor: param.Flavor,
OSType: param.OSType,
OSArch: param.OSArch,
Enabled: param.Enabled,
GitHubRunnerGroup: param.GitHubRunnerGroup,
State: params.ScaleSetPendingCreate,
}
if len(param.ExtraSpecs) > 0 {
newScaleSet.ExtraSpecs = datatypes.JSON(param.ExtraSpecs)
}
entityID, err := uuid.Parse(entity.ID)
if err != nil {
return params.ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
switch entity.EntityType {
case params.GithubEntityTypeRepository:
newScaleSet.RepoID = &entityID
case params.GithubEntityTypeOrganization:
newScaleSet.OrgID = &entityID
case params.GithubEntityTypeEnterprise:
newScaleSet.EnterpriseID = &entityID
}
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := s.hasGithubEntity(tx, entity.EntityType, entity.ID); err != nil {
return errors.Wrap(err, "checking entity existence")
}
q := tx.Create(&newScaleSet)
if q.Error != nil {
return errors.Wrap(q.Error, "creating scale set")
}
return nil
})
if err != nil {
return params.ScaleSet{}, err
}
dbScaleSet, err := s.getScaleSetByID(s.conn, newScaleSet.ID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set")
}
return s.sqlToCommonScaleSet(dbScaleSet)
}
func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.GithubEntityType, entityID string, preload ...string) ([]ScaleSet, error) {
if _, err := uuid.Parse(entityID); err != nil {
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
if err := s.hasGithubEntity(tx, entityType, entityID); err != nil {
return nil, errors.Wrap(err, "checking entity existence")
}
var preloadEntity string
var fieldName string
switch entityType {
case params.GithubEntityTypeRepository:
fieldName = entityTypeRepoName
preloadEntity = repositoryFieldName
case params.GithubEntityTypeOrganization:
fieldName = entityTypeOrgName
preloadEntity = organizationFieldName
case params.GithubEntityTypeEnterprise:
fieldName = entityTypeEnterpriseName
preloadEntity = enterpriseFieldName
default:
return nil, fmt.Errorf("invalid entityType: %v", entityType)
}
q := tx
q = q.Preload(preloadEntity)
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
var scaleSets []ScaleSet
condition := fmt.Sprintf("%s = ?", fieldName)
err := q.Model(&ScaleSet{}).
Where(condition, entityID).
Omit("extra_specs").
Omit("status_messages").
Find(&scaleSets).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return []ScaleSet{}, nil
}
return nil, errors.Wrap(err, "fetching scale sets")
}
return scaleSets, nil
}
func (s *sqlDatabase) ListEntityScaleSets(_ context.Context, entity params.GithubEntity) ([]params.ScaleSet, error) {
scaleSets, err := s.listEntityScaleSets(s.conn, entity.EntityType, entity.ID)
if err != nil {
return nil, errors.Wrap(err, "fetching scale sets")
}
ret := make([]params.ScaleSet, len(scaleSets))
for idx, set := range scaleSets {
ret[idx], err = s.sqlToCommonScaleSet(set)
if err != nil {
return nil, errors.Wrap(err, "conbverting scale set")
}
}
return ret, nil
}
func (s *sqlDatabase) UpdateEntityScaleSet(_ context.Context, entity params.GithubEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(old, newSet params.ScaleSet) error) (updatedScaleSet params.ScaleSet, err error) {
defer func() {
if err == nil {
s.sendNotify(common.ScaleSetEntityType, common.UpdateOperation, updatedScaleSet)
}
}()
err = s.conn.Transaction(func(tx *gorm.DB) error {
scaleSet, err := s.getEntityScaleSet(tx, entity.EntityType, entity.ID, scaleSetID, "Instances")
if err != nil {
return errors.Wrap(err, "fetching scale set")
}
old, err := s.sqlToCommonScaleSet(scaleSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
}
updatedScaleSet, err = s.updateScaleSet(tx, scaleSet, param)
if err != nil {
return errors.Wrap(err, "updating scale set")
}
if callback != nil {
if err := callback(old, updatedScaleSet); err != nil {
return errors.Wrap(err, "executing update callback")
}
}
return nil
})
if err != nil {
return params.ScaleSet{}, err
}
return updatedScaleSet, nil
}
func (s *sqlDatabase) getEntityScaleSet(tx *gorm.DB, entityType params.GithubEntityType, entityID string, scaleSetID uint, preload ...string) (ScaleSet, error) {
if entityID == "" {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
}
if scaleSetID == 0 {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing scaleset id")
}
var fieldName string
var entityField string
switch entityType {
case params.GithubEntityTypeRepository:
fieldName = entityTypeRepoName
entityField = "Repository"
case params.GithubEntityTypeOrganization:
fieldName = entityTypeOrgName
entityField = "Organization"
case params.GithubEntityTypeEnterprise:
fieldName = entityTypeEnterpriseName
entityField = "Enterprise"
default:
return ScaleSet{}, fmt.Errorf("invalid entityType: %v", entityType)
}
q := tx
q = q.Preload(entityField)
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
var scaleSet ScaleSet
condition := fmt.Sprintf("id = ? and %s = ?", fieldName)
err := q.Model(&ScaleSet{}).
Where(condition, scaleSetID, entityID).
First(&scaleSet).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrNotFound, "finding scale set")
}
return ScaleSet{}, errors.Wrap(err, "fetching scale set")
}
return scaleSet, nil
}
func (s *sqlDatabase) updateScaleSet(tx *gorm.DB, scaleSet ScaleSet, param params.UpdateScaleSetParams) (params.ScaleSet, error) {
if param.Enabled != nil && scaleSet.Enabled != *param.Enabled {
scaleSet.Enabled = *param.Enabled
}
if param.State != nil && *param.State != scaleSet.State {
scaleSet.State = *param.State
}
if param.ExtendedState != nil && *param.ExtendedState != scaleSet.ExtendedState {
scaleSet.ExtendedState = *param.ExtendedState
}
if param.Name != "" {
scaleSet.Name = param.Name
}
if param.GitHubRunnerGroup != nil && *param.GitHubRunnerGroup != "" {
scaleSet.GitHubRunnerGroup = *param.GitHubRunnerGroup
}
if param.Flavor != "" {
scaleSet.Flavor = param.Flavor
}
if param.Image != "" {
scaleSet.Image = param.Image
}
if param.Prefix != "" {
scaleSet.RunnerPrefix = param.Prefix
}
if param.MaxRunners != nil {
scaleSet.MaxRunners = *param.MaxRunners
}
if param.MinIdleRunners != nil {
scaleSet.MinIdleRunners = *param.MinIdleRunners
}
if param.OSArch != "" {
scaleSet.OSArch = param.OSArch
}
if param.OSType != "" {
scaleSet.OSType = param.OSType
}
if param.ExtraSpecs != nil {
scaleSet.ExtraSpecs = datatypes.JSON(param.ExtraSpecs)
}
if param.RunnerBootstrapTimeout != nil && *param.RunnerBootstrapTimeout > 0 {
scaleSet.RunnerBootstrapTimeout = *param.RunnerBootstrapTimeout
}
if param.GitHubRunnerGroup != nil {
scaleSet.GitHubRunnerGroup = *param.GitHubRunnerGroup
}
if q := tx.Save(&scaleSet); q.Error != nil {
return params.ScaleSet{}, errors.Wrap(q.Error, "saving database entry")
}
return s.sqlToCommonScaleSet(scaleSet)
}
func (s *sqlDatabase) GetScaleSetByID(_ context.Context, scaleSet uint) (params.ScaleSet, error) {
set, err := s.getScaleSetByID(s.conn, scaleSet, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set by ID")
}
return s.sqlToCommonScaleSet(set)
}
func (s *sqlDatabase) DeleteScaleSetByID(_ context.Context, scaleSetID uint) (err error) {
var scaleSet params.ScaleSet
defer func() {
if err == nil && scaleSet.ID != 0 {
s.sendNotify(common.ScaleSetEntityType, common.DeleteOperation, scaleSet)
}
}()
err = s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return errors.Wrap(err, "fetching scale set")
}
if len(dbSet.Instances) > 0 {
return runnerErrors.NewBadRequestError("cannot delete scaleset with runners")
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
}
if q := tx.Unscoped().Delete(&dbSet); q.Error != nil {
return errors.Wrap(q.Error, "deleting scale set")
}
return nil
})
if err != nil {
return errors.Wrap(err, "removing scale set")
}
return nil
}
func (s *sqlDatabase) SetScaleSetLastMessageID(_ context.Context, scaleSetID uint, lastMessageID int64) (err error) {
var scaleSet params.ScaleSet
defer func() {
if err == nil && scaleSet.ID != 0 {
s.sendNotify(common.ScaleSetEntityType, common.UpdateOperation, scaleSet)
}
}()
if err := s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID)
if err != nil {
return errors.Wrap(err, "fetching scale set")
}
dbSet.LastMessageID = lastMessageID
if err := tx.Save(&dbSet).Error; err != nil {
return errors.Wrap(err, "saving database entry")
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
}
return nil
}); err != nil {
return errors.Wrap(err, "setting last message ID")
}
return nil
}
func (s *sqlDatabase) SetScaleSetDesiredRunnerCount(_ context.Context, scaleSetID uint, desiredRunnerCount int) (err error) {
var scaleSet params.ScaleSet
defer func() {
if err == nil && scaleSet.ID != 0 {
s.sendNotify(common.ScaleSetEntityType, common.UpdateOperation, scaleSet)
}
}()
if err := s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID)
if err != nil {
return errors.Wrap(err, "fetching scale set")
}
dbSet.DesiredRunnerCount = desiredRunnerCount
if err := tx.Save(&dbSet).Error; err != nil {
return errors.Wrap(err, "saving database entry")
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
}
return nil
}); err != nil {
return errors.Wrap(err, "setting desired runner count")
}
return nil
}

View file

@ -0,0 +1,354 @@
package sql
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/suite"
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/database/watcher"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
type ScaleSetsTestSuite struct {
suite.Suite
Store dbCommon.Store
adminCtx context.Context
creds params.GithubCredentials
org params.Organization
repo params.Repository
enterprise params.Enterprise
orgEntity params.GithubEntity
repoEntity params.GithubEntity
enterpriseEntity params.GithubEntity
}
func (s *ScaleSetsTestSuite) SetupTest() {
// create testing sqlite database
ctx := context.Background()
watcher.InitWatcher(ctx)
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(ctx, db, s.T())
s.adminCtx = adminCtx
githubEndpoint := garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.creds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), githubEndpoint)
// create an organization for testing purposes
s.org, err = s.Store.CreateOrganization(s.adminCtx, "test-org", s.creds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create org: %s", err))
}
s.repo, err = s.Store.CreateRepository(s.adminCtx, "test-org", "test-repo", s.creds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create repo: %s", err))
}
s.enterprise, err = s.Store.CreateEnterprise(s.adminCtx, "test-enterprise", s.creds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create enterprise: %s", err))
}
s.orgEntity, err = s.org.GetEntity()
if err != nil {
s.FailNow(fmt.Sprintf("failed to get org entity: %s", err))
}
s.repoEntity, err = s.repo.GetEntity()
if err != nil {
s.FailNow(fmt.Sprintf("failed to get repo entity: %s", err))
}
s.enterpriseEntity, err = s.enterprise.GetEntity()
if err != nil {
s.FailNow(fmt.Sprintf("failed to get enterprise entity: %s", err))
}
s.T().Cleanup(func() {
err := s.Store.DeleteOrganization(s.adminCtx, s.org.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete org: %s", err))
}
err = s.Store.DeleteRepository(s.adminCtx, s.repo.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete repo: %s", err))
}
err = s.Store.DeleteEnterprise(s.adminCtx, s.enterprise.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete enterprise: %s", err))
}
})
}
func (s *ScaleSetsTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *ScaleSetsTestSuite) callback(old, newSet params.ScaleSet) error {
s.Require().Equal(old.Name, "test-scaleset")
s.Require().Equal(newSet.Name, "test-scaleset-updated")
s.Require().Equal(old.OSType, commonParams.Linux)
s.Require().Equal(newSet.OSType, commonParams.Windows)
s.Require().Equal(old.OSArch, commonParams.Amd64)
s.Require().Equal(newSet.OSArch, commonParams.Arm64)
s.Require().Equal(old.ExtraSpecs, json.RawMessage(`{"test": 1}`))
s.Require().Equal(newSet.ExtraSpecs, json.RawMessage(`{"test": 111}`))
s.Require().Equal(old.MaxRunners, uint(10))
s.Require().Equal(newSet.MaxRunners, uint(60))
s.Require().Equal(old.MinIdleRunners, uint(5))
s.Require().Equal(newSet.MinIdleRunners, uint(50))
s.Require().Equal(old.Image, "test-image")
s.Require().Equal(newSet.Image, "new-test-image")
s.Require().Equal(old.Flavor, "test-flavor")
s.Require().Equal(newSet.Flavor, "new-test-flavor")
s.Require().Equal(old.GitHubRunnerGroup, "test-group")
s.Require().Equal(newSet.GitHubRunnerGroup, "new-test-group")
s.Require().Equal(old.RunnerPrefix.Prefix, "garm")
s.Require().Equal(newSet.RunnerPrefix.Prefix, "test-prefix2")
s.Require().Equal(old.Enabled, false)
s.Require().Equal(newSet.Enabled, true)
return nil
}
func (s *ScaleSetsTestSuite) TestScaleSetOperations() {
// create a scale set for the organization
createScaleSetPrams := params.CreateScaleSetParams{
Name: "test-scaleset",
ProviderName: "test-provider",
MaxRunners: 10,
MinIdleRunners: 5,
Image: "test-image",
Flavor: "test-flavor",
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
ExtraSpecs: json.RawMessage(`{"test": 1}`),
GitHubRunnerGroup: "test-group",
}
var orgScaleSet params.ScaleSet
var repoScaleSet params.ScaleSet
var enterpriseScaleSet params.ScaleSet
var err error
s.T().Run("create org scaleset", func(_ *testing.T) {
orgScaleSet, err = s.Store.CreateEntityScaleSet(s.adminCtx, s.orgEntity, createScaleSetPrams)
s.Require().NoError(err)
s.Require().NotNil(orgScaleSet)
s.Require().Equal(orgScaleSet.Name, createScaleSetPrams.Name)
s.T().Cleanup(func() {
err := s.Store.DeleteScaleSetByID(s.adminCtx, orgScaleSet.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete scaleset: %s", err))
}
})
})
s.T().Run("create repo scaleset", func(_ *testing.T) {
repoScaleSet, err = s.Store.CreateEntityScaleSet(s.adminCtx, s.repoEntity, createScaleSetPrams)
s.Require().NoError(err)
s.Require().NotNil(repoScaleSet)
s.Require().Equal(repoScaleSet.Name, createScaleSetPrams.Name)
s.T().Cleanup(func() {
err := s.Store.DeleteScaleSetByID(s.adminCtx, repoScaleSet.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete scaleset: %s", err))
}
})
})
s.T().Run("create enterprise scaleset", func(_ *testing.T) {
enterpriseScaleSet, err = s.Store.CreateEntityScaleSet(s.adminCtx, s.enterpriseEntity, createScaleSetPrams)
s.Require().NoError(err)
s.Require().NotNil(enterpriseScaleSet)
s.Require().Equal(enterpriseScaleSet.Name, createScaleSetPrams.Name)
s.T().Cleanup(func() {
err := s.Store.DeleteScaleSetByID(s.adminCtx, enterpriseScaleSet.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete scaleset: %s", err))
}
})
})
s.T().Run("create list all scalesets", func(_ *testing.T) {
allScaleSets, err := s.Store.ListAllScaleSets(s.adminCtx)
s.Require().NoError(err)
s.Require().NotEmpty(allScaleSets)
s.Require().Len(allScaleSets, 3)
})
s.T().Run("list repo scalesets", func(_ *testing.T) {
repoScaleSets, err := s.Store.ListEntityScaleSets(s.adminCtx, s.repoEntity)
s.Require().NoError(err)
s.Require().NotEmpty(repoScaleSets)
s.Require().Len(repoScaleSets, 1)
})
s.T().Run("list org scalesets", func(_ *testing.T) {
orgScaleSets, err := s.Store.ListEntityScaleSets(s.adminCtx, s.orgEntity)
s.Require().NoError(err)
s.Require().NotEmpty(orgScaleSets)
s.Require().Len(orgScaleSets, 1)
})
s.T().Run("list enterprise scalesets", func(_ *testing.T) {
enterpriseScaleSets, err := s.Store.ListEntityScaleSets(s.adminCtx, s.enterpriseEntity)
s.Require().NoError(err)
s.Require().NotEmpty(enterpriseScaleSets)
s.Require().Len(enterpriseScaleSets, 1)
})
s.T().Run("get repo scaleset by ID", func(_ *testing.T) {
repoScaleSetByID, err := s.Store.GetScaleSetByID(s.adminCtx, repoScaleSet.ID)
s.Require().NoError(err)
s.Require().NotNil(repoScaleSetByID)
s.Require().Equal(repoScaleSetByID.ID, repoScaleSet.ID)
s.Require().Equal(repoScaleSetByID.Name, repoScaleSet.Name)
})
s.T().Run("get org scaleset by ID", func(_ *testing.T) {
orgScaleSetByID, err := s.Store.GetScaleSetByID(s.adminCtx, orgScaleSet.ID)
s.Require().NoError(err)
s.Require().NotNil(orgScaleSetByID)
s.Require().Equal(orgScaleSetByID.ID, orgScaleSet.ID)
s.Require().Equal(orgScaleSetByID.Name, orgScaleSet.Name)
})
s.T().Run("get enterprise scaleset by ID", func(_ *testing.T) {
enterpriseScaleSetByID, err := s.Store.GetScaleSetByID(s.adminCtx, enterpriseScaleSet.ID)
s.Require().NoError(err)
s.Require().NotNil(enterpriseScaleSetByID)
s.Require().Equal(enterpriseScaleSetByID.ID, enterpriseScaleSet.ID)
s.Require().Equal(enterpriseScaleSetByID.Name, enterpriseScaleSet.Name)
})
s.T().Run("get scaleset by ID not found", func(_ *testing.T) {
_, err = s.Store.GetScaleSetByID(s.adminCtx, 999)
s.Require().Error(err)
s.Require().Contains(err.Error(), "not found")
})
s.T().Run("Set scale set last message ID and desired count", func(_ *testing.T) {
err = s.Store.SetScaleSetLastMessageID(s.adminCtx, orgScaleSet.ID, 20)
s.Require().NoError(err)
err = s.Store.SetScaleSetDesiredRunnerCount(s.adminCtx, orgScaleSet.ID, 5)
s.Require().NoError(err)
orgScaleSetByID, err := s.Store.GetScaleSetByID(s.adminCtx, orgScaleSet.ID)
s.Require().NoError(err)
s.Require().NotNil(orgScaleSetByID)
s.Require().Equal(orgScaleSetByID.LastMessageID, int64(20))
s.Require().Equal(orgScaleSetByID.DesiredRunnerCount, 5)
})
updateParams := params.UpdateScaleSetParams{
Name: "test-scaleset-updated",
RunnerPrefix: params.RunnerPrefix{
Prefix: "test-prefix2",
},
OSType: commonParams.Windows,
OSArch: commonParams.Arm64,
ExtraSpecs: json.RawMessage(`{"test": 111}`),
Enabled: garmTesting.Ptr(true),
MaxRunners: garmTesting.Ptr(uint(60)),
MinIdleRunners: garmTesting.Ptr(uint(50)),
Image: "new-test-image",
Flavor: "new-test-flavor",
GitHubRunnerGroup: garmTesting.Ptr("new-test-group"),
}
s.T().Run("update repo scaleset", func(_ *testing.T) {
newRepoScaleSet, err := s.Store.UpdateEntityScaleSet(s.adminCtx, s.repoEntity, repoScaleSet.ID, updateParams, s.callback)
s.Require().NoError(err)
s.Require().NotNil(newRepoScaleSet)
s.Require().NoError(s.callback(repoScaleSet, newRepoScaleSet))
})
s.T().Run("update org scaleset", func(_ *testing.T) {
newOrgScaleSet, err := s.Store.UpdateEntityScaleSet(s.adminCtx, s.orgEntity, orgScaleSet.ID, updateParams, s.callback)
s.Require().NoError(err)
s.Require().NotNil(newOrgScaleSet)
s.Require().NoError(s.callback(orgScaleSet, newOrgScaleSet))
})
s.T().Run("update enterprise scaleset", func(_ *testing.T) {
newEnterpriseScaleSet, err := s.Store.UpdateEntityScaleSet(s.adminCtx, s.enterpriseEntity, enterpriseScaleSet.ID, updateParams, s.callback)
s.Require().NoError(err)
s.Require().NotNil(newEnterpriseScaleSet)
s.Require().NoError(s.callback(enterpriseScaleSet, newEnterpriseScaleSet))
})
s.T().Run("update scaleset not found", func(_ *testing.T) {
_, err = s.Store.UpdateEntityScaleSet(s.adminCtx, s.enterpriseEntity, 99999, updateParams, s.callback)
s.Require().Error(err)
s.Require().Contains(err.Error(), "not found")
})
s.T().Run("update scaleset with invalid entity", func(_ *testing.T) {
_, err = s.Store.UpdateEntityScaleSet(s.adminCtx, params.GithubEntity{}, enterpriseScaleSet.ID, params.UpdateScaleSetParams{}, nil)
s.Require().Error(err)
s.Require().Contains(err.Error(), "missing entity id")
})
s.T().Run("Create repo scale set instance", func(_ *testing.T) {
param := params.CreateInstanceParams{
Name: "test-instance",
Status: commonParams.InstancePendingCreate,
RunnerStatus: params.RunnerPending,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
CallbackURL: "http://localhost:8080/callback",
MetadataURL: "http://localhost:8080/metadata",
GitHubRunnerGroup: "test-group",
JitConfiguration: map[string]string{
"test": "test",
},
AgentID: 5,
}
instance, err := s.Store.CreateScaleSetInstance(s.adminCtx, repoScaleSet.ID, param)
s.Require().NoError(err)
s.Require().NotNil(instance)
s.Require().Equal(instance.Name, param.Name)
s.Require().Equal(instance.Status, param.Status)
s.Require().Equal(instance.RunnerStatus, param.RunnerStatus)
s.Require().Equal(instance.OSType, param.OSType)
s.Require().Equal(instance.OSArch, param.OSArch)
s.Require().Equal(instance.CallbackURL, param.CallbackURL)
s.Require().Equal(instance.MetadataURL, param.MetadataURL)
s.Require().Equal(instance.GitHubRunnerGroup, param.GitHubRunnerGroup)
s.Require().Equal(instance.JitConfiguration, param.JitConfiguration)
s.Require().Equal(instance.AgentID, param.AgentID)
s.T().Cleanup(func() {
err := s.Store.DeleteInstanceByName(s.adminCtx, instance.Name)
if err != nil {
s.FailNow(fmt.Sprintf("failed to delete scaleset instance: %s", err))
}
})
})
s.T().Run("List repo scale set instances", func(_ *testing.T) {
instances, err := s.Store.ListScaleSetInstances(s.adminCtx, repoScaleSet.ID)
s.Require().NoError(err)
s.Require().NotEmpty(instances)
s.Require().Len(instances, 1)
})
}
func TestScaleSetsTestSuite(t *testing.T) {
suite.Run(t, new(ScaleSetsTestSuite))
}

View file

@ -36,6 +36,12 @@ import (
"github.com/cloudbase/garm/util/appdefaults"
)
const (
repositoryFieldName string = "Repository"
organizationFieldName string = "Organization"
enterpriseFieldName string = "Enterprise"
)
// newDBConn returns a new gorm db connection, given the config
func newDBConn(dbCfg config.Database) (conn *gorm.DB, err error) {
dbType, connURI, err := dbCfg.GormParams()
@ -423,11 +429,15 @@ func (s *sqlDatabase) migrateDB() error {
&Repository{},
&Organization{},
&Enterprise{},
&EnterpriseEvent{},
&OrganizationEvent{},
&RepositoryEvent{},
&Address{},
&InstanceStatusUpdate{},
&Instance{},
&ControllerInfo{},
&WorkflowJob{},
&ScaleSet{},
); err != nil {
return errors.Wrap(err, "running auto migrate")
}

View file

@ -28,6 +28,7 @@ import (
"gorm.io/gorm/logger"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/database/watcher"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
@ -53,7 +54,13 @@ func (s *UserTestSuite) assertSQLMockExpectations() {
}
}
func (s *UserTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *UserTestSuite) SetupTest() {
ctx := context.Background()
watcher.InitWatcher(ctx)
// create testing sqlite database
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {

View file

@ -15,6 +15,7 @@
package sql
import (
"context"
"encoding/json"
"fmt"
@ -60,7 +61,6 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) (params.Instance, e
OSArch: instance.OSArch,
Status: instance.Status,
RunnerStatus: instance.RunnerStatus,
PoolID: instance.PoolID.String(),
CallbackURL: instance.CallbackURL,
MetadataURL: instance.MetadataURL,
StatusMessages: []params.StatusMessage{},
@ -73,6 +73,24 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) (params.Instance, e
AditionalLabels: labels,
}
if instance.ScaleSetFkID != nil {
ret.ScaleSetID = *instance.ScaleSetFkID
ret.ProviderName = instance.ScaleSet.ProviderName
}
if instance.PoolID != nil {
ret.PoolID = instance.PoolID.String()
ret.ProviderName = instance.Pool.ProviderName
}
if ret.ScaleSetID == 0 && ret.PoolID == "" {
return params.Instance{}, errors.New("missing pool or scale set id")
}
if ret.ScaleSetID != 0 && ret.PoolID != "" {
return params.Instance{}, errors.New("both pool and scale set ids are set")
}
if instance.Job != nil {
paramJob, err := sqlWorkflowJobToParamsJob(*instance.Job)
if err != nil {
@ -265,6 +283,62 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
return ret, nil
}
func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, error) {
ret := params.ScaleSet{
ID: scaleSet.ID,
ScaleSetID: scaleSet.ScaleSetID,
Name: scaleSet.Name,
DisableUpdate: scaleSet.DisableUpdate,
ProviderName: scaleSet.ProviderName,
MaxRunners: scaleSet.MaxRunners,
MinIdleRunners: scaleSet.MinIdleRunners,
RunnerPrefix: params.RunnerPrefix{
Prefix: scaleSet.RunnerPrefix,
},
Image: scaleSet.Image,
Flavor: scaleSet.Flavor,
OSArch: scaleSet.OSArch,
OSType: scaleSet.OSType,
Enabled: scaleSet.Enabled,
Instances: make([]params.Instance, len(scaleSet.Instances)),
RunnerBootstrapTimeout: scaleSet.RunnerBootstrapTimeout,
ExtraSpecs: json.RawMessage(scaleSet.ExtraSpecs),
GitHubRunnerGroup: scaleSet.GitHubRunnerGroup,
State: scaleSet.State,
ExtendedState: scaleSet.ExtendedState,
LastMessageID: scaleSet.LastMessageID,
DesiredRunnerCount: scaleSet.DesiredRunnerCount,
}
if scaleSet.RepoID != nil {
ret.RepoID = scaleSet.RepoID.String()
if scaleSet.Repository.Owner != "" && scaleSet.Repository.Name != "" {
ret.RepoName = fmt.Sprintf("%s/%s", scaleSet.Repository.Owner, scaleSet.Repository.Name)
}
}
if scaleSet.OrgID != nil && scaleSet.Organization.Name != "" {
ret.OrgID = scaleSet.OrgID.String()
ret.OrgName = scaleSet.Organization.Name
}
if scaleSet.EnterpriseID != nil && scaleSet.Enterprise.Name != "" {
ret.EnterpriseID = scaleSet.EnterpriseID.String()
ret.EnterpriseName = scaleSet.Enterprise.Name
}
var err error
for idx, inst := range scaleSet.Instances {
ret.Instances[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "converting instance")
}
}
return ret, nil
}
func (s *sqlDatabase) sqlToCommonTags(tag Tag) params.Tag {
return params.Tag{
ID: tag.ID.String(),
@ -452,6 +526,26 @@ func (s *sqlDatabase) getPoolByID(tx *gorm.DB, poolID string, preload ...string)
return pool, nil
}
func (s *sqlDatabase) getScaleSetByID(tx *gorm.DB, scaleSetID uint, preload ...string) (ScaleSet, error) {
var scaleSet ScaleSet
q := tx.Model(&ScaleSet{})
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
q = q.Where("id = ?", scaleSetID).First(&scaleSet)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return ScaleSet{}, runnerErrors.ErrNotFound
}
return ScaleSet{}, errors.Wrap(q.Error, "fetching scale set from database")
}
return scaleSet, nil
}
func (s *sqlDatabase) hasGithubEntity(tx *gorm.DB, entityType params.GithubEntityType, entityID string) error {
u, err := uuid.Parse(entityID)
if err != nil {
@ -513,3 +607,159 @@ func (s *sqlDatabase) sendNotify(entityType dbCommon.DatabaseEntityType, op dbCo
}
return s.producer.Notify(message)
}
func (s *sqlDatabase) GetGithubEntity(_ context.Context, entityType params.GithubEntityType, entityID string) (params.GithubEntity, error) {
var ghEntity params.EntityGetter
var err error
switch entityType {
case params.GithubEntityTypeEnterprise:
ghEntity, err = s.GetEnterpriseByID(s.ctx, entityID)
case params.GithubEntityTypeOrganization:
ghEntity, err = s.GetOrganizationByID(s.ctx, entityID)
case params.GithubEntityTypeRepository:
ghEntity, err = s.GetRepositoryByID(s.ctx, entityID)
default:
return params.GithubEntity{}, errors.Wrap(runnerErrors.ErrBadRequest, "invalid entity type")
}
if err != nil {
return params.GithubEntity{}, errors.Wrap(err, "failed to get ")
}
entity, err := ghEntity.GetEntity()
if err != nil {
return params.GithubEntity{}, errors.Wrap(err, "failed to get entity")
}
return entity, nil
}
func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
repo, err := s.GetRepositoryByID(ctx, repoID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
msg := InstanceStatusUpdate{
Message: statusMessage,
EventType: event,
EventLevel: eventLevel,
}
if err := s.conn.Model(&repo).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
}
if maxEvents > 0 {
repoID, err := uuid.Parse(repo.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []OrganizationEvent
q := s.conn.Model(&OrganizationEvent{}).
Limit(maxEvents).Order("id desc").
Where("repo_id = ?", repoID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("repo_id = ? and id < ?", repoID, lastInList.ID).Unscoped().Delete(&OrganizationEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}
}
return nil
}
func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
org, err := s.GetOrganizationByID(ctx, orgID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
msg := InstanceStatusUpdate{
Message: statusMessage,
EventType: event,
EventLevel: eventLevel,
}
if err := s.conn.Model(&org).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
}
if maxEvents > 0 {
orgID, err := uuid.Parse(org.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []OrganizationEvent
q := s.conn.Model(&OrganizationEvent{}).
Limit(maxEvents).Order("id desc").
Where("org_id = ?", orgID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("org_id = ? and id < ?", orgID, lastInList.ID).Unscoped().Delete(&OrganizationEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}
}
return nil
}
func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
ent, err := s.GetEnterpriseByID(ctx, entID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
msg := InstanceStatusUpdate{
Message: statusMessage,
EventType: event,
EventLevel: eventLevel,
}
if err := s.conn.Model(&ent).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
}
if maxEvents > 0 {
entID, err := uuid.Parse(ent.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []EnterpriseEvent
q := s.conn.Model(&EnterpriseEvent{}).
Limit(maxEvents).Order("id desc").
Where("enterprise_id = ?", entID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("enterprise_id = ? and id < ?", entID, lastInList.ID).Unscoped().Delete(&EnterpriseEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}
}
return nil
}
func (s *sqlDatabase) AddEntityEvent(ctx context.Context, entity params.GithubEntity, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
if maxEvents == 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "max events cannot be 0")
}
switch entity.EntityType {
case params.GithubEntityTypeRepository:
return s.addRepositoryEvent(ctx, entity.ID, event, eventLevel, statusMessage, maxEvents)
case params.GithubEntityTypeOrganization:
return s.addOrgEvent(ctx, entity.ID, event, eventLevel, statusMessage, maxEvents)
case params.GithubEntityTypeEnterprise:
return s.addEnterpriseEvent(ctx, entity.ID, event, eventLevel, statusMessage, maxEvents)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid entity type")
}
}

View file

@ -1,11 +1,12 @@
package watcher
import (
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
)
type idGetter interface {
type IDGetter interface {
GetID() string
}
@ -72,21 +73,41 @@ func WithEntityPoolFilter(ghEntity params.GithubEntity) dbCommon.PayloadFilterFu
}
switch ghEntity.EntityType {
case params.GithubEntityTypeRepository:
if pool.RepoID != ghEntity.ID {
return false
}
return pool.RepoID == ghEntity.ID
case params.GithubEntityTypeOrganization:
if pool.OrgID != ghEntity.ID {
return false
}
return pool.OrgID == ghEntity.ID
case params.GithubEntityTypeEnterprise:
if pool.EnterpriseID != ghEntity.ID {
return false
}
return pool.EnterpriseID == ghEntity.ID
default:
return false
}
default:
return false
}
}
}
// WithEntityPoolFilter returns true if the change payload is a pool that belongs to the
// supplied Github entity. This is useful when an entity worker wants to watch for changes
// in pools that belong to it.
func WithEntityScaleSetFilter(ghEntity params.GithubEntity) dbCommon.PayloadFilterFunc {
return func(payload dbCommon.ChangePayload) bool {
switch payload.EntityType {
case dbCommon.ScaleSetEntityType:
scaleSet, ok := payload.Payload.(params.ScaleSet)
if !ok {
return false
}
switch ghEntity.EntityType {
case params.GithubEntityTypeRepository:
return scaleSet.RepoID == ghEntity.ID
case params.GithubEntityTypeOrganization:
return scaleSet.OrgID == ghEntity.ID
case params.GithubEntityTypeEnterprise:
return scaleSet.EnterpriseID == ghEntity.ID
default:
return false
}
return true
default:
return false
}
@ -100,7 +121,7 @@ func WithEntityFilter(entity params.GithubEntity) dbCommon.PayloadFilterFunc {
if params.GithubEntityType(payload.EntityType) != entity.EntityType {
return false
}
var ent idGetter
var ent IDGetter
var ok bool
switch payload.EntityType {
case dbCommon.RepositoryEntityType:
@ -210,3 +231,76 @@ func WithExcludeEntityTypeFilter(entityType dbCommon.DatabaseEntityType) dbCommo
return payload.EntityType != entityType
}
}
// WithScaleSetFilter returns a filter function that matches a particular scale set.
func WithScaleSetFilter(scaleset params.ScaleSet) dbCommon.PayloadFilterFunc {
return func(payload dbCommon.ChangePayload) bool {
if payload.EntityType != dbCommon.ScaleSetEntityType {
return false
}
ss, ok := payload.Payload.(params.ScaleSet)
if !ok {
return false
}
return ss.ID == scaleset.ID
}
}
func WithScaleSetInstanceFilter(scaleset params.ScaleSet) dbCommon.PayloadFilterFunc {
return func(payload dbCommon.ChangePayload) bool {
if payload.EntityType != dbCommon.InstanceEntityType {
return false
}
instance, ok := payload.Payload.(params.Instance)
if !ok || instance.ScaleSetID == 0 {
return false
}
return instance.ScaleSetID == scaleset.ID
}
}
// EntityTypeCallbackFilter is a callback function that takes a ChangePayload and returns a boolean.
// This callback type is used in the WithEntityTypeAndCallbackFilter (and potentially others) when
// a filter needs to delegate logic to a specific callback function.
type EntityTypeCallbackFilter func(payload dbCommon.ChangePayload) (bool, error)
// WithEntityTypeAndCallbackFilter returns a filter function that filters payloads by entity type and the
// result of a callback function.
func WithEntityTypeAndCallbackFilter(entityType dbCommon.DatabaseEntityType, callback EntityTypeCallbackFilter) dbCommon.PayloadFilterFunc {
return func(payload dbCommon.ChangePayload) bool {
if payload.EntityType != entityType {
return false
}
ok, err := callback(payload)
if err != nil {
return false
}
return ok
}
}
func WithInstanceStatusFilter(statuses ...commonParams.InstanceStatus) dbCommon.PayloadFilterFunc {
return func(payload dbCommon.ChangePayload) bool {
if payload.EntityType != dbCommon.InstanceEntityType {
return false
}
instance, ok := payload.Payload.(params.Instance)
if !ok {
return false
}
if len(statuses) == 0 {
return false
}
for _, status := range statuses {
if instance.Status == status {
return true
}
}
return false
}
}

View file

@ -17,7 +17,7 @@ func InitWatcher(ctx context.Context) {
if databaseWatcher != nil {
return
}
ctx = garmUtil.WithContext(ctx, slog.Any("watcher", "database"))
ctx = garmUtil.WithSlogContext(ctx, slog.Any("watcher", "database"))
w := &watcher{
producers: make(map[string]*producer),
consumers: make(map[string]*consumer),
@ -29,11 +29,20 @@ func InitWatcher(ctx context.Context) {
databaseWatcher = w
}
func CloseWatcher() error {
if databaseWatcher == nil {
return nil
}
databaseWatcher.Close()
databaseWatcher = nil
return nil
}
func RegisterProducer(ctx context.Context, id string) (common.Producer, error) {
if databaseWatcher == nil {
return nil, common.ErrWatcherNotInitialized
}
ctx = garmUtil.WithContext(ctx, slog.Any("producer_id", id))
ctx = garmUtil.WithSlogContext(ctx, slog.Any("producer_id", id))
return databaseWatcher.RegisterProducer(ctx, id)
}
@ -41,7 +50,7 @@ func RegisterConsumer(ctx context.Context, id string, filters ...common.PayloadF
if databaseWatcher == nil {
return nil, common.ErrWatcherNotInitialized
}
ctx = garmUtil.WithContext(ctx, slog.Any("consumer_id", id))
ctx = garmUtil.WithSlogContext(ctx, slog.Any("consumer_id", id))
return databaseWatcher.RegisterConsumer(ctx, id, filters...)
}

View file

@ -241,6 +241,112 @@ func (s *WatcherStoreTestSuite) TestInstanceWatcher() {
}
}
func (s *WatcherStoreTestSuite) TestScaleSetInstanceWatcher() {
consumer, err := watcher.RegisterConsumer(
s.ctx, "instance-test",
watcher.WithEntityTypeFilter(common.InstanceEntityType),
watcher.WithAny(
watcher.WithOperationTypeFilter(common.CreateOperation),
watcher.WithOperationTypeFilter(common.UpdateOperation),
watcher.WithOperationTypeFilter(common.DeleteOperation)),
)
s.Require().NoError(err)
s.Require().NotNil(consumer)
s.T().Cleanup(func() { consumer.Close() })
consumeEvents(consumer)
ep := garmTesting.CreateDefaultGithubEndpoint(s.ctx, s.store, s.T())
creds := garmTesting.CreateTestGithubCredentials(s.ctx, "test-creds", s.store, s.T(), ep)
s.T().Cleanup(func() { s.store.DeleteGithubCredentials(s.ctx, creds.ID) })
repo, err := s.store.CreateRepository(s.ctx, "test-owner", "test-repo", creds.Name, "test-secret", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotEmpty(repo.ID)
s.T().Cleanup(func() { s.store.DeleteRepository(s.ctx, repo.ID) })
entity, err := repo.GetEntity()
s.Require().NoError(err)
createScaleSetParams := params.CreateScaleSetParams{
ProviderName: "test-provider",
Name: "test-scaleset",
Image: "test-image",
Flavor: "test-flavor",
MinIdleRunners: 0,
MaxRunners: 1,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
}
scaleSet, err := s.store.CreateEntityScaleSet(s.ctx, entity, createScaleSetParams)
s.Require().NoError(err)
s.Require().NotEmpty(scaleSet.ID)
s.T().Cleanup(func() { s.store.DeleteScaleSetByID(s.ctx, scaleSet.ID) })
createInstanceParams := params.CreateInstanceParams{
Name: "test-instance",
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Status: commonParams.InstanceCreating,
}
instance, err := s.store.CreateScaleSetInstance(s.ctx, scaleSet.ID, createInstanceParams)
s.Require().NoError(err)
s.Require().NotEmpty(instance.ID)
select {
case event := <-consumer.Watch():
s.Require().Equal(common.ChangePayload{
EntityType: common.InstanceEntityType,
Operation: common.CreateOperation,
Payload: instance,
}, event)
asInstance, ok := event.Payload.(params.Instance)
s.Require().True(ok)
s.Require().Equal(instance.Name, "test-instance")
s.Require().Equal(asInstance.Name, "test-instance")
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
updateParams := params.UpdateInstanceParams{
RunnerStatus: params.RunnerActive,
}
updatedInstance, err := s.store.UpdateInstance(s.ctx, instance.Name, updateParams)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
s.Require().Equal(common.ChangePayload{
EntityType: common.InstanceEntityType,
Operation: common.UpdateOperation,
Payload: updatedInstance,
}, event)
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
err = s.store.DeleteInstanceByName(s.ctx, updatedInstance.Name)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
s.Require().Equal(common.ChangePayload{
EntityType: common.InstanceEntityType,
Operation: common.DeleteOperation,
Payload: params.Instance{
ID: updatedInstance.ID,
Name: updatedInstance.Name,
ProviderID: updatedInstance.ProviderID,
AgentID: updatedInstance.AgentID,
ScaleSetID: updatedInstance.ScaleSetID,
},
}, event)
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
}
func (s *WatcherStoreTestSuite) TestPoolWatcher() {
consumer, err := watcher.RegisterConsumer(
s.ctx, "pool-test",
@ -362,6 +468,134 @@ func (s *WatcherStoreTestSuite) TestPoolWatcher() {
}
}
func (s *WatcherStoreTestSuite) TestScaleSetWatcher() {
consumer, err := watcher.RegisterConsumer(
s.ctx, "scaleset-test",
watcher.WithEntityTypeFilter(common.ScaleSetEntityType),
watcher.WithAny(
watcher.WithOperationTypeFilter(common.CreateOperation),
watcher.WithOperationTypeFilter(common.UpdateOperation),
watcher.WithOperationTypeFilter(common.DeleteOperation)),
)
s.Require().NoError(err)
s.Require().NotNil(consumer)
s.T().Cleanup(func() { consumer.Close() })
consumeEvents(consumer)
ep := garmTesting.CreateDefaultGithubEndpoint(s.ctx, s.store, s.T())
creds := garmTesting.CreateTestGithubCredentials(s.ctx, "test-creds", s.store, s.T(), ep)
s.T().Cleanup(func() {
if err := s.store.DeleteGithubCredentials(s.ctx, creds.ID); err != nil {
s.T().Logf("failed to delete Github credentials: %v", err)
}
})
repo, err := s.store.CreateRepository(s.ctx, "test-owner", "test-repo", creds.Name, "test-secret", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotEmpty(repo.ID)
s.T().Cleanup(func() { s.store.DeleteRepository(s.ctx, repo.ID) })
entity, err := repo.GetEntity()
s.Require().NoError(err)
createScaleSetParams := params.CreateScaleSetParams{
ProviderName: "test-provider",
Name: "test-scaleset",
Image: "test-image",
Flavor: "test-flavor",
MinIdleRunners: 0,
MaxRunners: 1,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Tags: []string{"test-tag"},
}
scaleSet, err := s.store.CreateEntityScaleSet(s.ctx, entity, createScaleSetParams)
s.Require().NoError(err)
s.Require().NotEmpty(scaleSet.ID)
select {
case event := <-consumer.Watch():
s.Require().Equal(common.ChangePayload{
EntityType: common.ScaleSetEntityType,
Operation: common.CreateOperation,
Payload: scaleSet,
}, event)
asScaleSet, ok := event.Payload.(params.ScaleSet)
s.Require().True(ok)
s.Require().Equal(scaleSet.Image, "test-image")
s.Require().Equal(asScaleSet.Image, "test-image")
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
updateParams := params.UpdateScaleSetParams{
Flavor: "updated-flavor",
}
callbackFn := func(old, newScaleSet params.ScaleSet) error {
s.Require().Equal(old.ID, newScaleSet.ID)
s.Require().Equal(old.Flavor, "test-flavor")
s.Require().Equal(newScaleSet.Flavor, "updated-flavor")
return nil
}
updatedScaleSet, err := s.store.UpdateEntityScaleSet(s.ctx, entity, scaleSet.ID, updateParams, callbackFn)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
s.Require().Equal(common.ChangePayload{
EntityType: common.ScaleSetEntityType,
Operation: common.UpdateOperation,
Payload: updatedScaleSet,
}, event)
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
err = s.store.SetScaleSetLastMessageID(s.ctx, updatedScaleSet.ID, 99)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
asScaleSet, ok := event.Payload.(params.ScaleSet)
s.Require().True(ok)
s.Require().Equal(asScaleSet.ID, updatedScaleSet.ID)
s.Require().Equal(asScaleSet.LastMessageID, int64(99))
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
err = s.store.SetScaleSetDesiredRunnerCount(s.ctx, updatedScaleSet.ID, 5)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
asScaleSet, ok := event.Payload.(params.ScaleSet)
s.Require().True(ok)
s.Require().Equal(asScaleSet.ID, updatedScaleSet.ID)
s.Require().Equal(asScaleSet.DesiredRunnerCount, 5)
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
err = s.store.DeleteScaleSetByID(s.ctx, scaleSet.ID)
s.Require().NoError(err)
select {
case event := <-consumer.Watch():
// We updated last message ID and desired runner count above.
updatedScaleSet.DesiredRunnerCount = 5
updatedScaleSet.LastMessageID = 99
s.Require().Equal(common.ChangePayload{
EntityType: common.ScaleSetEntityType,
Operation: common.DeleteOperation,
Payload: updatedScaleSet,
}, event)
case <-time.After(1 * time.Second):
s.T().Fatal("expected payload not received")
}
}
func (s *WatcherStoreTestSuite) TestControllerWatcher() {
consumer, err := watcher.RegisterConsumer(
s.ctx, "controller-test",
@ -748,8 +982,11 @@ func consumeEvents(consumer common.Consumer) {
consume:
for {
select {
case <-consumer.Watch():
case _, ok := <-consumer.Watch():
// throw away event.
if !ok {
return
}
case <-time.After(100 * time.Millisecond):
break consume
}

21
go.mod
View file

@ -7,14 +7,14 @@ toolchain go1.23.6
require (
github.com/BurntSushi/toml v1.5.0
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0
github.com/cloudbase/garm-provider-common v0.1.4
github.com/cloudbase/garm-provider-common v0.1.5-0.20250417155201-8ef03502d06e
github.com/felixge/httpsnoop v1.0.4
github.com/go-openapi/errors v0.22.1
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/go-openapi/swag v0.23.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-github/v57 v57.0.0
github.com/google/go-github/v71 v71.0.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
@ -28,15 +28,15 @@ require (
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.37.0
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0
golang.org/x/crypto v0.38.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/datatypes v1.2.5
gorm.io/driver/mysql v1.5.7
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12
gorm.io/gorm v1.26.0
)
require (
@ -56,7 +56,6 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/go-github/v71 v71.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@ -77,7 +76,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
@ -87,9 +86,9 @@ require (
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

38
go.sum
View file

@ -19,8 +19,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cloudbase/garm-provider-common v0.1.4 h1:spRjl0PV4r8vKaCTNp6xBQbRKfls/cmbBEl/i/eGWSo=
github.com/cloudbase/garm-provider-common v0.1.4/go.mod h1:sK26i2NpjjAjhanNKiWw8iPkqt+XeohTKpFnEP7JdZ4=
github.com/cloudbase/garm-provider-common v0.1.5-0.20250417155201-8ef03502d06e h1:giq2Prk9I/ez1dc4/r9jivf2jbhjX9apZ41TWQ5g3qE=
github.com/cloudbase/garm-provider-common v0.1.5-0.20250417155201-8ef03502d06e/go.mod h1:sSrTBtTc0q72MZdmS9EuLLdDhkmXZAqAwRIgEK0TqUo=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -66,8 +66,6 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@ -157,8 +155,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -190,21 +188,21 @@ go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucg
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
@ -231,5 +229,5 @@ gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDa
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

View file

@ -153,6 +153,10 @@ type NameAndIDDBEntity interface {
GetName() string
}
func Ptr[T any](v T) *T {
return &v
}
func EqualDBEntityByName[T NameAndIDDBEntity](t *testing.T, expected, actual []T) {
require.Equal(t, len(expected), len(actual))

17
locking/interface.go Normal file
View file

@ -0,0 +1,17 @@
package locking
import "time"
type Locker interface {
TryLock(key, identifier string) bool
Lock(key, identifier string)
LockedBy(key string) (string, bool)
Unlock(key string, remove bool)
Delete(key string)
}
type InstanceDeleteBackoff interface {
ShouldProcess(key string) (bool, time.Time)
Delete(key string)
RecordFailure(key string)
}

View file

@ -1,40 +1,15 @@
package pool
package locking
import (
"context"
"sync"
"time"
"github.com/cloudbase/garm/runner/common"
)
const (
maxBackoffSeconds float64 = 1200 // 20 minutes
)
type keyMutex struct {
muxes sync.Map
}
func (k *keyMutex) TryLock(key string) bool {
mux, _ := k.muxes.LoadOrStore(key, &sync.Mutex{})
keyMux := mux.(*sync.Mutex)
return keyMux.TryLock()
}
func (k *keyMutex) Unlock(key string, remove bool) {
mux, ok := k.muxes.Load(key)
if !ok {
return
}
keyMux := mux.(*sync.Mutex)
if remove {
k.Delete(key)
}
keyMux.Unlock()
}
func (k *keyMutex) Delete(key string) {
k.muxes.Delete(key)
func NewInstanceDeleteBackoff(_ context.Context) (InstanceDeleteBackoff, error) {
return &instanceDeleteBackoff{}, nil
}
type instanceBackOff struct {
@ -63,7 +38,7 @@ func (i *instanceDeleteBackoff) ShouldProcess(key string) (bool, time.Time) {
now := time.Now().UTC()
deadline := ib.lastRecordedFailureTime.Add(time.Duration(ib.backoffSeconds) * time.Second)
return deadline.After(now), deadline
return now.After(deadline), deadline
}
func (i *instanceDeleteBackoff) Delete(key string) {

View file

@ -0,0 +1,75 @@
package locking
import (
"testing"
"time"
"github.com/stretchr/testify/suite"
)
type LockerBackoffTestSuite struct {
suite.Suite
locker *instanceDeleteBackoff
}
func (l *LockerBackoffTestSuite) SetupTest() {
l.locker = &instanceDeleteBackoff{}
}
func (l *LockerBackoffTestSuite) TearDownTest() {
l.locker = nil
}
func (l *LockerBackoffTestSuite) TestShouldProcess() {
shouldProcess, deadline := l.locker.ShouldProcess("test")
l.Require().True(shouldProcess)
l.Require().Equal(time.Time{}, deadline)
l.locker.muxes.Store("test", &instanceBackOff{
backoffSeconds: 0,
lastRecordedFailureTime: time.Time{},
})
shouldProcess, deadline = l.locker.ShouldProcess("test")
l.Require().True(shouldProcess)
l.Require().Equal(time.Time{}, deadline)
l.locker.muxes.Store("test", &instanceBackOff{
backoffSeconds: 100,
lastRecordedFailureTime: time.Now().UTC(),
})
shouldProcess, deadline = l.locker.ShouldProcess("test")
l.Require().False(shouldProcess)
l.Require().NotEqual(time.Time{}, deadline)
}
func (l *LockerBackoffTestSuite) TestRecordFailure() {
l.locker.RecordFailure("test")
mux, ok := l.locker.muxes.Load("test")
l.Require().True(ok)
ib := mux.(*instanceBackOff)
l.Require().NotNil(ib)
l.Require().NotEqual(time.Time{}, ib.lastRecordedFailureTime)
l.Require().Equal(float64(5), ib.backoffSeconds)
l.locker.RecordFailure("test")
mux, ok = l.locker.muxes.Load("test")
l.Require().True(ok)
ib = mux.(*instanceBackOff)
l.Require().NotNil(ib)
l.Require().NotEqual(time.Time{}, ib.lastRecordedFailureTime)
l.Require().Equal(7.5, ib.backoffSeconds)
l.locker.Delete("test")
mux, ok = l.locker.muxes.Load("test")
l.Require().False(ok)
l.Require().Nil(mux)
}
func TestBackoffTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(LockerBackoffTestSuite))
}

78
locking/local_locker.go Normal file
View file

@ -0,0 +1,78 @@
package locking
import (
"context"
"sync"
dbCommon "github.com/cloudbase/garm/database/common"
)
const (
maxBackoffSeconds float64 = 1200 // 20 minutes
)
func NewLocalLocker(_ context.Context, _ dbCommon.Store) (Locker, error) {
return &keyMutex{}, nil
}
type keyMutex struct {
muxes sync.Map
}
type lockWithIdent struct {
mux sync.Mutex
ident string
}
var _ Locker = &keyMutex{}
func (k *keyMutex) TryLock(key, identifier string) bool {
mux, _ := k.muxes.LoadOrStore(key, &lockWithIdent{
mux: sync.Mutex{},
})
keyMux := mux.(*lockWithIdent)
locked := keyMux.mux.TryLock()
if locked {
keyMux.ident = identifier
}
return locked
}
func (k *keyMutex) Lock(key, identifier string) {
mux, _ := k.muxes.LoadOrStore(key, &lockWithIdent{
mux: sync.Mutex{},
})
keyMux := mux.(*lockWithIdent)
keyMux.ident = identifier
keyMux.mux.Lock()
}
func (k *keyMutex) Unlock(key string, remove bool) {
mux, ok := k.muxes.Load(key)
if !ok {
return
}
keyMux := mux.(*lockWithIdent)
if remove {
k.Delete(key)
}
keyMux.ident = ""
keyMux.mux.Unlock()
}
func (k *keyMutex) Delete(key string) {
k.muxes.Delete(key)
}
func (k *keyMutex) LockedBy(key string) (string, bool) {
mux, ok := k.muxes.Load(key)
if !ok {
return "", false
}
keyMux := mux.(*lockWithIdent)
if keyMux.ident == "" {
return "", false
}
return keyMux.ident, true
}

View file

@ -0,0 +1,228 @@
package locking
import (
"testing"
"github.com/stretchr/testify/suite"
)
type LockerTestSuite struct {
suite.Suite
mux *keyMutex
}
func (l *LockerTestSuite) SetupTest() {
l.mux = &keyMutex{}
err := RegisterLocker(l.mux)
l.Require().NoError(err, "should register the locker")
}
func (l *LockerTestSuite) TearDownTest() {
l.mux = nil
locker = nil
}
func (l *LockerTestSuite) TestLocalLockerLockUnlock() {
l.mux.Lock("test", "test-identifier")
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
l.mux.Unlock("test", true)
mux, ok = l.mux.muxes.Load("test")
l.Require().False(ok)
l.Require().Nil(mux)
l.mux.Unlock("test", false)
}
func (l *LockerTestSuite) TestLocalLockerTryLock() {
locked := l.mux.TryLock("test", "test-identifier")
l.Require().True(locked)
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
locked = l.mux.TryLock("test", "another-identifier2")
l.Require().False(locked)
mux, ok = l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux = mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
l.mux.Unlock("test", true)
locked = l.mux.TryLock("test", "another-identifier2")
l.Require().True(locked)
mux, ok = l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux = mux.(*lockWithIdent)
l.Require().Equal("another-identifier2", keyMux.ident)
l.mux.Unlock("test", true)
}
func (l *LockerTestSuite) TestLocalLockertLockedBy() {
l.mux.Lock("test", "test-identifier")
identifier, ok := l.mux.LockedBy("test")
l.Require().True(ok)
l.Require().Equal("test-identifier", identifier)
l.mux.Unlock("test", true)
identifier, ok = l.mux.LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
l.mux.Lock("test", "test-identifier")
identifier, ok = l.mux.LockedBy("test")
l.Require().True(ok)
l.Require().Equal("test-identifier", identifier)
l.mux.Unlock("test", false)
identifier, ok = l.mux.LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
}
func (l *LockerTestSuite) TestLockerPanicsIfNotInitialized() {
locker = nil
l.Require().Panics(
func() {
Lock("test", "test-identifier")
},
"Lock should panic if locker is not initialized",
)
l.Require().Panics(
func() {
TryLock("test", "test-identifier")
},
"TryLock should panic if locker is not initialized",
)
l.Require().Panics(
func() {
Unlock("test", false)
},
"Unlock should panic if locker is not initialized",
)
l.Require().Panics(
func() {
Delete("test")
},
"Delete should panic if locker is not initialized",
)
l.Require().Panics(
func() {
LockedBy("test")
},
"LockedBy should panic if locker is not initialized",
)
}
func (l *LockerTestSuite) TestLockerAlreadyRegistered() {
err := RegisterLocker(l.mux)
l.Require().Error(err, "should not be able to register the same locker again")
l.Require().Equal("locker already registered", err.Error())
}
func (l *LockerTestSuite) TestLockerDelete() {
Lock("test", "test-identifier")
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
Delete("test")
mux, ok = l.mux.muxes.Load("test")
l.Require().False(ok)
l.Require().Nil(mux)
identifier, ok := l.mux.LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
}
func (l *LockerTestSuite) TestLockUnlock() {
Lock("test", "test-identifier")
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
Unlock("test", true)
mux, ok = l.mux.muxes.Load("test")
l.Require().False(ok)
l.Require().Nil(mux)
identifier, ok := l.mux.LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
}
func (l *LockerTestSuite) TestLockUnlockWithoutRemove() {
Lock("test", "test-identifier")
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
Unlock("test", false)
mux, ok = l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux = mux.(*lockWithIdent)
l.Require().Equal("", keyMux.ident)
identifier, ok := l.mux.LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
}
func (l *LockerTestSuite) TestTryLock() {
locked := TryLock("test", "test-identifier")
l.Require().True(locked)
mux, ok := l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux := mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
locked = TryLock("test", "another-identifier2")
l.Require().False(locked)
mux, ok = l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux = mux.(*lockWithIdent)
l.Require().Equal("test-identifier", keyMux.ident)
Unlock("test", true)
locked = TryLock("test", "another-identifier2")
l.Require().True(locked)
mux, ok = l.mux.muxes.Load("test")
l.Require().True(ok)
keyMux = mux.(*lockWithIdent)
l.Require().Equal("another-identifier2", keyMux.ident)
Unlock("test", true)
}
func (l *LockerTestSuite) TestLockedBy() {
Lock("test", "test-identifier")
identifier, ok := LockedBy("test")
l.Require().True(ok)
l.Require().Equal("test-identifier", identifier)
Unlock("test", true)
identifier, ok = LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
Lock("test", "test-identifier2")
identifier, ok = LockedBy("test")
l.Require().True(ok)
l.Require().Equal("test-identifier2", identifier)
Unlock("test", false)
identifier, ok = LockedBy("test")
l.Require().False(ok)
l.Require().Equal("", identifier)
}
func TestLockerTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(LockerTestSuite))
}

76
locking/locking.go Normal file
View file

@ -0,0 +1,76 @@
package locking
import (
"fmt"
"log/slog"
"runtime"
"sync"
)
var locker Locker
var lockerMux = sync.Mutex{}
func TryLock(key, identifier string) (ok bool) {
if locker == nil {
panic("no locker is registered")
}
_, filename, line, _ := runtime.Caller(1)
slog.Debug("attempting to try lock", "key", key, "identifier", identifier, "caller", fmt.Sprintf("%s:%d", filename, line))
defer slog.Debug("try lock returned", "key", key, "identifier", identifier, "locked", ok, "caller", fmt.Sprintf("%s:%d", filename, line))
ok = locker.TryLock(key, identifier)
return ok
}
func Lock(key, identifier string) {
if locker == nil {
panic("no locker is registered")
}
_, filename, line, _ := runtime.Caller(1)
slog.Debug("attempting to lock", "key", key, "identifier", identifier, "caller", fmt.Sprintf("%s:%d", filename, line))
defer slog.Debug("lock acquired", "key", key, "identifier", identifier, "caller", fmt.Sprintf("%s:%d", filename, line))
locker.Lock(key, identifier)
}
func Unlock(key string, remove bool) {
if locker == nil {
panic("no locker is registered")
}
_, filename, line, _ := runtime.Caller(1)
slog.Debug("attempting to unlock", "key", key, "remove", remove, "caller", fmt.Sprintf("%s:%d", filename, line))
defer slog.Debug("unlock completed", "key", key, "remove", remove, "caller", fmt.Sprintf("%s:%d", filename, line))
locker.Unlock(key, remove)
}
func LockedBy(key string) (string, bool) {
if locker == nil {
panic("no locker is registered")
}
return locker.LockedBy(key)
}
func Delete(key string) {
if locker == nil {
panic("no locker is registered")
}
locker.Delete(key)
}
func RegisterLocker(lock Locker) error {
lockerMux.Lock()
defer lockerMux.Unlock()
if locker != nil {
return fmt.Errorf("locker already registered")
}
locker = lock
return nil
}

View file

@ -14,7 +14,16 @@
package params
import "time"
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"time"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
type Event string
@ -208,3 +217,344 @@ type WorkflowJob struct {
SiteAdmin bool `json:"site_admin"`
} `json:"sender"`
}
type RunnerSetting struct {
Ephemeral bool `json:"ephemeral,omitempty"`
IsElastic bool `json:"isElastic,omitempty"`
DisableUpdate bool `json:"disableUpdate,omitempty"`
}
type Label struct {
Type string `json:"type"`
Name string `json:"name"`
}
type RunnerScaleSetStatistic struct {
TotalAvailableJobs int `json:"totalAvailableJobs"`
TotalAcquiredJobs int `json:"totalAcquiredJobs"`
TotalAssignedJobs int `json:"totalAssignedJobs"`
TotalRunningJobs int `json:"totalRunningJobs"`
TotalRegisteredRunners int `json:"totalRegisteredRunners"`
TotalBusyRunners int `json:"totalBusyRunners"`
TotalIdleRunners int `json:"totalIdleRunners"`
}
type RunnerScaleSet struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
RunnerGroupID int64 `json:"runnerGroupId,omitempty"`
RunnerGroupName string `json:"runnerGroupName,omitempty"`
Labels []Label `json:"labels,omitempty"`
RunnerSetting RunnerSetting `json:"RunnerSetting,omitempty"`
CreatedOn time.Time `json:"createdOn,omitempty"`
RunnerJitConfigURL string `json:"runnerJitConfigUrl,omitempty"`
GetAcquirableJobsURL string `json:"getAcquirableJobsUrl,omitempty"`
AcquireJobsURL string `json:"acquireJobsUrl,omitempty"`
Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"`
Status interface{} `json:"status,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
type RunnerScaleSetsResponse struct {
Count int `json:"count"`
RunnerScaleSets []RunnerScaleSet `json:"value"`
}
type ActionsServiceAdminInfoResponse struct {
URL string `json:"url,omitempty"`
Token string `json:"token,omitempty"`
}
func (a ActionsServiceAdminInfoResponse) GetURL() (*url.URL, error) {
if a.URL == "" {
return nil, fmt.Errorf("no url specified")
}
u, err := url.ParseRequestURI(a.URL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
return u, nil
}
func (a ActionsServiceAdminInfoResponse) getJWT() (*jwt.Token, error) {
// We're parsing a token we got from the GitHub API. We can't verify its signature.
// We do need the expiration date however, or other info.
token, _, err := jwt.NewParser().ParseUnverified(a.Token, &jwt.RegisteredClaims{})
if err != nil {
return nil, fmt.Errorf("failed to parse jwt token: %w", err)
}
return token, nil
}
func (a ActionsServiceAdminInfoResponse) ExiresAt() (time.Time, error) {
jwt, err := a.getJWT()
if err != nil {
return time.Time{}, fmt.Errorf("failed to decode jwt token: %w", err)
}
expiration, err := jwt.Claims.GetExpirationTime()
if err != nil {
return time.Time{}, fmt.Errorf("failed to get expiration time: %w", err)
}
return expiration.Time, nil
}
func (a ActionsServiceAdminInfoResponse) IsExpired() bool {
if exp, err := a.ExiresAt(); err == nil {
return time.Now().UTC().After(exp)
}
return true
}
func (a ActionsServiceAdminInfoResponse) TimeRemaining() (time.Duration, error) {
exp, err := a.ExiresAt()
if err != nil {
return 0, fmt.Errorf("failed to get expiration: %w", err)
}
now := time.Now().UTC()
return exp.Sub(now), nil
}
func (a ActionsServiceAdminInfoResponse) ExpiresIn(t time.Duration) bool {
remaining, err := a.TimeRemaining()
if err != nil {
return true
}
return remaining <= t
}
type ActionsServiceAdminInfoRequest struct {
URL string `json:"url,omitempty"`
RunnerEvent string `json:"runner_event,omitempty"`
}
type RunnerScaleSetSession struct {
SessionID *uuid.UUID `json:"sessionId,omitempty"`
OwnerName string `json:"ownerName,omitempty"`
RunnerScaleSet *RunnerScaleSet `json:"runnerScaleSet,omitempty"`
MessageQueueURL string `json:"messageQueueUrl,omitempty"`
MessageQueueAccessToken string `json:"messageQueueAccessToken,omitempty"`
Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"`
}
func (a RunnerScaleSetSession) GetURL() (*url.URL, error) {
if a.MessageQueueURL == "" {
return nil, fmt.Errorf("no url specified")
}
u, err := url.ParseRequestURI(a.MessageQueueURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
return u, nil
}
func (a RunnerScaleSetSession) getJWT() (*jwt.Token, error) {
// We're parsing a token we got from the GitHub API. We can't verify its signature.
// We do need the expiration date however, or other info.
token, _, err := jwt.NewParser().ParseUnverified(a.MessageQueueAccessToken, &jwt.RegisteredClaims{})
if err != nil {
return nil, fmt.Errorf("failed to parse jwt token: %w", err)
}
return token, nil
}
func (a RunnerScaleSetSession) ExiresAt() (time.Time, error) {
jwt, err := a.getJWT()
if err != nil {
return time.Time{}, fmt.Errorf("failed to decode jwt token: %w", err)
}
expiration, err := jwt.Claims.GetExpirationTime()
if err != nil {
return time.Time{}, fmt.Errorf("failed to get expiration time: %w", err)
}
return expiration.Time, nil
}
func (a RunnerScaleSetSession) IsExpired() bool {
if exp, err := a.ExiresAt(); err == nil {
return time.Now().UTC().After(exp)
}
return true
}
func (a RunnerScaleSetSession) TimeRemaining() (time.Duration, error) {
exp, err := a.ExiresAt()
if err != nil {
return 0, fmt.Errorf("failed to get expiration: %w", err)
}
now := time.Now().UTC()
return exp.Sub(now), nil
}
func (a RunnerScaleSetSession) ExpiresIn(t time.Duration) bool {
remaining, err := a.TimeRemaining()
if err != nil {
return true
}
return remaining <= t
}
type RunnerScaleSetMessage struct {
MessageID int64 `json:"messageId"`
MessageType string `json:"messageType"`
Body string `json:"body"`
Statistics *RunnerScaleSetStatistic `json:"statistics"`
}
func (r RunnerScaleSetMessage) IsNil() bool {
return r.MessageID == 0 && r.MessageType == "" && r.Body == "" && r.Statistics == nil
}
func (r RunnerScaleSetMessage) GetJobsFromBody() ([]ScaleSetJobMessage, error) {
var body []ScaleSetJobMessage
if r.Body == "" {
return nil, fmt.Errorf("no body specified")
}
if err := json.Unmarshal([]byte(r.Body), &body); err != nil {
return nil, fmt.Errorf("failed to unmarshal body: %w", err)
}
return body, nil
}
type RunnerReference struct {
ID int64 `json:"id"`
Name string `json:"name"`
OS string `json:"os"`
RunnerScaleSetID int `json:"runnerScaleSetId"`
CreatedOn interface{} `json:"createdOn"`
RunnerGroupID uint64 `json:"runnerGroupId"`
RunnerGroupName string `json:"runnerGroupName"`
Version string `json:"version"`
Enabled bool `json:"enabled"`
Ephemeral bool `json:"ephemeral"`
Status interface{} `json:"status"`
DisableUpdate bool `json:"disableUpdate"`
ProvisioningState string `json:"provisioningState"`
Busy bool `json:"busy"`
Labels []Label `json:"labels,omitempty"`
}
func (r RunnerReference) GetStatus() RunnerStatus {
status, ok := r.Status.(string)
if !ok {
return RunnerUnknown
}
runnerStatus := RunnerStatus(status)
if !runnerStatus.IsValid() {
return RunnerUnknown
}
if runnerStatus == RunnerOnline {
if r.Busy {
return RunnerActive
}
return RunnerIdle
}
return runnerStatus
}
type RunnerScaleSetJitRunnerConfig struct {
Runner *RunnerReference `json:"runner"`
EncodedJITConfig string `json:"encodedJITConfig"`
}
func (r RunnerScaleSetJitRunnerConfig) DecodedJITConfig() (map[string]string, error) {
if r.EncodedJITConfig == "" {
return nil, fmt.Errorf("no encoded JIT config specified")
}
decoded, err := base64.StdEncoding.DecodeString(r.EncodedJITConfig)
if err != nil {
return nil, fmt.Errorf("failed to decode JIT config: %w", err)
}
jitConfig := make(map[string]string)
if err := json.Unmarshal(decoded, &jitConfig); err != nil {
return nil, fmt.Errorf("failed to unmarshal JIT config: %w", err)
}
return jitConfig, nil
}
type RunnerReferenceList struct {
Count int `json:"count"`
RunnerReferences []RunnerReference `json:"value"`
}
type AcquirableJobList struct {
Count int `json:"count"`
Jobs []AcquirableJob `json:"value"`
}
type AcquirableJob struct {
AcquireJobURL string `json:"acquireJobUrl"`
MessageType string `json:"messageType"`
RunnerRequestID int64 `json:"run0ne00rRequestId"`
RepositoryName string `json:"repositoryName"`
OwnerName string `json:"ownerName"`
JobWorkflowRef string `json:"jobWorkflowRef"`
EventName string `json:"eventName"`
RequestLabels []string `json:"requestLabels"`
}
type RunnerGroup struct {
ID int64 `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
IsDefault bool `json:"isDefaultGroup"`
}
type RunnerGroupList struct {
Count int `json:"count"`
RunnerGroups []RunnerGroup `json:"value"`
}
type ScaleSetJobMessage struct {
MessageType string `json:"messageType,omitempty"`
RunnerRequestID int64 `json:"runnerRequestId,omitempty"`
RepositoryName string `json:"repositoryName,omitempty"`
OwnerName string `json:"ownerName,omitempty"`
JobWorkflowRef string `json:"jobWorkflowRef,omitempty"`
JobDisplayName string `json:"jobDisplayName,omitempty"`
WorkflowRunID int64 `json:"workflowRunId,omitempty"`
EventName string `json:"eventName,omitempty"`
RequestLabels []string `json:"requestLabels,omitempty"`
QueueTime time.Time `json:"queueTime,omitempty"`
ScaleSetAssignTime time.Time `json:"scaleSetAssignTime,omitempty"`
RunnerAssignTime time.Time `json:"runnerAssignTime,omitempty"`
FinishTime time.Time `json:"finishTime,omitempty"`
Result string `json:"result,omitempty"`
RunnerID int64 `json:"runnerId,omitempty"`
RunnerName string `json:"runnerName,omitempty"`
AcquireJobURL string `json:"acquireJobUrl,omitempty"`
}
func (s ScaleSetJobMessage) MessageTypeToStatus() JobStatus {
switch s.MessageType {
case MessageTypeJobAssigned:
return JobStatusQueued
case MessageTypeJobStarted:
return JobStatusInProgress
case MessageTypeJobCompleted:
return JobStatusCompleted
default:
return JobStatusQueued
}
}
func (s ScaleSetJobMessage) ToJob() Job {
return Job{
ID: s.RunnerRequestID,
Action: s.EventName,
RunID: s.WorkflowRunID,
Status: string(s.MessageTypeToStatus()),
Conclusion: s.Result,
CompletedAt: s.FinishTime,
StartedAt: s.RunnerAssignTime,
Name: s.JobDisplayName,
GithubRunnerID: s.RunnerID,
RunnerName: s.RunnerName,
RepositoryName: s.RepositoryName,
RepositoryOwner: s.OwnerName,
Labels: s.RequestLabels,
}
}

7
params/interfaces.go Normal file
View file

@ -0,0 +1,7 @@
package params
// EntityGetter is implemented by all github entities (repositories, organizations and enterprises).
// It defines the GetEntity() function which returns a github entity.
type EntityGetter interface {
GetEntity() (GithubEntity, error)
}

View file

@ -22,11 +22,12 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"math"
"net/http"
"time"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v57/github"
"github.com/google/go-github/v71/github"
"github.com/google/uuid"
"golang.org/x/oauth2"
@ -44,8 +45,22 @@ type (
WebhookEndpointType string
GithubAuthType string
PoolBalancerType string
ScaleSetState string
ScaleSetMessageType string
)
func (s RunnerStatus) IsValid() bool {
switch s {
case RunnerIdle, RunnerPending, RunnerTerminated,
RunnerInstalling, RunnerFailed,
RunnerActive, RunnerOffline,
RunnerUnknown, RunnerOnline:
return true
}
return false
}
const (
// PoolBalancerTypeRoundRobin will try to cycle through the pools of an entity
// in a round robin fashion. For example, if a repository has multiple pools that
@ -114,6 +129,9 @@ const (
RunnerInstalling RunnerStatus = "installing"
RunnerFailed RunnerStatus = "failed"
RunnerActive RunnerStatus = "active"
RunnerOffline RunnerStatus = "offline"
RunnerOnline RunnerStatus = "online"
RunnerUnknown RunnerStatus = "unknown"
)
const (
@ -127,6 +145,25 @@ func (e GithubEntityType) String() string {
return string(e)
}
const (
ScaleSetPendingCreate ScaleSetState = "pending_create"
ScaleSetCreated ScaleSetState = "created"
ScaleSetError ScaleSetState = "error"
ScaleSetPendingDelete ScaleSetState = "pending_delete"
ScaleSetPendingForceDelete ScaleSetState = "pending_force_delete"
)
const (
MessageTypeRunnerScaleSetJobMessages ScaleSetMessageType = "RunnerScaleSetJobMessages"
)
const (
MessageTypeJobAssigned = "JobAssigned"
MessageTypeJobCompleted = "JobCompleted"
MessageTypeJobStarted = "JobStarted"
MessageTypeJobAvailable = "JobAvailable"
)
type StatusMessage struct {
CreatedAt time.Time `json:"created_at,omitempty"`
Message string `json:"message,omitempty"`
@ -143,6 +180,10 @@ type Instance struct {
// instance in the provider.
ProviderID string `json:"provider_id,omitempty"`
// ProviderName is the name of the IaaS where the instance was
// created.
ProviderName string `json:"provider_name"`
// AgentID is the github runner agent ID.
AgentID int64 `json:"agent_id,omitempty"`
@ -178,6 +219,9 @@ type Instance struct {
// PoolID is the ID of the garm pool to which a runner belongs.
PoolID string `json:"pool_id,omitempty"`
// ScaleSetID is the ID of the scale set to which a runner belongs.
ScaleSetID uint `json:"scale_set_id,omitempty"`
// ProviderFault holds any error messages captured from the IaaS provider that is
// responsible for managing the lifecycle of the runner.
ProviderFault []byte `json:"provider_fault,omitempty"`
@ -326,7 +370,22 @@ type Pool struct {
Priority uint `json:"priority,omitempty"`
}
func (p Pool) GithubEntity() (GithubEntity, error) {
func (p Pool) MinIdleRunnersAsInt() int {
if p.MinIdleRunners > math.MaxInt {
return math.MaxInt
}
return int(p.MinIdleRunners)
}
func (p Pool) MaxRunnersAsInt() int {
if p.MaxRunners > math.MaxInt {
return math.MaxInt
}
return int(p.MaxRunners)
}
func (p Pool) GetEntity() (GithubEntity, error) {
switch p.PoolType() {
case GithubEntityTypeRepository:
return GithubEntity{
@ -387,6 +446,100 @@ func (p *Pool) HasRequiredLabels(set []string) bool {
// used by swagger client generated code
type Pools []Pool
type ScaleSet struct {
RunnerPrefix
ID uint `json:"id,omitempty"`
ScaleSetID int `json:"scale_set_id,omitempty"`
Name string `json:"name,omitempty"`
DisableUpdate bool `json:"disable_update"`
State ScaleSetState `json:"state"`
ExtendedState string `json:"extended_state,omitempty"`
ProviderName string `json:"provider_name,omitempty"`
MaxRunners uint `json:"max_runners,omitempty"`
MinIdleRunners uint `json:"min_idle_runners,omitempty"`
Image string `json:"image,omitempty"`
Flavor string `json:"flavor,omitempty"`
OSType commonParams.OSType `json:"os_type,omitempty"`
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
Enabled bool `json:"enabled,omitempty"`
Instances []Instance `json:"instances,omitempty"`
DesiredRunnerCount int `json:"desired_runner_count,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
// ExtraSpecs is an opaque raw json that gets sent to the provider
// as part of the bootstrap params for instances. It can contain
// any kind of data needed by providers. The contents of this field means
// nothing to garm itself. We don't act on the information in this field at
// all. We only validate that it's a proper json.
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
// GithubRunnerGroup is the github runner group in which the runners will be added.
// The runner group must be created by someone with access to the enterprise.
GitHubRunnerGroup string `json:"github-runner-group,omitempty"`
StatusMessages []StatusMessage `json:"status_messages"`
RepoID string `json:"repo_id,omitempty"`
RepoName string `json:"repo_name,omitempty"`
OrgID string `json:"org_id,omitempty"`
OrgName string `json:"org_name,omitempty"`
EnterpriseID string `json:"enterprise_id,omitempty"`
EnterpriseName string `json:"enterprise_name,omitempty"`
LastMessageID int64 `json:"-"`
}
func (p ScaleSet) GetEntity() (GithubEntity, error) {
switch p.ScaleSetType() {
case GithubEntityTypeRepository:
return GithubEntity{
ID: p.RepoID,
EntityType: GithubEntityTypeRepository,
}, nil
case GithubEntityTypeOrganization:
return GithubEntity{
ID: p.OrgID,
EntityType: GithubEntityTypeOrganization,
}, nil
case GithubEntityTypeEnterprise:
return GithubEntity{
ID: p.EnterpriseID,
EntityType: GithubEntityTypeEnterprise,
}, nil
}
return GithubEntity{}, fmt.Errorf("pool has no associated entity")
}
func (p *ScaleSet) ScaleSetType() GithubEntityType {
switch {
case p.RepoID != "":
return GithubEntityTypeRepository
case p.OrgID != "":
return GithubEntityTypeOrganization
case p.EnterpriseID != "":
return GithubEntityTypeEnterprise
}
return ""
}
func (p ScaleSet) GetID() uint {
return p.ID
}
func (p *ScaleSet) RunnerTimeout() uint {
if p.RunnerBootstrapTimeout == 0 {
return appdefaults.DefaultRunnerBootstrapTimeout
}
return p.RunnerBootstrapTimeout
}
// used by swagger client generated code
type ScaleSets []ScaleSet
type Repository struct {
ID string `json:"id,omitempty"`
Owner string `json:"owner,omitempty"`
@ -611,6 +764,14 @@ type ControllerInfo struct {
Version string `json:"version,omitempty"`
}
func (c *ControllerInfo) JobBackoff() time.Duration {
if math.MaxInt64 > c.MinimumJobAgeBackoff {
return time.Duration(math.MaxInt64)
}
return time.Duration(int64(c.MinimumJobAgeBackoff))
}
type GithubCredentials struct {
ID uint `json:"id,omitempty"`
Name string `json:"name,omitempty"`
@ -837,6 +998,18 @@ type GithubEntity struct {
WebhookSecret string `json:"-"`
}
func (g *GithubEntity) GithubURL() string {
switch g.EntityType {
case GithubEntityTypeRepository:
return fmt.Sprintf("%s/%s/%s", g.Credentials.BaseURL, g.Owner, g.Name)
case GithubEntityTypeOrganization:
return fmt.Sprintf("%s/%s", g.Credentials.BaseURL, g.Owner)
case GithubEntityTypeEnterprise:
return fmt.Sprintf("%s/enterprises/%s", g.Credentials.BaseURL, g.Owner)
}
return ""
}
func (g GithubEntity) GetPoolBalancerType() PoolBalancerType {
if g.PoolBalancerType == "" {
return PoolBalancerTypeRoundRobin
@ -866,6 +1039,17 @@ func (g GithubEntity) String() string {
return ""
}
func (g GithubEntity) GetIDAsUUID() (uuid.UUID, error) {
if g.ID == "" {
return uuid.Nil, nil
}
id, err := uuid.Parse(g.ID)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to parse entity ID: %w", err)
}
return id, nil
}
// used by swagger client generated code
type GithubEndpoints []GithubEndpoint

View file

@ -533,3 +533,76 @@ func (u UpdateControllerParams) Validate() error {
return nil
}
type CreateScaleSetParams struct {
RunnerPrefix
Name string `json:"name"`
DisableUpdate bool `json:"disable_update"`
ScaleSetID int `json:"scale_set_id"`
ProviderName string `json:"provider_name,omitempty"`
MaxRunners uint `json:"max_runners,omitempty"`
MinIdleRunners uint `json:"min_idle_runners,omitempty"`
Image string `json:"image,omitempty"`
Flavor string `json:"flavor,omitempty"`
OSType commonParams.OSType `json:"os_type,omitempty"`
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
Tags []string `json:"tags,omitempty"`
Enabled bool `json:"enabled,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
// GithubRunnerGroup is the github runner group in which the runners of this
// pool will be added to.
// The runner group must be created by someone with access to the enterprise.
GitHubRunnerGroup string `json:"github-runner-group,omitempty"`
}
func (s *CreateScaleSetParams) Validate() error {
if s.ProviderName == "" {
return fmt.Errorf("missing provider")
}
if s.MinIdleRunners > s.MaxRunners {
return fmt.Errorf("min_idle_runners cannot be larger than max_runners")
}
if s.MaxRunners == 0 {
return fmt.Errorf("max_runners cannot be 0")
}
if s.Flavor == "" {
return fmt.Errorf("missing flavor")
}
if s.Image == "" {
return fmt.Errorf("missing image")
}
if s.Name == "" {
return fmt.Errorf("missing scale set name")
}
return nil
}
type UpdateScaleSetParams struct {
RunnerPrefix
Name string `json:"name,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
MaxRunners *uint `json:"max_runners,omitempty"`
MinIdleRunners *uint `json:"min_idle_runners,omitempty"`
RunnerBootstrapTimeout *uint `json:"runner_bootstrap_timeout,omitempty"`
Image string `json:"image,omitempty"`
Flavor string `json:"flavor,omitempty"`
OSType commonParams.OSType `json:"os_type,omitempty"`
OSArch commonParams.OSArch `json:"os_arch,omitempty"`
ExtraSpecs json.RawMessage `json:"extra_specs,omitempty"`
// GithubRunnerGroup is the github runner group in which the runners of this
// pool will be added to.
// The runner group must be created by someone with access to the enterprise.
GitHubRunnerGroup *string `json:"runner_group,omitempty"`
State *ScaleSetState `json:"state"`
ExtendedState *string `json:"extended_state"`
}

View file

@ -1,14 +1,16 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
github "github.com/google/go-github/v71/github"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
url "net/url"
)
// GithubClient is an autogenerated mock type for the GithubClient type
@ -115,6 +117,24 @@ func (_m *GithubClient) DeleteEntityHook(ctx context.Context, id int64) (*github
return r0, r1
}
// GetEntity provides a mock function with no fields
func (_m *GithubClient) GetEntity() params.GithubEntity {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetEntity")
}
var r0 params.GithubEntity
if rf, ok := ret.Get(0).(func() params.GithubEntity); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(params.GithubEntity)
}
return r0
}
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
@ -223,6 +243,26 @@ func (_m *GithubClient) GetWorkflowJobByID(ctx context.Context, owner string, re
return r0, r1, r2
}
// GithubBaseURL provides a mock function with no fields
func (_m *GithubClient) GithubBaseURL() *url.URL {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GithubBaseURL")
}
var r0 *url.URL
if rf, ok := ret.Get(0).(func() *url.URL); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*url.URL)
}
}
return r0
}
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubClient) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -371,33 +411,21 @@ func (_m *GithubClient) PingEntityHook(ctx context.Context, id int64) (*github.R
}
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubClient) RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error) {
func (_m *GithubClient) RemoveEntityRunner(ctx context.Context, runnerID int64) error {
ret := _m.Called(ctx, runnerID)
if len(ret) == 0 {
panic("no return value specified for RemoveEntityRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
r0 = ret.Error(0)
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// NewGithubClient creates a new instance of GithubClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.

View file

@ -5,7 +5,7 @@ package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
github "github.com/google/go-github/v71/github"
mock "github.com/stretchr/testify/mock"
)

View file

@ -1,14 +1,16 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
github "github.com/google/go-github/v71/github"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
url "net/url"
)
// GithubEntityOperations is an autogenerated mock type for the GithubEntityOperations type
@ -115,6 +117,24 @@ func (_m *GithubEntityOperations) DeleteEntityHook(ctx context.Context, id int64
return r0, r1
}
// GetEntity provides a mock function with no fields
func (_m *GithubEntityOperations) GetEntity() params.GithubEntity {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetEntity")
}
var r0 params.GithubEntity
if rf, ok := ret.Get(0).(func() params.GithubEntity); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(params.GithubEntity)
}
return r0
}
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
@ -184,6 +204,26 @@ func (_m *GithubEntityOperations) GetEntityJITConfig(ctx context.Context, instan
return r0, r1, r2
}
// GithubBaseURL provides a mock function with no fields
func (_m *GithubEntityOperations) GithubBaseURL() *url.URL {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GithubBaseURL")
}
var r0 *url.URL
if rf, ok := ret.Get(0).(func() *url.URL); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*url.URL)
}
}
return r0
}
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubEntityOperations) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -332,33 +372,21 @@ func (_m *GithubEntityOperations) PingEntityHook(ctx context.Context, id int64)
}
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubEntityOperations) RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error) {
func (_m *GithubEntityOperations) RemoveEntityRunner(ctx context.Context, runnerID int64) error {
ret := _m.Called(ctx, runnerID)
if len(ret) == 0 {
panic("no return value specified for RemoveEntityRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
r0 = ret.Error(0)
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// NewGithubEntityOperations creates a new instance of GithubEntityOperations. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.

View file

@ -5,7 +5,7 @@ package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
github "github.com/google/go-github/v71/github"
mock "github.com/stretchr/testify/mock"
)

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@ -14,24 +14,6 @@ type PoolManager struct {
mock.Mock
}
// DeleteRunner provides a mock function with given fields: runner, forceRemove, bypassGHUnauthorizedError
func (_m *PoolManager) DeleteRunner(runner params.Instance, forceRemove bool, bypassGHUnauthorizedError bool) error {
ret := _m.Called(runner, forceRemove, bypassGHUnauthorizedError)
if len(ret) == 0 {
panic("no return value specified for DeleteRunner")
}
var r0 error
if rf, ok := ret.Get(0).(func(params.Instance, bool, bool) error); ok {
r0 = rf(runner, forceRemove, bypassGHUnauthorizedError)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetWebhookInfo provides a mock function with given fields: ctx
func (_m *PoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) {
ret := _m.Called(ctx)
@ -60,7 +42,7 @@ func (_m *PoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, err
return r0, r1
}
// GithubRunnerRegistrationToken provides a mock function with given fields:
// GithubRunnerRegistrationToken provides a mock function with no fields
func (_m *PoolManager) GithubRunnerRegistrationToken() (string, error) {
ret := _m.Called()
@ -106,7 +88,7 @@ func (_m *PoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
return r0
}
// ID provides a mock function with given fields:
// ID provides a mock function with no fields
func (_m *PoolManager) ID() string {
ret := _m.Called()
@ -152,7 +134,7 @@ func (_m *PoolManager) InstallWebhook(ctx context.Context, param params.InstallW
return r0, r1
}
// RootCABundle provides a mock function with given fields:
// RootCABundle provides a mock function with no fields
func (_m *PoolManager) RootCABundle() (params.CertificateBundle, error) {
ret := _m.Called()
@ -180,7 +162,12 @@ func (_m *PoolManager) RootCABundle() (params.CertificateBundle, error) {
return r0, r1
}
// Start provides a mock function with given fields:
// SetPoolRunningState provides a mock function with given fields: isRunning, failureReason
func (_m *PoolManager) SetPoolRunningState(isRunning bool, failureReason string) {
_m.Called(isRunning, failureReason)
}
// Start provides a mock function with no fields
func (_m *PoolManager) Start() error {
ret := _m.Called()
@ -198,7 +185,7 @@ func (_m *PoolManager) Start() error {
return r0
}
// Status provides a mock function with given fields:
// Status provides a mock function with no fields
func (_m *PoolManager) Status() params.PoolManagerStatus {
ret := _m.Called()
@ -216,7 +203,7 @@ func (_m *PoolManager) Status() params.PoolManagerStatus {
return r0
}
// Stop provides a mock function with given fields:
// Stop provides a mock function with no fields
func (_m *PoolManager) Stop() error {
ret := _m.Called()
@ -252,7 +239,7 @@ func (_m *PoolManager) UninstallWebhook(ctx context.Context) error {
return r0
}
// Wait provides a mock function with given fields:
// Wait provides a mock function with no fields
func (_m *PoolManager) Wait() error {
ret := _m.Called()
@ -270,7 +257,7 @@ func (_m *PoolManager) Wait() error {
return r0
}
// WebhookSecret provides a mock function with given fields:
// WebhookSecret provides a mock function with no fields
func (_m *PoolManager) WebhookSecret() string {
ret := _m.Called()

View file

@ -1,15 +1,17 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
common "github.com/cloudbase/garm/runner/common"
garm_provider_commonparams "github.com/cloudbase/garm-provider-common/params"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
)
// Provider is an autogenerated mock type for the Provider type
@ -17,7 +19,7 @@ type Provider struct {
mock.Mock
}
// AsParams provides a mock function with given fields:
// AsParams provides a mock function with no fields
func (_m *Provider) AsParams() params.Provider {
ret := _m.Called()
@ -35,9 +37,9 @@ func (_m *Provider) AsParams() params.Provider {
return r0
}
// CreateInstance provides a mock function with given fields: ctx, bootstrapParams
// CreateInstance provides a mock function with given fields: ctx, bootstrapParams, createInstanceParams
func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams garm_provider_commonparams.BootstrapInstance, createInstanceParams common.CreateInstanceParams) (garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, bootstrapParams)
ret := _m.Called(ctx, bootstrapParams, createInstanceParams)
if len(ret) == 0 {
panic("no return value specified for CreateInstance")
@ -45,17 +47,17 @@ func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams garm_pro
var r0 garm_provider_commonparams.ProviderInstance
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, garm_provider_commonparams.BootstrapInstance) (garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, bootstrapParams)
if rf, ok := ret.Get(0).(func(context.Context, garm_provider_commonparams.BootstrapInstance, common.CreateInstanceParams) (garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, bootstrapParams, createInstanceParams)
}
if rf, ok := ret.Get(0).(func(context.Context, garm_provider_commonparams.BootstrapInstance) garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, bootstrapParams)
if rf, ok := ret.Get(0).(func(context.Context, garm_provider_commonparams.BootstrapInstance, common.CreateInstanceParams) garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, bootstrapParams, createInstanceParams)
} else {
r0 = ret.Get(0).(garm_provider_commonparams.ProviderInstance)
}
if rf, ok := ret.Get(1).(func(context.Context, garm_provider_commonparams.BootstrapInstance) error); ok {
r1 = rf(ctx, bootstrapParams)
if rf, ok := ret.Get(1).(func(context.Context, garm_provider_commonparams.BootstrapInstance, common.CreateInstanceParams) error); ok {
r1 = rf(ctx, bootstrapParams, createInstanceParams)
} else {
r1 = ret.Error(1)
}
@ -63,17 +65,17 @@ func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams garm_pro
return r0, r1
}
// DeleteInstance provides a mock function with given fields: ctx, instance
// DeleteInstance provides a mock function with given fields: ctx, instance, deleteInstanceParams
func (_m *Provider) DeleteInstance(ctx context.Context, instance string, deleteInstanceParams common.DeleteInstanceParams) error {
ret := _m.Called(ctx, instance)
ret := _m.Called(ctx, instance, deleteInstanceParams)
if len(ret) == 0 {
panic("no return value specified for DeleteInstance")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instance)
if rf, ok := ret.Get(0).(func(context.Context, string, common.DeleteInstanceParams) error); ok {
r0 = rf(ctx, instance, deleteInstanceParams)
} else {
r0 = ret.Error(0)
}
@ -81,7 +83,7 @@ func (_m *Provider) DeleteInstance(ctx context.Context, instance string, deleteI
return r0
}
// DisableJITConfig provides a mock function with given fields:
// DisableJITConfig provides a mock function with no fields
func (_m *Provider) DisableJITConfig() bool {
ret := _m.Called()
@ -99,9 +101,9 @@ func (_m *Provider) DisableJITConfig() bool {
return r0
}
// GetInstance provides a mock function with given fields: ctx, instance
// GetInstance provides a mock function with given fields: ctx, instance, getInstanceParams
func (_m *Provider) GetInstance(ctx context.Context, instance string, getInstanceParams common.GetInstanceParams) (garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, instance)
ret := _m.Called(ctx, instance, getInstanceParams)
if len(ret) == 0 {
panic("no return value specified for GetInstance")
@ -109,17 +111,17 @@ func (_m *Provider) GetInstance(ctx context.Context, instance string, getInstanc
var r0 garm_provider_commonparams.ProviderInstance
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, instance)
if rf, ok := ret.Get(0).(func(context.Context, string, common.GetInstanceParams) (garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, instance, getInstanceParams)
}
if rf, ok := ret.Get(0).(func(context.Context, string) garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, instance)
if rf, ok := ret.Get(0).(func(context.Context, string, common.GetInstanceParams) garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, instance, getInstanceParams)
} else {
r0 = ret.Get(0).(garm_provider_commonparams.ProviderInstance)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, instance)
if rf, ok := ret.Get(1).(func(context.Context, string, common.GetInstanceParams) error); ok {
r1 = rf(ctx, instance, getInstanceParams)
} else {
r1 = ret.Error(1)
}
@ -127,9 +129,9 @@ func (_m *Provider) GetInstance(ctx context.Context, instance string, getInstanc
return r0, r1
}
// ListInstances provides a mock function with given fields: ctx, poolID
// ListInstances provides a mock function with given fields: ctx, poolID, listInstancesParams
func (_m *Provider) ListInstances(ctx context.Context, poolID string, listInstancesParams common.ListInstancesParams) ([]garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, poolID)
ret := _m.Called(ctx, poolID, listInstancesParams)
if len(ret) == 0 {
panic("no return value specified for ListInstances")
@ -137,19 +139,19 @@ func (_m *Provider) ListInstances(ctx context.Context, poolID string, listInstan
var r0 []garm_provider_commonparams.ProviderInstance
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, poolID)
if rf, ok := ret.Get(0).(func(context.Context, string, common.ListInstancesParams) ([]garm_provider_commonparams.ProviderInstance, error)); ok {
return rf(ctx, poolID, listInstancesParams)
}
if rf, ok := ret.Get(0).(func(context.Context, string) []garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, poolID)
if rf, ok := ret.Get(0).(func(context.Context, string, common.ListInstancesParams) []garm_provider_commonparams.ProviderInstance); ok {
r0 = rf(ctx, poolID, listInstancesParams)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]garm_provider_commonparams.ProviderInstance)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, poolID)
if rf, ok := ret.Get(1).(func(context.Context, string, common.ListInstancesParams) error); ok {
r1 = rf(ctx, poolID, listInstancesParams)
} else {
r1 = ret.Error(1)
}
@ -157,17 +159,17 @@ func (_m *Provider) ListInstances(ctx context.Context, poolID string, listInstan
return r0, r1
}
// RemoveAllInstances provides a mock function with given fields: ctx
func (_m *Provider) RemoveAllInstances(ctx context.Context, removeAllInstances common.RemoveAllInstancesParams) error {
ret := _m.Called(ctx)
// RemoveAllInstances provides a mock function with given fields: ctx, removeAllInstancesParams
func (_m *Provider) RemoveAllInstances(ctx context.Context, removeAllInstancesParams common.RemoveAllInstancesParams) error {
ret := _m.Called(ctx, removeAllInstancesParams)
if len(ret) == 0 {
panic("no return value specified for RemoveAllInstances")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, common.RemoveAllInstancesParams) error); ok {
r0 = rf(ctx, removeAllInstancesParams)
} else {
r0 = ret.Error(0)
}
@ -175,17 +177,17 @@ func (_m *Provider) RemoveAllInstances(ctx context.Context, removeAllInstances c
return r0
}
// Start provides a mock function with given fields: ctx, instance
// Start provides a mock function with given fields: ctx, instance, startParams
func (_m *Provider) Start(ctx context.Context, instance string, startParams common.StartParams) error {
ret := _m.Called(ctx, instance)
ret := _m.Called(ctx, instance, startParams)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instance)
if rf, ok := ret.Get(0).(func(context.Context, string, common.StartParams) error); ok {
r0 = rf(ctx, instance, startParams)
} else {
r0 = ret.Error(0)
}
@ -193,17 +195,17 @@ func (_m *Provider) Start(ctx context.Context, instance string, startParams comm
return r0
}
// Stop provides a mock function with given fields: ctx, instance
// Stop provides a mock function with given fields: ctx, instance, stopParams
func (_m *Provider) Stop(ctx context.Context, instance string, stopParams common.StopParams) error {
ret := _m.Called(ctx, instance)
ret := _m.Called(ctx, instance, stopParams)
if len(ret) == 0 {
panic("no return value specified for Stop")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instance)
if rf, ok := ret.Get(0).(func(context.Context, string, common.StopParams) error); ok {
r0 = rf(ctx, instance, stopParams)
} else {
r0 = ret.Error(0)
}

View file

@ -5,7 +5,7 @@ package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
github "github.com/google/go-github/v71/github"
mock "github.com/stretchr/testify/mock"
)

View file

@ -54,13 +54,6 @@ type PoolManager interface {
// for it and call this function with the WorkflowJob as a parameter.
HandleWorkflowJob(job params.WorkflowJob) error
// DeleteRunner will attempt to remove a runner from the pool. If forceRemove is true, any error
// received from the provider will be ignored and we will proceed to remove the runner from the database.
// An error received while attempting to remove from GitHub (other than 404) will still stop the deletion
// process. This can happen if the runner is already processing a job. At which point, you can simply cancel
// the job in github. Doing so will prompt GARM to reap the runner automatically.
DeleteRunner(runner params.Instance, forceRemove, bypassGHUnauthorizedError bool) error
// InstallWebhook will create a webhook in github for the entity associated with this pool manager.
InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error)
// GetWebhookInfo will return information about the webhook installed in github for the entity associated
@ -74,6 +67,8 @@ type PoolManager interface {
// may use internal or self signed certificates.
RootCABundle() (params.CertificateBundle, error)
SetPoolRunningState(isRunning bool, failureReason string)
// Start will start the pool manager and all associated workers.
Start() error
// Stop will stop the pool manager and all associated workers.

View file

@ -2,8 +2,9 @@ package common
import (
"context"
"net/url"
"github.com/google/go-github/v57/github"
"github.com/google/go-github/v71/github"
"github.com/cloudbase/garm/params"
)
@ -14,11 +15,21 @@ type GithubEntityOperations interface {
CreateEntityHook(ctx context.Context, hook *github.Hook) (ret *github.Hook, err error)
DeleteEntityHook(ctx context.Context, id int64) (ret *github.Response, err error)
PingEntityHook(ctx context.Context, id int64) (ret *github.Response, err error)
ListEntityRunners(ctx context.Context, opts *github.ListOptions) (*github.Runners, *github.Response, error)
ListEntityRunners(ctx context.Context, opts *github.ListRunnersOptions) (*github.Runners, *github.Response, error)
ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)
RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error)
RemoveEntityRunner(ctx context.Context, runnerID int64) error
RateLimit(ctx context.Context) (*github.RateLimits, error)
CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error)
GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error)
// GetEntity returns the GitHub entity for which the github client was instanciated.
GetEntity() params.GithubEntity
// GithubBaseURL returns the base URL for the github or GHES API.
GithubBaseURL() *url.URL
}
type RateLimitClient interface {
RateLimit(ctx context.Context) (*github.RateLimits, error)
}
// GithubClient that describes the minimum list of functions we need to interact with github.

View file

@ -145,6 +145,15 @@ func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) erro
return runnerErrors.NewBadRequestError("enterprise has pools defined (%s)", strings.Join(poolIDs, ", "))
}
scaleSets, err := r.store.ListEntityScaleSets(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching enterprise scale sets")
}
if len(scaleSets) > 0 {
return runnerErrors.NewBadRequestError("enterprise has scale sets defined; delete them first")
}
if err := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); err != nil {
return errors.Wrap(err, "deleting enterprise pool manager")
}

View file

@ -7,7 +7,6 @@ import (
"fmt"
"html/template"
"log/slog"
"strings"
"github.com/pkg/errors"
@ -57,24 +56,52 @@ func (r *Runner) GetRunnerServiceName(ctx context.Context) (string, error) {
ctx, "failed to get instance params")
return "", runnerErrors.ErrUnauthorized
}
var entity params.GithubEntity
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get pool",
"pool_id", instance.PoolID)
return "", errors.Wrap(err, "fetching pool")
switch {
case instance.PoolID != "":
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get pool",
"pool_id", instance.PoolID)
return "", errors.Wrap(err, "fetching pool")
}
entity, err = pool.GetEntity()
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get pool entity",
"pool_id", instance.PoolID)
return "", errors.Wrap(err, "fetching pool entity")
}
case instance.ScaleSetID != 0:
scaleSet, err := r.store.GetScaleSetByID(r.ctx, instance.ScaleSetID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get scale set",
"scale_set_id", instance.ScaleSetID)
return "", errors.Wrap(err, "fetching scale set")
}
entity, err = scaleSet.GetEntity()
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get scale set entity",
"scale_set_id", instance.ScaleSetID)
return "", errors.Wrap(err, "fetching scale set entity")
}
default:
return "", errors.New("instance not associated with a pool or scale set")
}
tpl := "actions.runner.%s.%s"
var serviceName string
switch pool.PoolType() {
switch entity.EntityType {
case params.GithubEntityTypeEnterprise:
serviceName = fmt.Sprintf(tpl, pool.EnterpriseName, instance.Name)
serviceName = fmt.Sprintf(tpl, entity.Owner, instance.Name)
case params.GithubEntityTypeOrganization:
serviceName = fmt.Sprintf(tpl, pool.OrgName, instance.Name)
serviceName = fmt.Sprintf(tpl, entity.Owner, instance.Name)
case params.GithubEntityTypeRepository:
serviceName = fmt.Sprintf(tpl, strings.ReplaceAll(pool.RepoName, "/", "-"), instance.Name)
serviceName = fmt.Sprintf(tpl, fmt.Sprintf("%s-%s", entity.Owner, entity.Name), instance.Name)
}
return serviceName, nil
}

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@ -193,7 +193,7 @@ func (_m *PoolManagerController) GetEnterprisePoolManager(enterprise params.Ente
return r0, r1
}
// GetEnterprisePoolManagers provides a mock function with given fields:
// GetEnterprisePoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetEnterprisePoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
@ -253,7 +253,7 @@ func (_m *PoolManagerController) GetOrgPoolManager(org params.Organization) (com
return r0, r1
}
// GetOrgPoolManagers provides a mock function with given fields:
// GetOrgPoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetOrgPoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
@ -313,7 +313,7 @@ func (_m *PoolManagerController) GetRepoPoolManager(repo params.Repository) (com
return r0, r1
}
// GetRepoPoolManagers provides a mock function with given fields:
// GetRepoPoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetRepoPoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()

View file

@ -159,6 +159,15 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho
return runnerErrors.NewBadRequestError("org has pools defined (%s)", strings.Join(poolIDs, ", "))
}
scaleSets, err := r.store.ListEntityScaleSets(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching organization scale sets")
}
if len(scaleSets) > 0 {
return runnerErrors.NewBadRequestError("organization has scale sets defined; delete them first")
}
if !keepWebhook && r.config.Default.EnableWebhookManagement {
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {

Some files were not shown because too many files have changed in this diff Show more