Move URLs from default section of config to DB
This change moves the callback_url, metadata_url and webhooks_url from the config to the database. The goal is to move as much as possible from the config to the DB, in preparation for a potential refactor that will allow GARM to scale out. This would allow multiple nodes to share a single source of truth. Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
7ee235aeb0
commit
9748aa47af
22 changed files with 1067 additions and 177 deletions
|
|
@ -391,3 +391,42 @@ func (a *APIController) ControllerInfoHandler(w http.ResponseWriter, r *http.Req
|
||||||
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:route PUT /controller controller UpdateController
|
||||||
|
//
|
||||||
|
// Update controller.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: Body
|
||||||
|
// description: Parameters used when updating the controller.
|
||||||
|
// type: UpdateControllerParams
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: ControllerInfo
|
||||||
|
// 400: APIErrorResponse
|
||||||
|
func (a *APIController) UpdateControllerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
var updateParams runnerParams.UpdateControllerParams
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&updateParams); err != nil {
|
||||||
|
handleError(ctx, w, gErrors.ErrBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateParams.Validate(); err != nil {
|
||||||
|
handleError(ctx, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := a.r.UpdateController(ctx, updateParams)
|
||||||
|
if err != nil {
|
||||||
|
handleError(ctx, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(info); err != nil {
|
||||||
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,9 @@ var (
|
||||||
Error: "init_required",
|
Error: "init_required",
|
||||||
Details: "Missing superuser",
|
Details: "Missing superuser",
|
||||||
}
|
}
|
||||||
|
// URLsRequired is returned if the controller does not have the required URLs
|
||||||
|
URLsRequired = APIErrorResponse{
|
||||||
|
Error: "urls_required",
|
||||||
|
Details: "Missing required URLs. Make sure you update the metadata, callback and webhook URLs",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func requestLogger(h http.Handler) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware, instanceMiddleware auth.Middleware, manageWebhooks bool) *mux.Router {
|
func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware, urlsRequiredMiddleware, instanceMiddleware auth.Middleware, manageWebhooks bool) *mux.Router {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.Use(requestLogger)
|
router.Use(requestLogger)
|
||||||
|
|
||||||
|
|
@ -152,11 +152,38 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
|
||||||
authRouter.Handle("/{login:login\\/?}", http.HandlerFunc(han.LoginHandler)).Methods("POST", "OPTIONS")
|
authRouter.Handle("/{login:login\\/?}", http.HandlerFunc(han.LoginHandler)).Methods("POST", "OPTIONS")
|
||||||
authRouter.Use(initMiddleware.Middleware)
|
authRouter.Use(initMiddleware.Middleware)
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// Controller endpoints //
|
||||||
|
//////////////////////////
|
||||||
|
controllerRouter := apiSubRouter.PathPrefix("/controller").Subrouter()
|
||||||
|
// The controller endpoints allow us to get information about the controller and update the URL endpoints.
|
||||||
|
// This endpoint must not be guarded by the urlsRequiredMiddleware as that would prevent the user from
|
||||||
|
// updating the URLs.
|
||||||
|
controllerRouter.Use(initMiddleware.Middleware)
|
||||||
|
controllerRouter.Use(authMiddleware.Middleware)
|
||||||
|
controllerRouter.Use(auth.AdminRequiredMiddleware)
|
||||||
|
// Get controller info
|
||||||
|
controllerRouter.Handle("/", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
||||||
|
controllerRouter.Handle("", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
||||||
|
// Update controller
|
||||||
|
controllerRouter.Handle("/", http.HandlerFunc(han.UpdateControllerHandler)).Methods("PUT", "OPTIONS")
|
||||||
|
controllerRouter.Handle("", http.HandlerFunc(han.UpdateControllerHandler)).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
// API router for everything else //
|
||||||
|
////////////////////////////////////
|
||||||
apiRouter := apiSubRouter.PathPrefix("").Subrouter()
|
apiRouter := apiSubRouter.PathPrefix("").Subrouter()
|
||||||
apiRouter.Use(initMiddleware.Middleware)
|
apiRouter.Use(initMiddleware.Middleware)
|
||||||
|
// all endpoints except the controller endpoint should return an error
|
||||||
|
// if the required metadata, callback and webhook URLs are not set.
|
||||||
|
apiRouter.Use(urlsRequiredMiddleware.Middleware)
|
||||||
apiRouter.Use(authMiddleware.Middleware)
|
apiRouter.Use(authMiddleware.Middleware)
|
||||||
apiRouter.Use(auth.AdminRequiredMiddleware)
|
apiRouter.Use(auth.AdminRequiredMiddleware)
|
||||||
|
|
||||||
|
// Legacy controller path
|
||||||
|
apiRouter.Handle("/controller-info/", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
||||||
|
apiRouter.Handle("/controller-info", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// Metrics Token
|
// Metrics Token
|
||||||
apiRouter.Handle("/metrics-token/", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
|
apiRouter.Handle("/metrics-token/", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
|
||||||
apiRouter.Handle("/metrics-token", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
|
apiRouter.Handle("/metrics-token", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
|
||||||
|
|
@ -343,10 +370,6 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
|
||||||
apiRouter.Handle("/providers/", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
|
apiRouter.Handle("/providers/", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
|
||||||
apiRouter.Handle("/providers", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
|
apiRouter.Handle("/providers", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// Controller info
|
|
||||||
apiRouter.Handle("/controller-info/", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
|
||||||
apiRouter.Handle("/controller-info", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
|
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
// Github Endpoints //
|
// Github Endpoints //
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
|
||||||
|
|
@ -278,3 +278,10 @@ definitions:
|
||||||
import:
|
import:
|
||||||
package: github.com/cloudbase/garm/params
|
package: github.com/cloudbase/garm/params
|
||||||
alias: garm_params
|
alias: garm_params
|
||||||
|
UpdateControllerParams:
|
||||||
|
type: object
|
||||||
|
x-go-type:
|
||||||
|
type: UpdateControllerParams
|
||||||
|
import:
|
||||||
|
package: github.com/cloudbase/garm/params
|
||||||
|
alias: garm_params
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,13 @@ definitions:
|
||||||
alias: garm_params
|
alias: garm_params
|
||||||
package: github.com/cloudbase/garm/params
|
package: github.com/cloudbase/garm/params
|
||||||
type: Repository
|
type: Repository
|
||||||
|
UpdateControllerParams:
|
||||||
|
type: object
|
||||||
|
x-go-type:
|
||||||
|
import:
|
||||||
|
alias: garm_params
|
||||||
|
package: github.com/cloudbase/garm/params
|
||||||
|
type: UpdateControllerParams
|
||||||
UpdateEntityParams:
|
UpdateEntityParams:
|
||||||
type: object
|
type: object
|
||||||
x-go-type:
|
x-go-type:
|
||||||
|
|
@ -311,6 +318,30 @@ paths:
|
||||||
summary: Logs in a user and returns a JWT token.
|
summary: Logs in a user and returns a JWT token.
|
||||||
tags:
|
tags:
|
||||||
- login
|
- login
|
||||||
|
/controller:
|
||||||
|
put:
|
||||||
|
operationId: UpdateController
|
||||||
|
parameters:
|
||||||
|
- description: Parameters used when updating the controller.
|
||||||
|
in: body
|
||||||
|
name: Body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UpdateControllerParams'
|
||||||
|
description: Parameters used when updating the controller.
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ControllerInfo
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ControllerInfo'
|
||||||
|
"400":
|
||||||
|
description: APIErrorResponse
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/APIErrorResponse'
|
||||||
|
summary: Update controller.
|
||||||
|
tags:
|
||||||
|
- controller
|
||||||
/controller-info:
|
/controller-info:
|
||||||
get:
|
get:
|
||||||
operationId: ControllerInfo
|
operationId: ControllerInfo
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,30 @@ func (i *initRequired) Middleware(next http.Handler) http.Handler {
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewUrlsRequiredMiddleware(store common.Store) (Middleware, error) {
|
||||||
|
return &urlsRequired{
|
||||||
|
store: store,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlsRequired struct {
|
||||||
|
store common.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlsRequired) Middleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
ctrlInfo, err := u.store.ControllerInfo()
|
||||||
|
if err != nil || ctrlInfo.WebhookURL == "" || ctrlInfo.MetadataURL == "" || ctrlInfo.CallbackURL == "" {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusConflict)
|
||||||
|
if err := json.NewEncoder(w).Encode(params.URLsRequired); err != nil {
|
||||||
|
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
106
client/controller/controller_client.go
Normal file
106
client/controller/controller_client.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-openapi/runtime"
|
||||||
|
httptransport "github.com/go-openapi/runtime/client"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new controller API client.
|
||||||
|
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
|
||||||
|
return &Client{transport: transport, formats: formats}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new controller 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 controller 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 controller 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 {
|
||||||
|
UpdateController(params *UpdateControllerParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateControllerOK, error)
|
||||||
|
|
||||||
|
SetTransport(transport runtime.ClientTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateController updates controller
|
||||||
|
*/
|
||||||
|
func (a *Client) UpdateController(params *UpdateControllerParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateControllerOK, error) {
|
||||||
|
// TODO: Validate the params before sending
|
||||||
|
if params == nil {
|
||||||
|
params = NewUpdateControllerParams()
|
||||||
|
}
|
||||||
|
op := &runtime.ClientOperation{
|
||||||
|
ID: "UpdateController",
|
||||||
|
Method: "PUT",
|
||||||
|
PathPattern: "/controller",
|
||||||
|
ProducesMediaTypes: []string{"application/json"},
|
||||||
|
ConsumesMediaTypes: []string{"application/json"},
|
||||||
|
Schemes: []string{"http"},
|
||||||
|
Params: params,
|
||||||
|
Reader: &UpdateControllerReader{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.(*UpdateControllerOK)
|
||||||
|
if ok {
|
||||||
|
return success, nil
|
||||||
|
}
|
||||||
|
// unexpected success response
|
||||||
|
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
|
||||||
|
msg := fmt.Sprintf("unexpected success response for UpdateController: API contract not enforced by server. Client expected to get an error, but got: %T", result)
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransport changes the transport on the client
|
||||||
|
func (a *Client) SetTransport(transport runtime.ClientTransport) {
|
||||||
|
a.transport = transport
|
||||||
|
}
|
||||||
151
client/controller/update_controller_parameters.go
Normal file
151
client/controller/update_controller_parameters.go
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUpdateControllerParams creates a new UpdateControllerParams 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 NewUpdateControllerParams() *UpdateControllerParams {
|
||||||
|
return &UpdateControllerParams{
|
||||||
|
timeout: cr.DefaultTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUpdateControllerParamsWithTimeout creates a new UpdateControllerParams object
|
||||||
|
// with the ability to set a timeout on a request.
|
||||||
|
func NewUpdateControllerParamsWithTimeout(timeout time.Duration) *UpdateControllerParams {
|
||||||
|
return &UpdateControllerParams{
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUpdateControllerParamsWithContext creates a new UpdateControllerParams object
|
||||||
|
// with the ability to set a context for a request.
|
||||||
|
func NewUpdateControllerParamsWithContext(ctx context.Context) *UpdateControllerParams {
|
||||||
|
return &UpdateControllerParams{
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUpdateControllerParamsWithHTTPClient creates a new UpdateControllerParams object
|
||||||
|
// with the ability to set a custom HTTPClient for a request.
|
||||||
|
func NewUpdateControllerParamsWithHTTPClient(client *http.Client) *UpdateControllerParams {
|
||||||
|
return &UpdateControllerParams{
|
||||||
|
HTTPClient: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateControllerParams contains all the parameters to send to the API endpoint
|
||||||
|
|
||||||
|
for the update controller operation.
|
||||||
|
|
||||||
|
Typically these are written to a http.Request.
|
||||||
|
*/
|
||||||
|
type UpdateControllerParams struct {
|
||||||
|
|
||||||
|
/* Body.
|
||||||
|
|
||||||
|
Parameters used when updating the controller.
|
||||||
|
*/
|
||||||
|
Body garm_params.UpdateControllerParams
|
||||||
|
|
||||||
|
timeout time.Duration
|
||||||
|
Context context.Context
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaults hydrates default values in the update controller params (not the query body).
|
||||||
|
//
|
||||||
|
// All values with no default are reset to their zero value.
|
||||||
|
func (o *UpdateControllerParams) WithDefaults() *UpdateControllerParams {
|
||||||
|
o.SetDefaults()
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults hydrates default values in the update controller params (not the query body).
|
||||||
|
//
|
||||||
|
// All values with no default are reset to their zero value.
|
||||||
|
func (o *UpdateControllerParams) SetDefaults() {
|
||||||
|
// no default values defined for this parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout adds the timeout to the update controller params
|
||||||
|
func (o *UpdateControllerParams) WithTimeout(timeout time.Duration) *UpdateControllerParams {
|
||||||
|
o.SetTimeout(timeout)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout adds the timeout to the update controller params
|
||||||
|
func (o *UpdateControllerParams) SetTimeout(timeout time.Duration) {
|
||||||
|
o.timeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext adds the context to the update controller params
|
||||||
|
func (o *UpdateControllerParams) WithContext(ctx context.Context) *UpdateControllerParams {
|
||||||
|
o.SetContext(ctx)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext adds the context to the update controller params
|
||||||
|
func (o *UpdateControllerParams) SetContext(ctx context.Context) {
|
||||||
|
o.Context = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPClient adds the HTTPClient to the update controller params
|
||||||
|
func (o *UpdateControllerParams) WithHTTPClient(client *http.Client) *UpdateControllerParams {
|
||||||
|
o.SetHTTPClient(client)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPClient adds the HTTPClient to the update controller params
|
||||||
|
func (o *UpdateControllerParams) SetHTTPClient(client *http.Client) {
|
||||||
|
o.HTTPClient = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBody adds the body to the update controller params
|
||||||
|
func (o *UpdateControllerParams) WithBody(body garm_params.UpdateControllerParams) *UpdateControllerParams {
|
||||||
|
o.SetBody(body)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBody adds the body to the update controller params
|
||||||
|
func (o *UpdateControllerParams) SetBody(body garm_params.UpdateControllerParams) {
|
||||||
|
o.Body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToRequest writes these params to a swagger request
|
||||||
|
func (o *UpdateControllerParams) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
179
client/controller/update_controller_responses.go
Normal file
179
client/controller/update_controller_responses.go
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateControllerReader is a Reader for the UpdateController structure.
|
||||||
|
type UpdateControllerReader struct {
|
||||||
|
formats strfmt.Registry
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResponse reads a server response into the received o.
|
||||||
|
func (o *UpdateControllerReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
|
||||||
|
switch response.Code() {
|
||||||
|
case 200:
|
||||||
|
result := NewUpdateControllerOK()
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
case 400:
|
||||||
|
result := NewUpdateControllerBadRequest()
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, result
|
||||||
|
default:
|
||||||
|
return nil, runtime.NewAPIError("[PUT /controller] UpdateController", response, response.Code())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUpdateControllerOK creates a UpdateControllerOK with default headers values
|
||||||
|
func NewUpdateControllerOK() *UpdateControllerOK {
|
||||||
|
return &UpdateControllerOK{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateControllerOK describes a response with status code 200, with default header values.
|
||||||
|
|
||||||
|
ControllerInfo
|
||||||
|
*/
|
||||||
|
type UpdateControllerOK struct {
|
||||||
|
Payload garm_params.ControllerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this update controller o k response has a 2xx status code
|
||||||
|
func (o *UpdateControllerOK) IsSuccess() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this update controller o k response has a 3xx status code
|
||||||
|
func (o *UpdateControllerOK) IsRedirect() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this update controller o k response has a 4xx status code
|
||||||
|
func (o *UpdateControllerOK) IsClientError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this update controller o k response has a 5xx status code
|
||||||
|
func (o *UpdateControllerOK) IsServerError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this update controller o k response a status code equal to that given
|
||||||
|
func (o *UpdateControllerOK) IsCode(code int) bool {
|
||||||
|
return code == 200
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code gets the status code for the update controller o k response
|
||||||
|
func (o *UpdateControllerOK) Code() int {
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerOK) Error() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[PUT /controller][%d] updateControllerOK %s", 200, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerOK) String() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[PUT /controller][%d] updateControllerOK %s", 200, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerOK) GetPayload() garm_params.ControllerInfo {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerOK) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUpdateControllerBadRequest creates a UpdateControllerBadRequest with default headers values
|
||||||
|
func NewUpdateControllerBadRequest() *UpdateControllerBadRequest {
|
||||||
|
return &UpdateControllerBadRequest{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateControllerBadRequest describes a response with status code 400, with default header values.
|
||||||
|
|
||||||
|
APIErrorResponse
|
||||||
|
*/
|
||||||
|
type UpdateControllerBadRequest struct {
|
||||||
|
Payload apiserver_params.APIErrorResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this update controller bad request response has a 2xx status code
|
||||||
|
func (o *UpdateControllerBadRequest) IsSuccess() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this update controller bad request response has a 3xx status code
|
||||||
|
func (o *UpdateControllerBadRequest) IsRedirect() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this update controller bad request response has a 4xx status code
|
||||||
|
func (o *UpdateControllerBadRequest) IsClientError() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this update controller bad request response has a 5xx status code
|
||||||
|
func (o *UpdateControllerBadRequest) IsServerError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this update controller bad request response a status code equal to that given
|
||||||
|
func (o *UpdateControllerBadRequest) IsCode(code int) bool {
|
||||||
|
return code == 400
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code gets the status code for the update controller bad request response
|
||||||
|
func (o *UpdateControllerBadRequest) Code() int {
|
||||||
|
return 400
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerBadRequest) Error() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[PUT /controller][%d] updateControllerBadRequest %s", 400, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerBadRequest) String() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[PUT /controller][%d] updateControllerBadRequest %s", 400, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerBadRequest) GetPayload() apiserver_params.APIErrorResponse {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UpdateControllerBadRequest) 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
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
httptransport "github.com/go-openapi/runtime/client"
|
httptransport "github.com/go-openapi/runtime/client"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
|
||||||
|
"github.com/cloudbase/garm/client/controller"
|
||||||
"github.com/cloudbase/garm/client/controller_info"
|
"github.com/cloudbase/garm/client/controller_info"
|
||||||
"github.com/cloudbase/garm/client/credentials"
|
"github.com/cloudbase/garm/client/credentials"
|
||||||
"github.com/cloudbase/garm/client/endpoints"
|
"github.com/cloudbase/garm/client/endpoints"
|
||||||
|
|
@ -67,6 +68,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *GarmAPI {
|
||||||
|
|
||||||
cli := new(GarmAPI)
|
cli := new(GarmAPI)
|
||||||
cli.Transport = transport
|
cli.Transport = transport
|
||||||
|
cli.Controller = controller.New(transport, formats)
|
||||||
cli.ControllerInfo = controller_info.New(transport, formats)
|
cli.ControllerInfo = controller_info.New(transport, formats)
|
||||||
cli.Credentials = credentials.New(transport, formats)
|
cli.Credentials = credentials.New(transport, formats)
|
||||||
cli.Endpoints = endpoints.New(transport, formats)
|
cli.Endpoints = endpoints.New(transport, formats)
|
||||||
|
|
@ -124,6 +126,8 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig {
|
||||||
|
|
||||||
// GarmAPI is a client for garm API
|
// GarmAPI is a client for garm API
|
||||||
type GarmAPI struct {
|
type GarmAPI struct {
|
||||||
|
Controller controller.ClientService
|
||||||
|
|
||||||
ControllerInfo controller_info.ClientService
|
ControllerInfo controller_info.ClientService
|
||||||
|
|
||||||
Credentials credentials.ClientService
|
Credentials credentials.ClientService
|
||||||
|
|
@ -156,6 +160,7 @@ type GarmAPI struct {
|
||||||
// SetTransport changes the transport on the client and all its subresources
|
// SetTransport changes the transport on the client and all its subresources
|
||||||
func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) {
|
func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) {
|
||||||
c.Transport = transport
|
c.Transport = transport
|
||||||
|
c.Controller.SetTransport(transport)
|
||||||
c.ControllerInfo.SetTransport(transport)
|
c.ControllerInfo.SetTransport(transport)
|
||||||
c.Credentials.SetTransport(transport)
|
c.Credentials.SetTransport(transport)
|
||||||
c.Endpoints.SetTransport(transport)
|
c.Endpoints.SetTransport(transport)
|
||||||
|
|
|
||||||
173
cmd/garm-cli/cmd/controller.go
Normal file
173
cmd/garm-cli/cmd/controller.go
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright 2023 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"
|
||||||
|
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
apiClientController "github.com/cloudbase/garm/client/controller"
|
||||||
|
apiClientControllerInfo "github.com/cloudbase/garm/client/controller_info"
|
||||||
|
"github.com/cloudbase/garm/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
var controllerCmd = &cobra.Command{
|
||||||
|
Use: "controller",
|
||||||
|
Aliases: []string{"controller-info"},
|
||||||
|
SilenceUsage: true,
|
||||||
|
Short: "Controller operations",
|
||||||
|
Long: `Query or update information about the current controller.`,
|
||||||
|
Run: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var controllerShowCmd = &cobra.Command{
|
||||||
|
Use: "show",
|
||||||
|
Short: "Show information",
|
||||||
|
Long: `Show information about the current controller.`,
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
if needsInit {
|
||||||
|
return errNeedsInitError
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo := apiClientControllerInfo.NewControllerInfoParams()
|
||||||
|
response, err := apiCli.ControllerInfo.ControllerInfo(showInfo, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return formatInfo(response.Payload)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var controllerUpdateCmd = &cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "Update controller information",
|
||||||
|
Long: `Update information about the current controller.
|
||||||
|
|
||||||
|
Warning: Dragons ahead, please read carefully.
|
||||||
|
|
||||||
|
Changing the URLs for the controller metadata, callback and webhooks, will
|
||||||
|
impact the controller's ability to manage webhooks and runners.
|
||||||
|
|
||||||
|
As GARM can be set up behind a reverse proxy or through several layers of
|
||||||
|
network address translation or load balancing, we need to explicitly tell
|
||||||
|
GARM how to reach each of these URLs. Internally, GARM sets up API endpoints
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
* /webhooks - the base URL for the webhooks. Github needs to reach this URL.
|
||||||
|
* /api/v1/metadata - the metadata URL. Your runners need to be able to reach this URL.
|
||||||
|
* /api/v1/callbacks - the callback URL. Your runners need to be able to reach this URL.
|
||||||
|
|
||||||
|
You need to expose these endpoints to the interested parties (github or
|
||||||
|
your runners), then you need to update the controller with the URLs you set up.
|
||||||
|
|
||||||
|
For example, if you set the webhooks URL in your reverse proxy to
|
||||||
|
https://garm.example.com/garm-hooks, this still needs to point to the "/webhooks"
|
||||||
|
URL in the GARM backend, but in the controller info you need to set the URL to
|
||||||
|
https://garm.example.com/garm-hooks using:
|
||||||
|
|
||||||
|
garm-cli controller update --webhook-url=https://garm.example.com/garm-hooks
|
||||||
|
|
||||||
|
If you expose GARM to the outside world directly, or if you don't rewrite the URLs
|
||||||
|
above in your reverse proxy config, use the above 3 endpoints without change,
|
||||||
|
substituting garm.example.com with the correct hostname or IP address.
|
||||||
|
|
||||||
|
In most cases, you will have a GARM backend (say 192.168.100.10) and a reverse
|
||||||
|
proxy in front of it exposed as https://garm.example.com. If you don't rewrite
|
||||||
|
the URLs in the reverse proxy, and you just point to your backend, you can set
|
||||||
|
up the GARM controller URLs as:
|
||||||
|
|
||||||
|
garm-cli controller update \
|
||||||
|
--webhook-url=https://garm.example.com/webhooks \
|
||||||
|
--metadata-url=https://garm.example.com/api/v1/metadata \
|
||||||
|
--callback-url=https://garm.example.com/api/v1/callbacks
|
||||||
|
`,
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
if needsInit {
|
||||||
|
return errNeedsInitError
|
||||||
|
}
|
||||||
|
|
||||||
|
params := params.UpdateControllerParams{}
|
||||||
|
if cmd.Flags().Changed("metadata-url") {
|
||||||
|
params.MetadataURL = &metadataURL
|
||||||
|
}
|
||||||
|
if cmd.Flags().Changed("callback-url") {
|
||||||
|
params.CallbackURL = &callbackURL
|
||||||
|
}
|
||||||
|
if cmd.Flags().Changed("webhook-url") {
|
||||||
|
params.WebhookURL = &webhookURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.WebhookURL == nil && params.MetadataURL == nil && params.CallbackURL == nil {
|
||||||
|
cmd.Help()
|
||||||
|
return fmt.Errorf("at least one of metadata-url, callback-url or webhook-url must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUrlsReq := apiClientController.NewUpdateControllerParams()
|
||||||
|
updateUrlsReq.Body = params
|
||||||
|
|
||||||
|
info, err := apiCli.Controller.UpdateController(updateUrlsReq, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating controller: %w", err)
|
||||||
|
}
|
||||||
|
formatInfo(info.Payload)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderControllerInfoTable(info params.ControllerInfo) string {
|
||||||
|
t := table.NewWriter()
|
||||||
|
header := table.Row{"Field", "Value"}
|
||||||
|
|
||||||
|
if info.WebhookURL == "" {
|
||||||
|
info.WebhookURL = "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ControllerWebhookURL == "" {
|
||||||
|
info.ControllerWebhookURL = "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AppendHeader(header)
|
||||||
|
t.AppendRow(table.Row{"Controller ID", info.ControllerID})
|
||||||
|
if info.Hostname != "" {
|
||||||
|
t.AppendRow(table.Row{"Hostname", info.Hostname})
|
||||||
|
}
|
||||||
|
t.AppendRow(table.Row{"Metadata URL", info.MetadataURL})
|
||||||
|
t.AppendRow(table.Row{"Callback URL", info.CallbackURL})
|
||||||
|
t.AppendRow(table.Row{"Webhook Base URL", info.WebhookURL})
|
||||||
|
t.AppendRow(table.Row{"Controller Webhook URL", info.ControllerWebhookURL})
|
||||||
|
return t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatInfo(info params.ControllerInfo) error {
|
||||||
|
fmt.Println(renderControllerInfoTable(info))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
controllerUpdateCmd.Flags().StringVarP(&metadataURL, "metadata-url", "m", "", "The metadata URL for the controller (ie. https://garm.example.com/api/v1/metadata)")
|
||||||
|
controllerUpdateCmd.Flags().StringVarP(&callbackURL, "callback-url", "c", "", "The callback URL for the controller (ie. https://garm.example.com/api/v1/callbacks)")
|
||||||
|
controllerUpdateCmd.Flags().StringVarP(&webhookURL, "webhook-url", "w", "", "The webhook URL for the controller (ie. https://garm.example.com/webhooks)")
|
||||||
|
|
||||||
|
controllerCmd.AddCommand(
|
||||||
|
controllerShowCmd,
|
||||||
|
controllerUpdateCmd,
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(controllerCmd)
|
||||||
|
}
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
// Copyright 2023 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"
|
|
||||||
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
apiClientControllerInfo "github.com/cloudbase/garm/client/controller_info"
|
|
||||||
"github.com/cloudbase/garm/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var infoCmd = &cobra.Command{
|
|
||||||
Use: "controller-info",
|
|
||||||
SilenceUsage: true,
|
|
||||||
Short: "Information about controller",
|
|
||||||
Long: `Query information about the current controller.`,
|
|
||||||
Run: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
var infoShowCmd = &cobra.Command{
|
|
||||||
Use: "show",
|
|
||||||
Short: "Show information",
|
|
||||||
Long: `Show information about the current controller.`,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(_ *cobra.Command, _ []string) error {
|
|
||||||
if needsInit {
|
|
||||||
return errNeedsInitError
|
|
||||||
}
|
|
||||||
|
|
||||||
showInfo := apiClientControllerInfo.NewControllerInfoParams()
|
|
||||||
response, err := apiCli.ControllerInfo.ControllerInfo(showInfo, authToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return formatInfo(response.Payload)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatInfo(info params.ControllerInfo) error {
|
|
||||||
t := table.NewWriter()
|
|
||||||
header := table.Row{"Field", "Value"}
|
|
||||||
|
|
||||||
if info.WebhookURL == "" {
|
|
||||||
info.WebhookURL = "N/A"
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.ControllerWebhookURL == "" {
|
|
||||||
info.ControllerWebhookURL = "N/A"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.AppendHeader(header)
|
|
||||||
t.AppendRow(table.Row{"Controller ID", info.ControllerID})
|
|
||||||
t.AppendRow(table.Row{"Hostname", info.Hostname})
|
|
||||||
t.AppendRow(table.Row{"Metadata URL", info.MetadataURL})
|
|
||||||
t.AppendRow(table.Row{"Callback URL", info.CallbackURL})
|
|
||||||
t.AppendRow(table.Row{"Webhook Base URL", info.WebhookURL})
|
|
||||||
t.AppendRow(table.Row{"Controller Webhook URL", info.ControllerWebhookURL})
|
|
||||||
fmt.Println(t.Render())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
infoCmd.AddCommand(
|
|
||||||
infoShowCmd,
|
|
||||||
)
|
|
||||||
|
|
||||||
rootCmd.AddCommand(infoCmd)
|
|
||||||
}
|
|
||||||
|
|
@ -16,12 +16,15 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
openapiRuntimeClient "github.com/go-openapi/runtime/client"
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
apiClientController "github.com/cloudbase/garm/client/controller"
|
||||||
apiClientFirstRun "github.com/cloudbase/garm/client/first_run"
|
apiClientFirstRun "github.com/cloudbase/garm/client/first_run"
|
||||||
apiClientLogin "github.com/cloudbase/garm/client/login"
|
apiClientLogin "github.com/cloudbase/garm/client/login"
|
||||||
"github.com/cloudbase/garm/cmd/garm-cli/common"
|
"github.com/cloudbase/garm/cmd/garm-cli/common"
|
||||||
|
|
@ -29,6 +32,12 @@ import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
callbackURL string
|
||||||
|
metadataURL string
|
||||||
|
webhookURL string
|
||||||
|
)
|
||||||
|
|
||||||
// initCmd represents the init command
|
// initCmd represents the init command
|
||||||
var initCmd = &cobra.Command{
|
var initCmd = &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
|
|
@ -52,10 +61,13 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url := strings.TrimSuffix(loginURL, "/")
|
||||||
if err := promptUnsetInitVariables(); err != nil {
|
if err := promptUnsetInitVariables(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureDefaultEndpoints(url)
|
||||||
|
|
||||||
newUserReq := apiClientFirstRun.NewFirstRunParams()
|
newUserReq := apiClientFirstRun.NewFirstRunParams()
|
||||||
newUserReq.Body = params.NewUserParams{
|
newUserReq.Body = params.NewUserParams{
|
||||||
Username: loginUserName,
|
Username: loginUserName,
|
||||||
|
|
@ -63,9 +75,6 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
||||||
FullName: loginFullName,
|
FullName: loginFullName,
|
||||||
Email: loginEmail,
|
Email: loginEmail,
|
||||||
}
|
}
|
||||||
|
|
||||||
url := strings.TrimSuffix(loginURL, "/")
|
|
||||||
|
|
||||||
initAPIClient(url, "")
|
initAPIClient(url, "")
|
||||||
|
|
||||||
response, err := apiCli.FirstRun.FirstRun(newUserReq, authToken)
|
response, err := apiCli.FirstRun.FirstRun(newUserReq, authToken)
|
||||||
|
|
@ -90,17 +99,50 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
||||||
Token: token.Payload.Token,
|
Token: token.Payload.Token,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
authToken = openapiRuntimeClient.BearerToken(token.Payload.Token)
|
||||||
cfg.ActiveManager = loginProfileName
|
cfg.ActiveManager = loginProfileName
|
||||||
|
|
||||||
if err := cfg.SaveConfig(); err != nil {
|
if err := cfg.SaveConfig(); err != nil {
|
||||||
return errors.Wrap(err, "saving config")
|
return errors.Wrap(err, "saving config")
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUserTable(response.Payload)
|
updateUrlsReq := apiClientController.NewUpdateControllerParams()
|
||||||
|
updateUrlsReq.Body = params.UpdateControllerParams{
|
||||||
|
MetadataURL: &metadataURL,
|
||||||
|
CallbackURL: &callbackURL,
|
||||||
|
WebhookURL: &webhookURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerInfoResponse, err := apiCli.Controller.UpdateController(updateUrlsReq, authToken)
|
||||||
|
renderResponseMessage(response.Payload, controllerInfoResponse.Payload, err)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureDefaultEndpoints(loginURL string) (err error) {
|
||||||
|
if metadataURL == "" {
|
||||||
|
metadataURL, err = url.JoinPath(loginURL, "api/v1/callbacks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if callbackURL == "" {
|
||||||
|
callbackURL, err = url.JoinPath(loginURL, "api/v1/callbacks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if webhookURL == "" {
|
||||||
|
webhookURL, err = url.JoinPath(loginURL, "webhooks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func promptUnsetInitVariables() error {
|
func promptUnsetInitVariables() error {
|
||||||
var err error
|
var err error
|
||||||
if loginUserName == "" {
|
if loginUserName == "" {
|
||||||
|
|
@ -123,6 +165,7 @@ func promptUnsetInitVariables() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,13 +176,16 @@ func init() {
|
||||||
initCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
|
initCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
|
||||||
initCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "The desired administrative username")
|
initCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "The desired administrative username")
|
||||||
initCmd.Flags().StringVarP(&loginEmail, "email", "e", "", "Email address")
|
initCmd.Flags().StringVarP(&loginEmail, "email", "e", "", "Email address")
|
||||||
|
initCmd.Flags().StringVarP(&metadataURL, "metadata-url", "m", "", "The metadata URL for the controller (ie. https://garm.example.com/api/v1/metadata)")
|
||||||
|
initCmd.Flags().StringVarP(&callbackURL, "callback-url", "c", "", "The callback URL for the controller (ie. https://garm.example.com/api/v1/callbacks)")
|
||||||
|
initCmd.Flags().StringVarP(&webhookURL, "webhook-url", "w", "", "The webhook URL for the controller (ie. https://garm.example.com/webhooks)")
|
||||||
initCmd.Flags().StringVarP(&loginFullName, "full-name", "f", "", "Full name of the user")
|
initCmd.Flags().StringVarP(&loginFullName, "full-name", "f", "", "Full name of the user")
|
||||||
initCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The admin password")
|
initCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The admin password")
|
||||||
initCmd.MarkFlagRequired("name") //nolint
|
initCmd.MarkFlagRequired("name") //nolint
|
||||||
initCmd.MarkFlagRequired("url") //nolint
|
initCmd.MarkFlagRequired("url") //nolint
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderUserTable(user params.User) {
|
func renderUserTable(user params.User) string {
|
||||||
t := table.NewWriter()
|
t := table.NewWriter()
|
||||||
header := table.Row{"Field", "Value"}
|
header := table.Row{"Field", "Value"}
|
||||||
t.AppendHeader(header)
|
t.AppendHeader(header)
|
||||||
|
|
@ -148,5 +194,54 @@ func renderUserTable(user params.User) {
|
||||||
t.AppendRow(table.Row{"Username", user.Username})
|
t.AppendRow(table.Row{"Username", user.Username})
|
||||||
t.AppendRow(table.Row{"Email", user.Email})
|
t.AppendRow(table.Row{"Email", user.Email})
|
||||||
t.AppendRow(table.Row{"Enabled", user.Enabled})
|
t.AppendRow(table.Row{"Enabled", user.Enabled})
|
||||||
fmt.Println(t.Render())
|
return t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderResponseMessage(user params.User, controllerInfo params.ControllerInfo, err error) {
|
||||||
|
userTable := renderUserTable(user)
|
||||||
|
controllerInfoTable := renderControllerInfoTable(controllerInfo)
|
||||||
|
|
||||||
|
headerMsg := `Congrats! Your controller is now initialized.
|
||||||
|
|
||||||
|
Following are the details of the admin user and details about the controller.
|
||||||
|
|
||||||
|
Admin user information:
|
||||||
|
|
||||||
|
%s
|
||||||
|
`
|
||||||
|
|
||||||
|
controllerMsg := `Controller information:
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
Make sure that the URLs in the table above are reachable by the relevant parties.
|
||||||
|
|
||||||
|
The metadata and callback URLs *must* be accessible by the runners that GARM spins up.
|
||||||
|
The base webhook and the controller webhook URLs must be accessible by GitHub or GHES.
|
||||||
|
`
|
||||||
|
|
||||||
|
controllerErrorMsg := `WARNING: Failed to set the required controller URLs with error: %q
|
||||||
|
|
||||||
|
Please run:
|
||||||
|
|
||||||
|
garm-cli controller show
|
||||||
|
|
||||||
|
To make sure that the callback, metadata and webhook URLs are set correctly. If not,
|
||||||
|
you must set them up by running:
|
||||||
|
|
||||||
|
garm-cli controller update \
|
||||||
|
--metadata-url=<metadata-url> \
|
||||||
|
--callback-url=<callback-url> \
|
||||||
|
--webhook-url=<webhook-url>
|
||||||
|
|
||||||
|
See the help message for garm-cli controller update for more information.
|
||||||
|
`
|
||||||
|
var ctrlMsg string
|
||||||
|
if err != nil {
|
||||||
|
ctrlMsg = fmt.Sprintf(controllerErrorMsg, err)
|
||||||
|
} else {
|
||||||
|
ctrlMsg = fmt.Sprintf(controllerMsg, controllerInfoTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n%s\n", fmt.Sprintf(headerMsg, userTable), ctrlMsg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/nbutton23/zxcvbn-go"
|
"github.com/nbutton23/zxcvbn-go"
|
||||||
|
|
@ -45,7 +46,7 @@ func PromptPassword(label string) (string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PromptString(label string) (string, error) {
|
func PromptString(label string, a ...interface{}) (string, error) {
|
||||||
validate := func(input string) error {
|
validate := func(input string) error {
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return errors.New("empty input not allowed")
|
return errors.New("empty input not allowed")
|
||||||
|
|
@ -54,7 +55,7 @@ func PromptString(label string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt := promptui.Prompt{
|
prompt := promptui.Prompt{
|
||||||
Label: label,
|
Label: fmt.Sprintf(label, a...),
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
}
|
}
|
||||||
result, err := prompt.Run()
|
result, err := prompt.Run()
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import (
|
||||||
"github.com/cloudbase/garm/database"
|
"github.com/cloudbase/garm/database"
|
||||||
"github.com/cloudbase/garm/database/common"
|
"github.com/cloudbase/garm/database/common"
|
||||||
"github.com/cloudbase/garm/metrics"
|
"github.com/cloudbase/garm/metrics"
|
||||||
|
"github.com/cloudbase/garm/params"
|
||||||
"github.com/cloudbase/garm/runner" //nolint:typecheck
|
"github.com/cloudbase/garm/runner" //nolint:typecheck
|
||||||
runnerMetrics "github.com/cloudbase/garm/runner/metrics"
|
runnerMetrics "github.com/cloudbase/garm/runner/metrics"
|
||||||
garmUtil "github.com/cloudbase/garm/util"
|
garmUtil "github.com/cloudbase/garm/util"
|
||||||
|
|
@ -142,6 +143,38 @@ func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub
|
||||||
slog.SetDefault(slog.New(wrapped))
|
slog.SetDefault(slog.New(wrapped))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maybeUpdateURLsFromConfig(cfg config.Config, store common.Store) error {
|
||||||
|
info, err := store.ControllerInfo()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "fetching controller info")
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateParams params.UpdateControllerParams
|
||||||
|
|
||||||
|
if info.MetadataURL == "" && cfg.Default.MetadataURL != "" {
|
||||||
|
updateParams.MetadataURL = &cfg.Default.MetadataURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.CallbackURL == "" && cfg.Default.CallbackURL != "" {
|
||||||
|
updateParams.CallbackURL = &cfg.Default.CallbackURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.WebhookURL == "" && cfg.Default.WebhookURL != "" {
|
||||||
|
updateParams.WebhookURL = &cfg.Default.WebhookURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateParams.MetadataURL == nil && updateParams.CallbackURL == nil && updateParams.WebhookURL == nil {
|
||||||
|
// nothing to update
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = store.UpdateController(updateParams)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "updating controller info")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *version {
|
if *version {
|
||||||
|
|
@ -181,6 +214,10 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := maybeUpdateURLsFromConfig(*cfg, db); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
runner, err := runner.NewRunner(ctx, *cfg, db)
|
runner, err := runner.NewRunner(ctx, *cfg, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create controller: %+v", err)
|
log.Fatalf("failed to create controller: %+v", err)
|
||||||
|
|
@ -212,12 +249,17 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
urlsRequiredMiddleware, err := auth.NewUrlsRequiredMiddleware(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
metricsMiddleware, err := auth.NewMetricsMiddleware(cfg.JWTAuth)
|
metricsMiddleware, err := auth.NewMetricsMiddleware(cfg.JWTAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
router := routers.NewAPIRouter(controller, jwtMiddleware, initMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement)
|
router := routers.NewAPIRouter(controller, jwtMiddleware, initMiddleware, urlsRequiredMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement)
|
||||||
|
|
||||||
// start the metrics collector
|
// start the metrics collector
|
||||||
if cfg.Metrics.Enable {
|
if cfg.Metrics.Enable {
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,12 @@ type EntityPools interface {
|
||||||
ListEntityInstances(ctx context.Context, entity params.GithubEntity) ([]params.Instance, error)
|
ListEntityInstances(ctx context.Context, entity params.GithubEntity) ([]params.Instance, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ControllerStore interface {
|
||||||
|
ControllerInfo() (params.ControllerInfo, error)
|
||||||
|
InitController() (params.ControllerInfo, error)
|
||||||
|
UpdateController(info params.UpdateControllerParams) (params.ControllerInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate mockery --name=Store
|
//go:generate mockery --name=Store
|
||||||
type Store interface {
|
type Store interface {
|
||||||
RepoStore
|
RepoStore
|
||||||
|
|
@ -141,7 +147,5 @@ type Store interface {
|
||||||
EntityPools
|
EntityPools
|
||||||
GithubEndpointStore
|
GithubEndpointStore
|
||||||
GithubCredentialsStore
|
GithubCredentialsStore
|
||||||
|
ControllerStore
|
||||||
ControllerInfo() (params.ControllerInfo, error)
|
|
||||||
InitController() (params.ControllerInfo, error)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1516,6 +1516,34 @@ func (_m *Store) UnlockJob(ctx context.Context, jobID int64, entityID string) er
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateController provides a mock function with given fields: info
|
||||||
|
func (_m *Store) UpdateController(info params.UpdateControllerParams) (params.ControllerInfo, error) {
|
||||||
|
ret := _m.Called(info)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for UpdateController")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 params.ControllerInfo
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(params.UpdateControllerParams) (params.ControllerInfo, error)); ok {
|
||||||
|
return rf(info)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(params.UpdateControllerParams) params.ControllerInfo); ok {
|
||||||
|
r0 = rf(info)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(params.ControllerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(params.UpdateControllerParams) error); ok {
|
||||||
|
r1 = rf(info)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateEnterprise provides a mock function with given fields: ctx, enterpriseID, param
|
// UpdateEnterprise provides a mock function with given fields: ctx, enterpriseID, param
|
||||||
func (_m *Store) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateEntityParams) (params.Enterprise, error) {
|
func (_m *Store) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateEntityParams) (params.Enterprise, error) {
|
||||||
ret := _m.Called(ctx, enterpriseID, param)
|
ret := _m.Called(ctx, enterpriseID, param)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
package sql
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
@ -23,6 +25,21 @@ import (
|
||||||
"github.com/cloudbase/garm/params"
|
"github.com/cloudbase/garm/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func dbControllerToCommonController(dbInfo ControllerInfo) (params.ControllerInfo, error) {
|
||||||
|
url, err := url.JoinPath(dbInfo.WebhookBaseURL, dbInfo.ControllerID.String())
|
||||||
|
if err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "joining webhook URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return params.ControllerInfo{
|
||||||
|
ControllerID: dbInfo.ControllerID,
|
||||||
|
MetadataURL: dbInfo.MetadataURL,
|
||||||
|
WebhookURL: dbInfo.WebhookBaseURL,
|
||||||
|
ControllerWebhookURL: url,
|
||||||
|
CallbackURL: dbInfo.CallbackURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sqlDatabase) ControllerInfo() (params.ControllerInfo, error) {
|
func (s *sqlDatabase) ControllerInfo() (params.ControllerInfo, error) {
|
||||||
var info ControllerInfo
|
var info ControllerInfo
|
||||||
q := s.conn.Model(&ControllerInfo{}).First(&info)
|
q := s.conn.Model(&ControllerInfo{}).First(&info)
|
||||||
|
|
@ -32,9 +49,13 @@ func (s *sqlDatabase) ControllerInfo() (params.ControllerInfo, error) {
|
||||||
}
|
}
|
||||||
return params.ControllerInfo{}, errors.Wrap(q.Error, "fetching controller info")
|
return params.ControllerInfo{}, errors.Wrap(q.Error, "fetching controller info")
|
||||||
}
|
}
|
||||||
return params.ControllerInfo{
|
|
||||||
ControllerID: info.ControllerID,
|
paramInfo, err := dbControllerToCommonController(info)
|
||||||
}, nil
|
if err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "converting controller info")
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
|
func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
|
||||||
|
|
@ -60,3 +81,41 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
|
||||||
ControllerID: newInfo.ControllerID,
|
ControllerID: newInfo.ControllerID,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *sqlDatabase) UpdateController(info params.UpdateControllerParams) (params.ControllerInfo, error) {
|
||||||
|
var dbInfo ControllerInfo
|
||||||
|
q := s.conn.Model(&ControllerInfo{}).First(&dbInfo)
|
||||||
|
if q.Error != nil {
|
||||||
|
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(runnerErrors.ErrNotFound, "fetching controller info")
|
||||||
|
}
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(q.Error, "fetching controller info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := info.Validate(); err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "validating controller info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.MetadataURL != nil {
|
||||||
|
dbInfo.MetadataURL = *info.MetadataURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.CallbackURL != nil {
|
||||||
|
dbInfo.CallbackURL = *info.CallbackURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.WebhookURL != nil {
|
||||||
|
dbInfo.WebhookBaseURL = *info.WebhookURL
|
||||||
|
}
|
||||||
|
|
||||||
|
q = s.conn.Save(&dbInfo)
|
||||||
|
if q.Error != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(q.Error, "saving controller info")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramInfo, err := dbControllerToCommonController(dbInfo)
|
||||||
|
if err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "converting controller info")
|
||||||
|
}
|
||||||
|
return paramInfo, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,10 @@ type ControllerInfo struct {
|
||||||
Base
|
Base
|
||||||
|
|
||||||
ControllerID uuid.UUID
|
ControllerID uuid.UUID
|
||||||
|
|
||||||
|
CallbackURL string
|
||||||
|
MetadataURL string
|
||||||
|
WebhookBaseURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowJob struct {
|
type WorkflowJob struct {
|
||||||
|
|
|
||||||
|
|
@ -501,3 +501,34 @@ func (u UpdateGithubCredentialsParams) Validate() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateControllerParams struct {
|
||||||
|
MetadataURL *string `json:"metadata_url,omitempty"`
|
||||||
|
CallbackURL *string `json:"callback_url,omitempty"`
|
||||||
|
WebhookURL *string `json:"webhook_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UpdateControllerParams) Validate() error {
|
||||||
|
if u.MetadataURL != nil {
|
||||||
|
u, err := url.Parse(*u.MetadataURL)
|
||||||
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
|
return runnerErrors.NewBadRequestError("invalid metadata_url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.CallbackURL != nil {
|
||||||
|
u, err := url.Parse(*u.CallbackURL)
|
||||||
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
|
return runnerErrors.NewBadRequestError("invalid callback_url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.WebhookURL != nil {
|
||||||
|
u, err := url.Parse(*u.WebhookURL)
|
||||||
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
|
return runnerErrors.NewBadRequestError("invalid webhook_url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/juju/clock"
|
"github.com/juju/clock"
|
||||||
"github.com/juju/retry"
|
"github.com/juju/retry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -65,7 +64,6 @@ func NewRunner(ctx context.Context, cfg config.Config, db dbCommon.Store) (*Runn
|
||||||
}
|
}
|
||||||
|
|
||||||
poolManagerCtrl := &poolManagerCtrl{
|
poolManagerCtrl := &poolManagerCtrl{
|
||||||
controllerID: ctrlID.ControllerID.String(),
|
|
||||||
config: cfg,
|
config: cfg,
|
||||||
store: db,
|
store: db,
|
||||||
repositories: map[string]common.PoolManager{},
|
repositories: map[string]common.PoolManager{},
|
||||||
|
|
@ -78,7 +76,6 @@ func NewRunner(ctx context.Context, cfg config.Config, db dbCommon.Store) (*Runn
|
||||||
store: db,
|
store: db,
|
||||||
poolManagerCtrl: poolManagerCtrl,
|
poolManagerCtrl: poolManagerCtrl,
|
||||||
providers: providers,
|
providers: providers,
|
||||||
controllerID: ctrlID.ControllerID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runner.loadReposOrgsAndEnterprises(); err != nil {
|
if err := runner.loadReposOrgsAndEnterprises(); err != nil {
|
||||||
|
|
@ -91,9 +88,8 @@ func NewRunner(ctx context.Context, cfg config.Config, db dbCommon.Store) (*Runn
|
||||||
type poolManagerCtrl struct {
|
type poolManagerCtrl struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
|
||||||
controllerID string
|
config config.Config
|
||||||
config config.Config
|
store dbCommon.Store
|
||||||
store dbCommon.Store
|
|
||||||
|
|
||||||
repositories map[string]common.PoolManager
|
repositories map[string]common.PoolManager
|
||||||
organizations map[string]common.PoolManager
|
organizations map[string]common.PoolManager
|
||||||
|
|
@ -345,17 +341,17 @@ func (p *poolManagerCtrl) GetEnterprisePoolManagers() (map[string]common.PoolMan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *poolManagerCtrl) getInternalConfig(_ context.Context, creds params.GithubCredentials, poolBalancerType params.PoolBalancerType) (params.Internal, error) {
|
func (p *poolManagerCtrl) getInternalConfig(_ context.Context, creds params.GithubCredentials, poolBalancerType params.PoolBalancerType) (params.Internal, error) {
|
||||||
var controllerWebhookURL string
|
controllerInfo, err := p.store.ControllerInfo()
|
||||||
if p.config.Default.WebhookURL != "" {
|
if err != nil {
|
||||||
controllerWebhookURL = fmt.Sprintf("%s/%s", p.config.Default.WebhookURL, p.controllerID)
|
return params.Internal{}, errors.Wrap(err, "fetching controller info")
|
||||||
}
|
}
|
||||||
|
|
||||||
return params.Internal{
|
return params.Internal{
|
||||||
ControllerID: p.controllerID,
|
ControllerID: controllerInfo.ControllerID.String(),
|
||||||
InstanceCallbackURL: p.config.Default.CallbackURL,
|
InstanceCallbackURL: controllerInfo.CallbackURL,
|
||||||
InstanceMetadataURL: p.config.Default.MetadataURL,
|
InstanceMetadataURL: controllerInfo.MetadataURL,
|
||||||
BaseWebhookURL: p.config.Default.WebhookURL,
|
BaseWebhookURL: controllerInfo.WebhookURL,
|
||||||
ControllerWebhookURL: controllerWebhookURL,
|
ControllerWebhookURL: controllerInfo.ControllerWebhookURL,
|
||||||
JWTSecret: p.config.JWTAuth.Secret,
|
JWTSecret: p.config.JWTAuth.Secret,
|
||||||
PoolBalancerType: poolBalancerType,
|
PoolBalancerType: poolBalancerType,
|
||||||
GithubCredentialsDetails: creds,
|
GithubCredentialsDetails: creds,
|
||||||
|
|
@ -372,9 +368,23 @@ type Runner struct {
|
||||||
poolManagerCtrl PoolManagerController
|
poolManagerCtrl PoolManagerController
|
||||||
|
|
||||||
providers map[string]common.Provider
|
providers map[string]common.Provider
|
||||||
|
}
|
||||||
|
|
||||||
controllerInfo params.ControllerInfo
|
// UpdateController will update the controller settings.
|
||||||
controllerID uuid.UUID
|
func (r *Runner) UpdateController(ctx context.Context, param params.UpdateControllerParams) (params.ControllerInfo, error) {
|
||||||
|
if !auth.IsAdmin(ctx) {
|
||||||
|
return params.ControllerInfo{}, runnerErrors.ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := param.Validate(); err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "validating controller update params")
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := r.store.UpdateController(param)
|
||||||
|
if err != nil {
|
||||||
|
return params.ControllerInfo{}, errors.Wrap(err, "updating controller info")
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetControllerInfo returns the controller id and the hostname.
|
// GetControllerInfo returns the controller id and the hostname.
|
||||||
|
|
@ -408,19 +418,18 @@ func (r *Runner) GetControllerInfo(ctx context.Context) (params.ControllerInfo,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return params.ControllerInfo{}, errors.Wrap(err, "fetching hostname")
|
return params.ControllerInfo{}, errors.Wrap(err, "fetching hostname")
|
||||||
}
|
}
|
||||||
r.controllerInfo.Hostname = hostname
|
|
||||||
var controllerWebhook string
|
info, err := r.store.ControllerInfo()
|
||||||
if r.controllerID != uuid.Nil && r.config.Default.WebhookURL != "" {
|
if err != nil {
|
||||||
controllerWebhook = fmt.Sprintf("%s/%s", r.config.Default.WebhookURL, r.controllerID.String())
|
return params.ControllerInfo{}, errors.Wrap(err, "fetching controller info")
|
||||||
}
|
}
|
||||||
return params.ControllerInfo{
|
|
||||||
ControllerID: r.controllerID,
|
// This is temporary. Right now, GARM is a single-instance deployment. When we add the
|
||||||
Hostname: hostname,
|
// ability to scale out, the hostname field will be moved form here to a dedicated node
|
||||||
MetadataURL: r.config.Default.MetadataURL,
|
// object. As a single controller will be made up of multiple nodes, we will need to model
|
||||||
CallbackURL: r.config.Default.CallbackURL,
|
// that aspect of GARM.
|
||||||
WebhookURL: r.config.Default.WebhookURL,
|
info.Hostname = hostname
|
||||||
ControllerWebhookURL: controllerWebhook,
|
return info, nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) {
|
func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) {
|
||||||
|
|
|
||||||
45
testdata/config.toml
vendored
45
testdata/config.toml
vendored
|
|
@ -249,48 +249,3 @@ disable_jit_config = false
|
||||||
# anything (bash, a binary, python, etc). See documentation in this repo on how to write an
|
# anything (bash, a binary, python, etc). See documentation in this repo on how to write an
|
||||||
# external provider.
|
# external provider.
|
||||||
provider_executable = "/etc/garm/providers.d/azure/garm-external-provider"
|
provider_executable = "/etc/garm/providers.d/azure/garm-external-provider"
|
||||||
|
|
||||||
# This is a list of credentials that you can define as part of the repository
|
|
||||||
# or organization definitions. They are not saved inside the database, as there
|
|
||||||
# is no Vault integration (yet). This will change in the future.
|
|
||||||
# Credentials defined here can be listed using the API. Obviously, only the name
|
|
||||||
# and descriptions are returned.
|
|
||||||
[[github]]
|
|
||||||
name = "gabriel"
|
|
||||||
description = "github token or user gabriel"
|
|
||||||
# This is the type of authentication to use. It can be "pat" or "app"
|
|
||||||
auth_type = "pat"
|
|
||||||
[github.pat]
|
|
||||||
# This is a personal token with access to the repositories and organizations
|
|
||||||
# you plan on adding to garm. The "workflow" option needs to be selected in order
|
|
||||||
# to work with repositories, and the admin:org needs to be set if you plan on
|
|
||||||
# adding an organization.
|
|
||||||
oauth2_token = "super secret token"
|
|
||||||
[github.app]
|
|
||||||
# This is the app_id of the GitHub App that you want to use to authenticate
|
|
||||||
# with the GitHub API.
|
|
||||||
# This needs to be changed
|
|
||||||
app_id = 1
|
|
||||||
# This is the private key path of the GitHub App that you want to use to authenticate
|
|
||||||
# with the GitHub API.
|
|
||||||
# This needs to be changed
|
|
||||||
private_key_path = "/etc/garm/yourAppName.2024-03-01.private-key.pem"
|
|
||||||
# This is the installation_id of the GitHub App that you want to use to authenticate
|
|
||||||
# with the GitHub API.
|
|
||||||
# This needs to be changed
|
|
||||||
installation_id = 99
|
|
||||||
# base_url (optional) is the URL at which your GitHub Enterprise Server can be accessed.
|
|
||||||
# If these credentials are for github.com, leave this setting blank
|
|
||||||
base_url = "https://ghe.example.com"
|
|
||||||
# api_base_url (optional) is the base URL where the GitHub Enterprise Server API can be accessed.
|
|
||||||
# Leave this blank if these credentials are for github.com.
|
|
||||||
api_base_url = "https://ghe.example.com"
|
|
||||||
# upload_base_url (optional) is the base URL where the GitHub Enterprise Server upload API can be accessed.
|
|
||||||
# Leave this blank if these credentials are for github.com, or if you don't have a separate URL
|
|
||||||
# for the upload API.
|
|
||||||
upload_base_url = "https://api.ghe.example.com"
|
|
||||||
# ca_cert_bundle (optional) is the CA certificate bundle in PEM format that will be used by the github
|
|
||||||
# client to talk to the API. This bundle will also be sent to all runners as bootstrap params.
|
|
||||||
# Use this option if you're using a self signed certificate.
|
|
||||||
# Leave this blank if you're using github.com or if your certificate is signed by a valid CA.
|
|
||||||
ca_cert_bundle = "/etc/garm/ghe.crt"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue