diff --git a/apiserver/controllers/controllers.go b/apiserver/controllers/controllers.go index 2c1fd42a..70a56bd5 100644 --- a/apiserver/controllers/controllers.go +++ b/apiserver/controllers/controllers.go @@ -377,3 +377,24 @@ func (a *APIController) ListAllJobs(w http.ResponseWriter, r *http.Request) { log.Printf("failed to encode response: %q", err) } } + +// swagger:route GET /controller-info controllerInfo ControllerInfo +// +// Get controller info. +// +// Responses: +// 200: ControllerInfo +// 409: APIErrorResponse +func (a *APIController) ControllerInfoHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + info, err := a.r.GetControllerInfo(ctx) + if err != nil { + handleError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(info); err != nil { + log.Printf("failed to encode response: %q", err) + } +} diff --git a/apiserver/routers/routers.go b/apiserver/routers/routers.go index 55e42684..f14ffef4 100644 --- a/apiserver/routers/routers.go +++ b/apiserver/routers/routers.go @@ -285,6 +285,10 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl 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") + // Websocket log writer apiRouter.Handle("/{ws:ws\\/?}", http.HandlerFunc(han.WSHandler)).Methods("GET") return router diff --git a/apiserver/swagger-models.yaml b/apiserver/swagger-models.yaml index 417f178a..a2ad3474 100644 --- a/apiserver/swagger-models.yaml +++ b/apiserver/swagger-models.yaml @@ -8,6 +8,13 @@ definitions: import: package: github.com/cloudbase/garm/params alias: garm_params + ControllerInfo: + type: object + x-go-type: + type: ControllerInfo + import: + package: github.com/cloudbase/garm/params + alias: garm_params NewUserParams: type: object x-go-type: diff --git a/apiserver/swagger.yaml b/apiserver/swagger.yaml index 0da7dddf..5851740c 100644 --- a/apiserver/swagger.yaml +++ b/apiserver/swagger.yaml @@ -9,6 +9,13 @@ definitions: alias: apiserver_params package: github.com/cloudbase/garm/apiserver/params type: APIErrorResponse + ControllerInfo: + type: object + x-go-type: + import: + alias: garm_params + package: github.com/cloudbase/garm/params + type: ControllerInfo CreateEnterpriseParams: type: object x-go-type: @@ -239,6 +246,21 @@ paths: summary: Logs in a user and returns a JWT token. tags: - login + /controller-info: + get: + operationId: ControllerInfo + responses: + "200": + description: ControllerInfo + schema: + $ref: '#/definitions/ControllerInfo' + "409": + description: APIErrorResponse + schema: + $ref: '#/definitions/APIErrorResponse' + summary: Get controller info. + tags: + - controllerInfo /credentials: get: operationId: ListCredentials diff --git a/client/controller_info/controller_info_client.go b/client/controller_info/controller_info_client.go new file mode 100644 index 00000000..0c9f807b --- /dev/null +++ b/client/controller_info/controller_info_client.go @@ -0,0 +1,80 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package controller_info + +// 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" + "github.com/go-openapi/strfmt" +) + +// New creates a new controller info API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +/* +Client for controller info API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption is the option for Client methods +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + ControllerInfo(params *ControllerInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ControllerInfoOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +ControllerInfo gets controller info +*/ +func (a *Client) ControllerInfo(params *ControllerInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ControllerInfoOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewControllerInfoParams() + } + op := &runtime.ClientOperation{ + ID: "ControllerInfo", + Method: "GET", + PathPattern: "/controller-info", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &ControllerInfoReader{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.(*ControllerInfoOK) + 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 ControllerInfo: 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 +} diff --git a/client/controller_info/controller_info_parameters.go b/client/controller_info/controller_info_parameters.go new file mode 100644 index 00000000..f4d33ef6 --- /dev/null +++ b/client/controller_info/controller_info_parameters.go @@ -0,0 +1,128 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package controller_info + +// 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" +) + +// NewControllerInfoParams creates a new ControllerInfoParams 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 NewControllerInfoParams() *ControllerInfoParams { + return &ControllerInfoParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewControllerInfoParamsWithTimeout creates a new ControllerInfoParams object +// with the ability to set a timeout on a request. +func NewControllerInfoParamsWithTimeout(timeout time.Duration) *ControllerInfoParams { + return &ControllerInfoParams{ + timeout: timeout, + } +} + +// NewControllerInfoParamsWithContext creates a new ControllerInfoParams object +// with the ability to set a context for a request. +func NewControllerInfoParamsWithContext(ctx context.Context) *ControllerInfoParams { + return &ControllerInfoParams{ + Context: ctx, + } +} + +// NewControllerInfoParamsWithHTTPClient creates a new ControllerInfoParams object +// with the ability to set a custom HTTPClient for a request. +func NewControllerInfoParamsWithHTTPClient(client *http.Client) *ControllerInfoParams { + return &ControllerInfoParams{ + HTTPClient: client, + } +} + +/* +ControllerInfoParams contains all the parameters to send to the API endpoint + + for the controller info operation. + + Typically these are written to a http.Request. +*/ +type ControllerInfoParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the controller info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ControllerInfoParams) WithDefaults() *ControllerInfoParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the controller info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ControllerInfoParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the controller info params +func (o *ControllerInfoParams) WithTimeout(timeout time.Duration) *ControllerInfoParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the controller info params +func (o *ControllerInfoParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the controller info params +func (o *ControllerInfoParams) WithContext(ctx context.Context) *ControllerInfoParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the controller info params +func (o *ControllerInfoParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the controller info params +func (o *ControllerInfoParams) WithHTTPClient(client *http.Client) *ControllerInfoParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the controller info params +func (o *ControllerInfoParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *ControllerInfoParams) 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 +} diff --git a/client/controller_info/controller_info_responses.go b/client/controller_info/controller_info_responses.go new file mode 100644 index 00000000..3f018d00 --- /dev/null +++ b/client/controller_info/controller_info_responses.go @@ -0,0 +1,174 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package controller_info + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "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" +) + +// ControllerInfoReader is a Reader for the ControllerInfo structure. +type ControllerInfoReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ControllerInfoReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewControllerInfoOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 409: + result := NewControllerInfoConflict() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("[GET /controller-info] ControllerInfo", response, response.Code()) + } +} + +// NewControllerInfoOK creates a ControllerInfoOK with default headers values +func NewControllerInfoOK() *ControllerInfoOK { + return &ControllerInfoOK{} +} + +/* +ControllerInfoOK describes a response with status code 200, with default header values. + +ControllerInfo +*/ +type ControllerInfoOK struct { + Payload garm_params.ControllerInfo +} + +// IsSuccess returns true when this controller info o k response has a 2xx status code +func (o *ControllerInfoOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this controller info o k response has a 3xx status code +func (o *ControllerInfoOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this controller info o k response has a 4xx status code +func (o *ControllerInfoOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this controller info o k response has a 5xx status code +func (o *ControllerInfoOK) IsServerError() bool { + return false +} + +// IsCode returns true when this controller info o k response a status code equal to that given +func (o *ControllerInfoOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the controller info o k response +func (o *ControllerInfoOK) Code() int { + return 200 +} + +func (o *ControllerInfoOK) Error() string { + return fmt.Sprintf("[GET /controller-info][%d] controllerInfoOK %+v", 200, o.Payload) +} + +func (o *ControllerInfoOK) String() string { + return fmt.Sprintf("[GET /controller-info][%d] controllerInfoOK %+v", 200, o.Payload) +} + +func (o *ControllerInfoOK) GetPayload() garm_params.ControllerInfo { + return o.Payload +} + +func (o *ControllerInfoOK) 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 +} + +// NewControllerInfoConflict creates a ControllerInfoConflict with default headers values +func NewControllerInfoConflict() *ControllerInfoConflict { + return &ControllerInfoConflict{} +} + +/* +ControllerInfoConflict describes a response with status code 409, with default header values. + +APIErrorResponse +*/ +type ControllerInfoConflict struct { + Payload apiserver_params.APIErrorResponse +} + +// IsSuccess returns true when this controller info conflict response has a 2xx status code +func (o *ControllerInfoConflict) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this controller info conflict response has a 3xx status code +func (o *ControllerInfoConflict) IsRedirect() bool { + return false +} + +// IsClientError returns true when this controller info conflict response has a 4xx status code +func (o *ControllerInfoConflict) IsClientError() bool { + return true +} + +// IsServerError returns true when this controller info conflict response has a 5xx status code +func (o *ControllerInfoConflict) IsServerError() bool { + return false +} + +// IsCode returns true when this controller info conflict response a status code equal to that given +func (o *ControllerInfoConflict) IsCode(code int) bool { + return code == 409 +} + +// Code gets the status code for the controller info conflict response +func (o *ControllerInfoConflict) Code() int { + return 409 +} + +func (o *ControllerInfoConflict) Error() string { + return fmt.Sprintf("[GET /controller-info][%d] controllerInfoConflict %+v", 409, o.Payload) +} + +func (o *ControllerInfoConflict) String() string { + return fmt.Sprintf("[GET /controller-info][%d] controllerInfoConflict %+v", 409, o.Payload) +} + +func (o *ControllerInfoConflict) GetPayload() apiserver_params.APIErrorResponse { + return o.Payload +} + +func (o *ControllerInfoConflict) 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 +} diff --git a/client/garm_api_client.go b/client/garm_api_client.go index 0f9a208a..143953b4 100644 --- a/client/garm_api_client.go +++ b/client/garm_api_client.go @@ -10,6 +10,7 @@ import ( httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" + "github.com/cloudbase/garm/client/controller_info" "github.com/cloudbase/garm/client/credentials" "github.com/cloudbase/garm/client/enterprises" "github.com/cloudbase/garm/client/first_run" @@ -65,6 +66,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *GarmAPI { cli := new(GarmAPI) cli.Transport = transport + cli.ControllerInfo = controller_info.New(transport, formats) cli.Credentials = credentials.New(transport, formats) cli.Enterprises = enterprises.New(transport, formats) cli.FirstRun = first_run.New(transport, formats) @@ -120,6 +122,8 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { // GarmAPI is a client for garm API type GarmAPI struct { + ControllerInfo controller_info.ClientService + Credentials credentials.ClientService Enterprises enterprises.ClientService @@ -148,6 +152,7 @@ type GarmAPI struct { // SetTransport changes the transport on the client and all its subresources func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) { c.Transport = transport + c.ControllerInfo.SetTransport(transport) c.Credentials.SetTransport(transport) c.Enterprises.SetTransport(transport) c.FirstRun.SetTransport(transport) diff --git a/cmd/garm-cli/cmd/controller_info.go b/cmd/garm-cli/cmd/controller_info.go new file mode 100644 index 00000000..038fe8fe --- /dev/null +++ b/cmd/garm-cli/cmd/controller_info.go @@ -0,0 +1,73 @@ +// 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" + + apiClientControllerInfo "github.com/cloudbase/garm/client/controller_info" + "github.com/cloudbase/garm/params" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" +) + +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(cmd *cobra.Command, args []string) error { + if needsInit { + return errNeedsInitError + } + + showInfo := apiClientControllerInfo.NewControllerInfoParams() + response, err := apiCli.ControllerInfo.ControllerInfo(showInfo, authToken) + if err != nil { + return err + } + formatInfo(response.Payload) + return nil + }, +} + +func formatInfo(info params.ControllerInfo) { + t := table.NewWriter() + + header := table.Row{"Field", "Value"} + + 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}) + fmt.Println(t.Render()) +} + +func init() { + infoCmd.AddCommand( + infoShowCmd, + ) + + rootCmd.AddCommand(infoCmd) +} diff --git a/params/params.go b/params/params.go index 83aa2abc..2f0f7371 100644 --- a/params/params.go +++ b/params/params.go @@ -381,6 +381,8 @@ type JWTResponse struct { type ControllerInfo struct { ControllerID uuid.UUID `json:"controller_id"` Hostname string `json:"hostname"` + MetadataURL string `json:"metadata_url"` + CallbackURL string `json:"callback_url"` } type GithubCredentials struct { diff --git a/runner/runner.go b/runner/runner.go index b8120adc..9909ad58 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -379,6 +379,8 @@ func (r *Runner) GetControllerInfo(ctx context.Context) (params.ControllerInfo, return params.ControllerInfo{ ControllerID: r.controllerID, Hostname: hostname, + MetadataURL: r.config.Default.MetadataURL, + CallbackURL: r.config.Default.CallbackURL, }, nil }