diff --git a/apiserver/controllers/organizations.go b/apiserver/controllers/organizations.go index a06ef9c5..10cacba3 100644 --- a/apiserver/controllers/organizations.go +++ b/apiserver/controllers/organizations.go @@ -43,21 +43,21 @@ import ( func (a *APIController) CreateOrgHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var repoData runnerParams.CreateOrgParams - if err := json.NewDecoder(r.Body).Decode(&repoData); err != nil { + var orgData runnerParams.CreateOrgParams + if err := json.NewDecoder(r.Body).Decode(&orgData); err != nil { handleError(w, gErrors.ErrBadRequest) return } - repo, err := a.r.CreateOrganization(ctx, repoData) + org, err := a.r.CreateOrganization(ctx, orgData) if err != nil { - log.Printf("error creating repository: %+v", err) + log.Printf("error creating organization: %+v", err) handleError(w, err) return } w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(repo); err != nil { + if err := json.NewEncoder(w).Encode(org); err != nil { log.Printf("failed to encode response: %q", err) } } @@ -344,9 +344,9 @@ func (a *APIController) ListOrgPoolsHandler(w http.ResponseWriter, r *http.Reque func (a *APIController) GetOrgPoolHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) - orgID, repoOk := vars["orgID"] + orgID, orgOk := vars["orgID"] poolID, poolOk := vars["poolID"] - if !repoOk || !poolOk { + if !orgOk || !poolOk { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(params.APIErrorResponse{ Error: "Bad Request", @@ -499,7 +499,7 @@ func (a *APIController) UpdateOrgPoolHandler(w http.ResponseWriter, r *http.Requ // required: true // // Responses: -// 200: +// 200: HookInfo // default: APIErrorResponse func (a *APIController) InstallOrgWebhookHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -524,14 +524,17 @@ func (a *APIController) InstallOrgWebhookHandler(w http.ResponseWriter, r *http. return } - if err := a.r.InstallOrgWebhook(ctx, orgID, hookParam); err != nil { + info, err := a.r.InstallOrgWebhook(ctx, orgID, hookParam) + if err != nil { log.Printf("installing webhook: %s", err) handleError(w, err) return } w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(info); err != nil { + log.Printf("failed to encode response: %q", err) + } } // swagger:route DELETE /organizations/{orgID}/webhook organizations hooks UninstallOrgWebhook @@ -572,3 +575,46 @@ func (a *APIController) UninstallOrgWebhookHandler(w http.ResponseWriter, r *htt w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } + +// swagger:route GET /organizations/{orgID}/webhook organizations hooks GetOrgWebhookInfo +// +// Get information about the GARM installed webhook on an organization. +// +// Parameters: +// + name: orgID +// description: Organization ID. +// type: string +// in: path +// required: true +// +// Responses: +// 200: HookInfo +// default: APIErrorResponse +func (a *APIController) GetOrgWebhookInfoHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + orgID, orgOk := vars["orgID"] + if !orgOk { + w.WriteHeader(http.StatusBadRequest) + if err := json.NewEncoder(w).Encode(params.APIErrorResponse{ + Error: "Bad Request", + Details: "No org ID specified", + }); err != nil { + log.Printf("failed to encode response: %q", err) + } + return + } + + info, err := a.r.GetOrgWebhookInfo(ctx, orgID) + if err != nil { + log.Printf("getting webhook info: %s", err) + 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/controllers/repositories.go b/apiserver/controllers/repositories.go index 16845b47..615520c1 100644 --- a/apiserver/controllers/repositories.go +++ b/apiserver/controllers/repositories.go @@ -499,7 +499,7 @@ func (a *APIController) UpdateRepoPoolHandler(w http.ResponseWriter, r *http.Req // required: true // // Responses: -// 200: +// 200: HookInfo // default: APIErrorResponse func (a *APIController) InstallRepoWebhookHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -524,14 +524,17 @@ func (a *APIController) InstallRepoWebhookHandler(w http.ResponseWriter, r *http return } - if err := a.r.InstallRepoWebhook(ctx, repoID, hookParam); err != nil { + info, err := a.r.InstallRepoWebhook(ctx, repoID, hookParam) + if err != nil { log.Printf("installing webhook: %s", err) handleError(w, err) return } w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(info); err != nil { + log.Printf("failed to encode response: %q", err) + } } // swagger:route DELETE /repositories/{repoID}/webhook repositories hooks UninstallRepoWebhook @@ -572,3 +575,46 @@ func (a *APIController) UninstallRepoWebhookHandler(w http.ResponseWriter, r *ht w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } + +// swagger:route GET /repositories/{repoID}/webhook repositories hooks GetRepoWebhookInfo +// +// Get information about the GARM installed webhook on a repository. +// +// Parameters: +// + name: repoID +// description: Repository ID. +// type: string +// in: path +// required: true +// +// Responses: +// 200: HookInfo +// default: APIErrorResponse +func (a *APIController) GetRepoWebhookInfoHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + repoID, orgOk := vars["repoID"] + if !orgOk { + w.WriteHeader(http.StatusBadRequest) + if err := json.NewEncoder(w).Encode(params.APIErrorResponse{ + Error: "Bad Request", + Details: "No repository ID specified", + }); err != nil { + log.Printf("failed to encode response: %q", err) + } + return + } + + info, err := a.r.GetRepoWebhookInfo(ctx, repoID) + if err != nil { + log.Printf("getting webhook info: %s", err) + 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 5cf782a9..ef342f4a 100644 --- a/apiserver/routers/routers.go +++ b/apiserver/routers/routers.go @@ -210,6 +210,9 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl // Uninstall Webhook apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") + // Get webhook info + apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") + apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") ///////////////////////////// // Organizations and pools // @@ -256,6 +259,9 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl // Uninstall Webhook apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") + // Get webhook info + apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") + apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") ///////////////////////////// // Enterprises and pools // diff --git a/apiserver/swagger-models.yaml b/apiserver/swagger-models.yaml index 333bdc70..fa8181f7 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 + HookInfo: + type: object + x-go-type: + type: HookInfo + import: + package: github.com/cloudbase/garm/params + alias: garm_params ControllerInfo: type: object x-go-type: diff --git a/apiserver/swagger.yaml b/apiserver/swagger.yaml index c22562d1..dba80a58 100644 --- a/apiserver/swagger.yaml +++ b/apiserver/swagger.yaml @@ -76,6 +76,13 @@ definitions: alias: garm_params package: github.com/cloudbase/garm/params type: GithubCredentials + HookInfo: + type: object + x-go-type: + import: + alias: garm_params + package: github.com/cloudbase/garm/params + type: HookInfo InstallWebhookParams: type: object x-go-type: @@ -925,6 +932,27 @@ paths: tags: - organizations - hooks + get: + operationId: GetOrgWebhookInfo + parameters: + - description: Organization ID. + in: path + name: orgID + required: true + type: string + responses: + "200": + description: HookInfo + schema: + $ref: '#/definitions/HookInfo' + default: + description: APIErrorResponse + schema: + $ref: '#/definitions/APIErrorResponse' + summary: Get information about the GARM installed webhook on an organization. + tags: + - organizations + - hooks post: description: |- Install the GARM webhook for an organization. The secret configured on the organization will @@ -946,7 +974,9 @@ paths: type: object responses: "200": - description: "" + description: HookInfo + schema: + $ref: '#/definitions/HookInfo' default: description: APIErrorResponse schema: @@ -1347,6 +1377,27 @@ paths: tags: - repositories - hooks + get: + operationId: GetRepoWebhookInfo + parameters: + - description: Repository ID. + in: path + name: repoID + required: true + type: string + responses: + "200": + description: HookInfo + schema: + $ref: '#/definitions/HookInfo' + default: + description: APIErrorResponse + schema: + $ref: '#/definitions/APIErrorResponse' + summary: Get information about the GARM installed webhook on a repository. + tags: + - repositories + - hooks post: description: |- Install the GARM webhook for an organization. The secret configured on the organization will @@ -1368,7 +1419,9 @@ paths: type: object responses: "200": - description: "" + description: HookInfo + schema: + $ref: '#/definitions/HookInfo' default: description: APIErrorResponse schema: diff --git a/client/organizations/get_org_webhook_info_parameters.go b/client/organizations/get_org_webhook_info_parameters.go new file mode 100644 index 00000000..fe67c584 --- /dev/null +++ b/client/organizations/get_org_webhook_info_parameters.go @@ -0,0 +1,151 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package organizations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetOrgWebhookInfoParams creates a new GetOrgWebhookInfoParams 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 NewGetOrgWebhookInfoParams() *GetOrgWebhookInfoParams { + return &GetOrgWebhookInfoParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetOrgWebhookInfoParamsWithTimeout creates a new GetOrgWebhookInfoParams object +// with the ability to set a timeout on a request. +func NewGetOrgWebhookInfoParamsWithTimeout(timeout time.Duration) *GetOrgWebhookInfoParams { + return &GetOrgWebhookInfoParams{ + timeout: timeout, + } +} + +// NewGetOrgWebhookInfoParamsWithContext creates a new GetOrgWebhookInfoParams object +// with the ability to set a context for a request. +func NewGetOrgWebhookInfoParamsWithContext(ctx context.Context) *GetOrgWebhookInfoParams { + return &GetOrgWebhookInfoParams{ + Context: ctx, + } +} + +// NewGetOrgWebhookInfoParamsWithHTTPClient creates a new GetOrgWebhookInfoParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetOrgWebhookInfoParamsWithHTTPClient(client *http.Client) *GetOrgWebhookInfoParams { + return &GetOrgWebhookInfoParams{ + HTTPClient: client, + } +} + +/* +GetOrgWebhookInfoParams contains all the parameters to send to the API endpoint + + for the get org webhook info operation. + + Typically these are written to a http.Request. +*/ +type GetOrgWebhookInfoParams struct { + + /* OrgID. + + Organization ID. + */ + OrgID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get org webhook info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetOrgWebhookInfoParams) WithDefaults() *GetOrgWebhookInfoParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get org webhook info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetOrgWebhookInfoParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get org webhook info params +func (o *GetOrgWebhookInfoParams) WithTimeout(timeout time.Duration) *GetOrgWebhookInfoParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get org webhook info params +func (o *GetOrgWebhookInfoParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get org webhook info params +func (o *GetOrgWebhookInfoParams) WithContext(ctx context.Context) *GetOrgWebhookInfoParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get org webhook info params +func (o *GetOrgWebhookInfoParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get org webhook info params +func (o *GetOrgWebhookInfoParams) WithHTTPClient(client *http.Client) *GetOrgWebhookInfoParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get org webhook info params +func (o *GetOrgWebhookInfoParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithOrgID adds the orgID to the get org webhook info params +func (o *GetOrgWebhookInfoParams) WithOrgID(orgID string) *GetOrgWebhookInfoParams { + o.SetOrgID(orgID) + return o +} + +// SetOrgID adds the orgId to the get org webhook info params +func (o *GetOrgWebhookInfoParams) SetOrgID(orgID string) { + o.OrgID = orgID +} + +// WriteToRequest writes these params to a swagger request +func (o *GetOrgWebhookInfoParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param orgID + if err := r.SetPathParam("orgID", o.OrgID); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/client/organizations/get_org_webhook_info_responses.go b/client/organizations/get_org_webhook_info_responses.go new file mode 100644 index 00000000..7de76956 --- /dev/null +++ b/client/organizations/get_org_webhook_info_responses.go @@ -0,0 +1,179 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package organizations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "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" +) + +// GetOrgWebhookInfoReader is a Reader for the GetOrgWebhookInfo structure. +type GetOrgWebhookInfoReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetOrgWebhookInfoReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetOrgWebhookInfoOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetOrgWebhookInfoDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetOrgWebhookInfoOK creates a GetOrgWebhookInfoOK with default headers values +func NewGetOrgWebhookInfoOK() *GetOrgWebhookInfoOK { + return &GetOrgWebhookInfoOK{} +} + +/* +GetOrgWebhookInfoOK describes a response with status code 200, with default header values. + +HookInfo +*/ +type GetOrgWebhookInfoOK struct { + Payload garm_params.HookInfo +} + +// IsSuccess returns true when this get org webhook info o k response has a 2xx status code +func (o *GetOrgWebhookInfoOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get org webhook info o k response has a 3xx status code +func (o *GetOrgWebhookInfoOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get org webhook info o k response has a 4xx status code +func (o *GetOrgWebhookInfoOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get org webhook info o k response has a 5xx status code +func (o *GetOrgWebhookInfoOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get org webhook info o k response a status code equal to that given +func (o *GetOrgWebhookInfoOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get org webhook info o k response +func (o *GetOrgWebhookInfoOK) Code() int { + return 200 +} + +func (o *GetOrgWebhookInfoOK) Error() string { + return fmt.Sprintf("[GET /organizations/{orgID}/webhook][%d] getOrgWebhookInfoOK %+v", 200, o.Payload) +} + +func (o *GetOrgWebhookInfoOK) String() string { + return fmt.Sprintf("[GET /organizations/{orgID}/webhook][%d] getOrgWebhookInfoOK %+v", 200, o.Payload) +} + +func (o *GetOrgWebhookInfoOK) GetPayload() garm_params.HookInfo { + return o.Payload +} + +func (o *GetOrgWebhookInfoOK) 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 +} + +// NewGetOrgWebhookInfoDefault creates a GetOrgWebhookInfoDefault with default headers values +func NewGetOrgWebhookInfoDefault(code int) *GetOrgWebhookInfoDefault { + return &GetOrgWebhookInfoDefault{ + _statusCode: code, + } +} + +/* +GetOrgWebhookInfoDefault describes a response with status code -1, with default header values. + +APIErrorResponse +*/ +type GetOrgWebhookInfoDefault struct { + _statusCode int + + Payload apiserver_params.APIErrorResponse +} + +// IsSuccess returns true when this get org webhook info default response has a 2xx status code +func (o *GetOrgWebhookInfoDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this get org webhook info default response has a 3xx status code +func (o *GetOrgWebhookInfoDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this get org webhook info default response has a 4xx status code +func (o *GetOrgWebhookInfoDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this get org webhook info default response has a 5xx status code +func (o *GetOrgWebhookInfoDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this get org webhook info default response a status code equal to that given +func (o *GetOrgWebhookInfoDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the get org webhook info default response +func (o *GetOrgWebhookInfoDefault) Code() int { + return o._statusCode +} + +func (o *GetOrgWebhookInfoDefault) Error() string { + return fmt.Sprintf("[GET /organizations/{orgID}/webhook][%d] GetOrgWebhookInfo default %+v", o._statusCode, o.Payload) +} + +func (o *GetOrgWebhookInfoDefault) String() string { + return fmt.Sprintf("[GET /organizations/{orgID}/webhook][%d] GetOrgWebhookInfo default %+v", o._statusCode, o.Payload) +} + +func (o *GetOrgWebhookInfoDefault) GetPayload() apiserver_params.APIErrorResponse { + return o.Payload +} + +func (o *GetOrgWebhookInfoDefault) 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/organizations/install_org_webhook_responses.go b/client/organizations/install_org_webhook_responses.go index 9bb50cfd..6721ec2f 100644 --- a/client/organizations/install_org_webhook_responses.go +++ b/client/organizations/install_org_webhook_responses.go @@ -13,6 +13,7 @@ import ( "github.com/go-openapi/strfmt" apiserver_params "github.com/cloudbase/garm/apiserver/params" + garm_params "github.com/cloudbase/garm/params" ) // InstallOrgWebhookReader is a Reader for the InstallOrgWebhook structure. @@ -49,9 +50,10 @@ func NewInstallOrgWebhookOK() *InstallOrgWebhookOK { /* InstallOrgWebhookOK describes a response with status code 200, with default header values. -InstallOrgWebhookOK install org webhook o k +HookInfo */ type InstallOrgWebhookOK struct { + Payload garm_params.HookInfo } // IsSuccess returns true when this install org webhook o k response has a 2xx status code @@ -85,15 +87,24 @@ func (o *InstallOrgWebhookOK) Code() int { } func (o *InstallOrgWebhookOK) Error() string { - return fmt.Sprintf("[POST /organizations/{orgID}/webhook][%d] installOrgWebhookOK ", 200) + return fmt.Sprintf("[POST /organizations/{orgID}/webhook][%d] installOrgWebhookOK %+v", 200, o.Payload) } func (o *InstallOrgWebhookOK) String() string { - return fmt.Sprintf("[POST /organizations/{orgID}/webhook][%d] installOrgWebhookOK ", 200) + return fmt.Sprintf("[POST /organizations/{orgID}/webhook][%d] installOrgWebhookOK %+v", 200, o.Payload) +} + +func (o *InstallOrgWebhookOK) GetPayload() garm_params.HookInfo { + return o.Payload } func (o *InstallOrgWebhookOK) 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/organizations/organizations_client.go b/client/organizations/organizations_client.go index 3e20c777..ee00493f 100644 --- a/client/organizations/organizations_client.go +++ b/client/organizations/organizations_client.go @@ -40,6 +40,8 @@ type ClientService interface { GetOrgPool(params *GetOrgPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetOrgPoolOK, error) + GetOrgWebhookInfo(params *GetOrgWebhookInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetOrgWebhookInfoOK, error) + InstallOrgWebhook(params *InstallOrgWebhookParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*InstallOrgWebhookOK, error) ListOrgInstances(params *ListOrgInstancesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListOrgInstancesOK, error) @@ -273,6 +275,44 @@ func (a *Client) GetOrgPool(params *GetOrgPoolParams, authInfo runtime.ClientAut return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +GetOrgWebhookInfo gets information about the g a r m installed webhook on an organization +*/ +func (a *Client) GetOrgWebhookInfo(params *GetOrgWebhookInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetOrgWebhookInfoOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetOrgWebhookInfoParams() + } + op := &runtime.ClientOperation{ + ID: "GetOrgWebhookInfo", + Method: "GET", + PathPattern: "/organizations/{orgID}/webhook", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetOrgWebhookInfoReader{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.(*GetOrgWebhookInfoOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*GetOrgWebhookInfoDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* InstallOrgWebhook Install the GARM webhook for an organization. The secret configured on the organization will diff --git a/client/repositories/get_repo_webhook_info_parameters.go b/client/repositories/get_repo_webhook_info_parameters.go new file mode 100644 index 00000000..b4c9e515 --- /dev/null +++ b/client/repositories/get_repo_webhook_info_parameters.go @@ -0,0 +1,151 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package repositories + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetRepoWebhookInfoParams creates a new GetRepoWebhookInfoParams 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 NewGetRepoWebhookInfoParams() *GetRepoWebhookInfoParams { + return &GetRepoWebhookInfoParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetRepoWebhookInfoParamsWithTimeout creates a new GetRepoWebhookInfoParams object +// with the ability to set a timeout on a request. +func NewGetRepoWebhookInfoParamsWithTimeout(timeout time.Duration) *GetRepoWebhookInfoParams { + return &GetRepoWebhookInfoParams{ + timeout: timeout, + } +} + +// NewGetRepoWebhookInfoParamsWithContext creates a new GetRepoWebhookInfoParams object +// with the ability to set a context for a request. +func NewGetRepoWebhookInfoParamsWithContext(ctx context.Context) *GetRepoWebhookInfoParams { + return &GetRepoWebhookInfoParams{ + Context: ctx, + } +} + +// NewGetRepoWebhookInfoParamsWithHTTPClient creates a new GetRepoWebhookInfoParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetRepoWebhookInfoParamsWithHTTPClient(client *http.Client) *GetRepoWebhookInfoParams { + return &GetRepoWebhookInfoParams{ + HTTPClient: client, + } +} + +/* +GetRepoWebhookInfoParams contains all the parameters to send to the API endpoint + + for the get repo webhook info operation. + + Typically these are written to a http.Request. +*/ +type GetRepoWebhookInfoParams struct { + + /* RepoID. + + Repository ID. + */ + RepoID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get repo webhook info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetRepoWebhookInfoParams) WithDefaults() *GetRepoWebhookInfoParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get repo webhook info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetRepoWebhookInfoParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) WithTimeout(timeout time.Duration) *GetRepoWebhookInfoParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) WithContext(ctx context.Context) *GetRepoWebhookInfoParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) WithHTTPClient(client *http.Client) *GetRepoWebhookInfoParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithRepoID adds the repoID to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) WithRepoID(repoID string) *GetRepoWebhookInfoParams { + o.SetRepoID(repoID) + return o +} + +// SetRepoID adds the repoId to the get repo webhook info params +func (o *GetRepoWebhookInfoParams) SetRepoID(repoID string) { + o.RepoID = repoID +} + +// WriteToRequest writes these params to a swagger request +func (o *GetRepoWebhookInfoParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param repoID + if err := r.SetPathParam("repoID", o.RepoID); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/client/repositories/get_repo_webhook_info_responses.go b/client/repositories/get_repo_webhook_info_responses.go new file mode 100644 index 00000000..75f61a2b --- /dev/null +++ b/client/repositories/get_repo_webhook_info_responses.go @@ -0,0 +1,179 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package repositories + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "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" +) + +// GetRepoWebhookInfoReader is a Reader for the GetRepoWebhookInfo structure. +type GetRepoWebhookInfoReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetRepoWebhookInfoReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetRepoWebhookInfoOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetRepoWebhookInfoDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetRepoWebhookInfoOK creates a GetRepoWebhookInfoOK with default headers values +func NewGetRepoWebhookInfoOK() *GetRepoWebhookInfoOK { + return &GetRepoWebhookInfoOK{} +} + +/* +GetRepoWebhookInfoOK describes a response with status code 200, with default header values. + +HookInfo +*/ +type GetRepoWebhookInfoOK struct { + Payload garm_params.HookInfo +} + +// IsSuccess returns true when this get repo webhook info o k response has a 2xx status code +func (o *GetRepoWebhookInfoOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get repo webhook info o k response has a 3xx status code +func (o *GetRepoWebhookInfoOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get repo webhook info o k response has a 4xx status code +func (o *GetRepoWebhookInfoOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get repo webhook info o k response has a 5xx status code +func (o *GetRepoWebhookInfoOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get repo webhook info o k response a status code equal to that given +func (o *GetRepoWebhookInfoOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get repo webhook info o k response +func (o *GetRepoWebhookInfoOK) Code() int { + return 200 +} + +func (o *GetRepoWebhookInfoOK) Error() string { + return fmt.Sprintf("[GET /repositories/{repoID}/webhook][%d] getRepoWebhookInfoOK %+v", 200, o.Payload) +} + +func (o *GetRepoWebhookInfoOK) String() string { + return fmt.Sprintf("[GET /repositories/{repoID}/webhook][%d] getRepoWebhookInfoOK %+v", 200, o.Payload) +} + +func (o *GetRepoWebhookInfoOK) GetPayload() garm_params.HookInfo { + return o.Payload +} + +func (o *GetRepoWebhookInfoOK) 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 +} + +// NewGetRepoWebhookInfoDefault creates a GetRepoWebhookInfoDefault with default headers values +func NewGetRepoWebhookInfoDefault(code int) *GetRepoWebhookInfoDefault { + return &GetRepoWebhookInfoDefault{ + _statusCode: code, + } +} + +/* +GetRepoWebhookInfoDefault describes a response with status code -1, with default header values. + +APIErrorResponse +*/ +type GetRepoWebhookInfoDefault struct { + _statusCode int + + Payload apiserver_params.APIErrorResponse +} + +// IsSuccess returns true when this get repo webhook info default response has a 2xx status code +func (o *GetRepoWebhookInfoDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this get repo webhook info default response has a 3xx status code +func (o *GetRepoWebhookInfoDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this get repo webhook info default response has a 4xx status code +func (o *GetRepoWebhookInfoDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this get repo webhook info default response has a 5xx status code +func (o *GetRepoWebhookInfoDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this get repo webhook info default response a status code equal to that given +func (o *GetRepoWebhookInfoDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the get repo webhook info default response +func (o *GetRepoWebhookInfoDefault) Code() int { + return o._statusCode +} + +func (o *GetRepoWebhookInfoDefault) Error() string { + return fmt.Sprintf("[GET /repositories/{repoID}/webhook][%d] GetRepoWebhookInfo default %+v", o._statusCode, o.Payload) +} + +func (o *GetRepoWebhookInfoDefault) String() string { + return fmt.Sprintf("[GET /repositories/{repoID}/webhook][%d] GetRepoWebhookInfo default %+v", o._statusCode, o.Payload) +} + +func (o *GetRepoWebhookInfoDefault) GetPayload() apiserver_params.APIErrorResponse { + return o.Payload +} + +func (o *GetRepoWebhookInfoDefault) 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/repositories/install_repo_webhook_responses.go b/client/repositories/install_repo_webhook_responses.go index 3eeb3b9a..9296fbef 100644 --- a/client/repositories/install_repo_webhook_responses.go +++ b/client/repositories/install_repo_webhook_responses.go @@ -13,6 +13,7 @@ import ( "github.com/go-openapi/strfmt" apiserver_params "github.com/cloudbase/garm/apiserver/params" + garm_params "github.com/cloudbase/garm/params" ) // InstallRepoWebhookReader is a Reader for the InstallRepoWebhook structure. @@ -49,9 +50,10 @@ func NewInstallRepoWebhookOK() *InstallRepoWebhookOK { /* InstallRepoWebhookOK describes a response with status code 200, with default header values. -InstallRepoWebhookOK install repo webhook o k +HookInfo */ type InstallRepoWebhookOK struct { + Payload garm_params.HookInfo } // IsSuccess returns true when this install repo webhook o k response has a 2xx status code @@ -85,15 +87,24 @@ func (o *InstallRepoWebhookOK) Code() int { } func (o *InstallRepoWebhookOK) Error() string { - return fmt.Sprintf("[POST /repositories/{repoID}/webhook][%d] installRepoWebhookOK ", 200) + return fmt.Sprintf("[POST /repositories/{repoID}/webhook][%d] installRepoWebhookOK %+v", 200, o.Payload) } func (o *InstallRepoWebhookOK) String() string { - return fmt.Sprintf("[POST /repositories/{repoID}/webhook][%d] installRepoWebhookOK ", 200) + return fmt.Sprintf("[POST /repositories/{repoID}/webhook][%d] installRepoWebhookOK %+v", 200, o.Payload) +} + +func (o *InstallRepoWebhookOK) GetPayload() garm_params.HookInfo { + return o.Payload } func (o *InstallRepoWebhookOK) 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/repositories/repositories_client.go b/client/repositories/repositories_client.go index e3324e4a..c9903aa3 100644 --- a/client/repositories/repositories_client.go +++ b/client/repositories/repositories_client.go @@ -40,6 +40,8 @@ type ClientService interface { GetRepoPool(params *GetRepoPoolParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRepoPoolOK, error) + GetRepoWebhookInfo(params *GetRepoWebhookInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRepoWebhookInfoOK, error) + InstallRepoWebhook(params *InstallRepoWebhookParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*InstallRepoWebhookOK, error) ListRepoInstances(params *ListRepoInstancesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListRepoInstancesOK, error) @@ -273,6 +275,44 @@ func (a *Client) GetRepoPool(params *GetRepoPoolParams, authInfo runtime.ClientA return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +GetRepoWebhookInfo gets information about the g a r m installed webhook on a repository +*/ +func (a *Client) GetRepoWebhookInfo(params *GetRepoWebhookInfoParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRepoWebhookInfoOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetRepoWebhookInfoParams() + } + op := &runtime.ClientOperation{ + ID: "GetRepoWebhookInfo", + Method: "GET", + PathPattern: "/repositories/{repoID}/webhook", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetRepoWebhookInfoReader{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.(*GetRepoWebhookInfoOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*GetRepoWebhookInfoDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* InstallRepoWebhook Install the GARM webhook for an organization. The secret configured on the organization will diff --git a/cmd/garm-cli/cmd/organization.go b/cmd/garm-cli/cmd/organization.go index 3f37f855..3c7ba608 100644 --- a/cmd/garm-cli/cmd/organization.go +++ b/cmd/garm-cli/cmd/organization.go @@ -76,10 +76,39 @@ var orgWebhookInstallCmd = &cobra.Command{ installWebhookReq.Body.InsecureSSL = insecureOrgWebhook installWebhookReq.Body.WebhookEndpointType = params.WebhookEndpointDirect - _, err := apiCli.Organizations.InstallOrgWebhook(installWebhookReq, authToken) + response, err := apiCli.Organizations.InstallOrgWebhook(installWebhookReq, authToken) if err != nil { return err } + formatOneHookInfo(response.Payload) + return nil + }, +} + +var orgHookInfoShowCmd = &cobra.Command{ + Use: "show", + Short: "Show webhook info", + Long: `Show webhook info for an organization.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return errNeedsInitError + } + if len(args) == 0 { + return fmt.Errorf("requires an organization ID") + } + if len(args) > 1 { + return fmt.Errorf("too many arguments") + } + + showWebhookInfoReq := apiClientOrgs.NewGetOrgWebhookInfoParams() + showWebhookInfoReq.OrgID = args[0] + + response, err := apiCli.Organizations.GetOrgWebhookInfo(showWebhookInfoReq, authToken) + if err != nil { + return err + } + formatOneHookInfo(response.Payload) return nil }, } @@ -266,6 +295,7 @@ func init() { orgWebhookCmd.AddCommand( orgWebhookInstallCmd, orgWebhookUninstallCmd, + orgHookInfoShowCmd, ) organizationCmd.AddCommand( diff --git a/cmd/garm-cli/cmd/repository.go b/cmd/garm-cli/cmd/repository.go index 2f0b5937..f479ef42 100644 --- a/cmd/garm-cli/cmd/repository.go +++ b/cmd/garm-cli/cmd/repository.go @@ -77,10 +77,39 @@ var repoWebhookInstallCmd = &cobra.Command{ installWebhookReq.Body.InsecureSSL = insecureRepoWebhook installWebhookReq.Body.WebhookEndpointType = params.WebhookEndpointDirect - _, err := apiCli.Repositories.InstallRepoWebhook(installWebhookReq, authToken) + response, err := apiCli.Repositories.InstallRepoWebhook(installWebhookReq, authToken) if err != nil { return err } + formatOneHookInfo(response.Payload) + return nil + }, +} + +var repoHookInfoShowCmd = &cobra.Command{ + Use: "show", + Short: "Show webhook info", + Long: `Show webhook info for a repository.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return errNeedsInitError + } + if len(args) == 0 { + return fmt.Errorf("requires a repository ID") + } + if len(args) > 1 { + return fmt.Errorf("too many arguments") + } + + showWebhookInfoReq := apiClientRepos.NewGetRepoWebhookInfoParams() + showWebhookInfoReq.RepoID = args[0] + + response, err := apiCli.Repositories.GetRepoWebhookInfo(showWebhookInfoReq, authToken) + if err != nil { + return err + } + formatOneHookInfo(response.Payload) return nil }, } @@ -271,6 +300,7 @@ func init() { repoWebhookCmd.AddCommand( repoWebhookInstallCmd, repoWebhookUninstallCmd, + repoHookInfoShowCmd, ) repositoryCmd.AddCommand( diff --git a/cmd/garm-cli/cmd/root.go b/cmd/garm-cli/cmd/root.go index c491e263..72ecddc4 100644 --- a/cmd/garm-cli/cmd/root.go +++ b/cmd/garm-cli/cmd/root.go @@ -21,7 +21,9 @@ import ( apiClient "github.com/cloudbase/garm/client" "github.com/cloudbase/garm/cmd/garm-cli/config" + "github.com/cloudbase/garm/params" "github.com/go-openapi/runtime" + "github.com/jedib0t/go-pretty/v6/table" openapiRuntimeClient "github.com/go-openapi/runtime/client" "github.com/spf13/cobra" @@ -98,3 +100,17 @@ func initConfig() { } initApiClient(mgr.BaseURL, mgr.Token) } + +func formatOneHookInfo(hook params.HookInfo) { + t := table.NewWriter() + header := table.Row{"Field", "Value"} + t.AppendHeader(header) + t.AppendRows([]table.Row{ + {"ID", hook.ID}, + {"URL", hook.URL}, + {"Events", hook.Events}, + {"Active", hook.Active}, + {"Insecure SSL", hook.InsecureSSL}, + }) + fmt.Println(t.Render()) +} diff --git a/params/params.go b/params/params.go index 6deeb420..26764e1d 100644 --- a/params/params.go +++ b/params/params.go @@ -505,3 +505,11 @@ type InstallWebhookParams struct { WebhookEndpointType WebhookEndpointType `json:"webhook_endpoint_type"` InsecureSSL bool `json:"insecure_ssl"` } + +type HookInfo struct { + ID int64 `json:"id"` + URL string `json:"url"` + Events []string `json:"events"` + Active bool `json:"active"` + InsecureSSL bool `json:"insecure_ssl"` +} diff --git a/runner/common/mocks/PoolManager.go b/runner/common/mocks/PoolManager.go index f5a43868..46444e28 100644 --- a/runner/common/mocks/PoolManager.go +++ b/runner/common/mocks/PoolManager.go @@ -28,6 +28,30 @@ func (_m *PoolManager) ForceDeleteRunner(runner params.Instance) error { return r0 } +// GetWebhookInfo provides a mock function with given fields: ctx +func (_m *PoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) { + ret := _m.Called(ctx) + + var r0 params.HookInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (params.HookInfo, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) params.HookInfo); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(params.HookInfo) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GithubRunnerRegistrationToken provides a mock function with given fields: func (_m *PoolManager) GithubRunnerRegistrationToken() (string, error) { ret := _m.Called() @@ -81,17 +105,27 @@ func (_m *PoolManager) ID() string { } // InstallWebhook provides a mock function with given fields: ctx, param -func (_m *PoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) error { +func (_m *PoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) { ret := _m.Called(ctx, param) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, params.InstallWebhookParams) error); ok { + var r0 params.HookInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, params.InstallWebhookParams) (params.HookInfo, error)); ok { + return rf(ctx, param) + } + if rf, ok := ret.Get(0).(func(context.Context, params.InstallWebhookParams) params.HookInfo); ok { r0 = rf(ctx, param) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(params.HookInfo) } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, params.InstallWebhookParams) error); ok { + r1 = rf(ctx, param) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // RefreshState provides a mock function with given fields: param diff --git a/runner/common/pool.go b/runner/common/pool.go index dd7049b0..9024a930 100644 --- a/runner/common/pool.go +++ b/runner/common/pool.go @@ -44,7 +44,9 @@ type PoolManager interface { HandleWorkflowJob(job params.WorkflowJob) error RefreshState(param params.UpdatePoolStateParams) error ForceDeleteRunner(runner params.Instance) error - InstallWebhook(ctx context.Context, param params.InstallWebhookParams) error + + InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) + GetWebhookInfo(ctx context.Context) (params.HookInfo, error) UninstallWebhook(ctx context.Context) error // PoolManager lifecycle functions. Start/stop pool. diff --git a/runner/organizations.go b/runner/organizations.go index 06fc5ae8..abf5f544 100644 --- a/runner/organizations.go +++ b/runner/organizations.go @@ -340,25 +340,26 @@ func (r *Runner) findOrgPoolManager(name string) (common.PoolManager, error) { return poolManager, nil } -func (r *Runner) InstallOrgWebhook(ctx context.Context, orgID string, param params.InstallWebhookParams) error { +func (r *Runner) InstallOrgWebhook(ctx context.Context, orgID string, param params.InstallWebhookParams) (params.HookInfo, error) { if !auth.IsAdmin(ctx) { - return runnerErrors.ErrUnauthorized + return params.HookInfo{}, runnerErrors.ErrUnauthorized } org, err := r.store.GetOrganizationByID(ctx, orgID) if err != nil { - return errors.Wrap(err, "fetching org") + return params.HookInfo{}, errors.Wrap(err, "fetching org") } poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org) if err != nil { - return errors.Wrap(err, "fetching pool manager for org") + return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for org") } - if err := poolMgr.InstallWebhook(ctx, param); err != nil { - return errors.Wrap(err, "installing webhook") + info, err := poolMgr.InstallWebhook(ctx, param) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "installing webhook") } - return nil + return info, nil } func (r *Runner) UninstallOrgWebhook(ctx context.Context, orgID string) error { @@ -381,3 +382,25 @@ func (r *Runner) UninstallOrgWebhook(ctx context.Context, orgID string) error { } return nil } + +func (r *Runner) GetOrgWebhookInfo(ctx context.Context, orgID string) (params.HookInfo, error) { + if !auth.IsAdmin(ctx) { + return params.HookInfo{}, runnerErrors.ErrUnauthorized + } + + org, err := r.store.GetOrganizationByID(ctx, orgID) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "fetching org") + } + + poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for org") + } + + info, err := poolMgr.GetWebhookInfo(ctx) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "fetching webhook info") + } + return info, nil +} diff --git a/runner/pool/common.go b/runner/pool/common.go new file mode 100644 index 00000000..a89a5ac6 --- /dev/null +++ b/runner/pool/common.go @@ -0,0 +1,63 @@ +package pool + +import ( + "net/url" + "strings" + + runnerErrors "github.com/cloudbase/garm-provider-common/errors" + "github.com/cloudbase/garm/params" + "github.com/google/go-github/v53/github" + "github.com/pkg/errors" +) + +func validateHookRequest(controllerID, baseURL string, allHooks []*github.Hook, req *github.Hook) error { + parsed, err := url.Parse(baseURL) + if err != nil { + return errors.Wrap(err, "parsing webhook url") + } + + partialMatches := []string{} + for _, hook := range allHooks { + hookURL, ok := hook.Config["url"].(string) + if !ok { + continue + } + hookURL = strings.ToLower(hookURL) + + if hook.Config["url"] == req.Config["url"] { + return runnerErrors.NewConflictError("hook already installed") + } else if strings.Contains(hookURL, controllerID) || strings.Contains(hookURL, parsed.Hostname()) { + partialMatches = append(partialMatches, hook.Config["url"].(string)) + } + } + + if len(partialMatches) > 0 { + return runnerErrors.NewConflictError("a webhook containing the controller ID or hostname of this contreoller is already installed on this repository") + } + + return nil +} + +func hookToParamsHookInfo(hook *github.Hook) params.HookInfo { + var hookURL string + url, ok := hook.Config["url"] + if ok { + hookURL = url.(string) + } + + var insecureSSL bool + insecureSSLConfig, ok := hook.Config["insecure_ssl"] + if ok { + if insecureSSLConfig.(string) == "1" { + insecureSSL = true + } + } + + return params.HookInfo{ + ID: *hook.ID, + URL: hookURL, + Events: hook.Events, + Active: *hook.Active, + InsecureSSL: insecureSSL, + } +} diff --git a/runner/pool/enterprise.go b/runner/pool/enterprise.go index 54f7fc89..b61fb2ba 100644 --- a/runner/pool/enterprise.go +++ b/runner/pool/enterprise.go @@ -224,10 +224,14 @@ func (r *enterprise) ID() string { return r.id } -func (r *enterprise) InstallHook(ctx context.Context, req *github.Hook) error { - return fmt.Errorf("not implemented") +func (r *enterprise) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) { + return params.HookInfo{}, fmt.Errorf("not implemented") } func (r *enterprise) UninstallHook(ctx context.Context, url string) error { return fmt.Errorf("not implemented") } + +func (r *enterprise) GetHookInfo(ctx context.Context) (params.HookInfo, error) { + return params.HookInfo{}, fmt.Errorf("not implemented") +} diff --git a/runner/pool/interfaces.go b/runner/pool/interfaces.go index c589898a..c1882523 100644 --- a/runner/pool/interfaces.go +++ b/runner/pool/interfaces.go @@ -31,8 +31,9 @@ type poolHelper interface { RemoveGithubRunner(runnerID int64) (*github.Response, error) FetchTools() ([]*github.RunnerApplicationDownload, error) - InstallHook(ctx context.Context, req *github.Hook) error + InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) UninstallHook(ctx context.Context, url string) error + GetHookInfo(ctx context.Context) (params.HookInfo, error) GithubCLI() common.GithubClient diff --git a/runner/pool/organization.go b/runner/pool/organization.go index b97ef94e..59e872c5 100644 --- a/runner/pool/organization.go +++ b/runner/pool/organization.go @@ -265,23 +265,22 @@ func (r *organization) listHooks(ctx context.Context) ([]*github.Hook, error) { return allHooks, nil } -func (r *organization) InstallHook(ctx context.Context, req *github.Hook) error { +func (r *organization) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) { allHooks, err := r.listHooks(ctx) if err != nil { - return errors.Wrap(err, "listing hooks") + return params.HookInfo{}, errors.Wrap(err, "listing hooks") } - for _, hook := range allHooks { - if hook.Config["url"] == req.Config["url"] { - return runnerErrors.NewBadRequestError("hook already installed") - } + if err := validateHookRequest(r.cfgInternal.ControllerID, r.cfgInternal.BaseWebhookURL, allHooks, req); err != nil { + return params.HookInfo{}, errors.Wrap(err, "validating hook request") } - _, _, err = r.ghcli.CreateOrgHook(ctx, r.cfg.Name, req) + hook, _, err := r.ghcli.CreateOrgHook(ctx, r.cfg.Name, req) if err != nil { - return errors.Wrap(err, "creating organization hook") + return params.HookInfo{}, errors.Wrap(err, "creating organization hook") } - return nil + + return hookToParamsHookInfo(hook), nil } func (r *organization) UninstallHook(ctx context.Context, url string) error { @@ -301,3 +300,19 @@ func (r *organization) UninstallHook(ctx context.Context, url string) error { } return nil } + +func (r *organization) GetHookInfo(ctx context.Context) (params.HookInfo, error) { + allHooks, err := r.listHooks(ctx) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "listing hooks") + } + + for _, hook := range allHooks { + hookInfo := hookToParamsHookInfo(hook) + if strings.EqualFold(hookInfo.URL, r.cfgInternal.ControllerWebhookURL) { + return hookInfo, nil + } + } + + return params.HookInfo{}, runnerErrors.NewNotFoundError("hook not found") +} diff --git a/runner/pool/pool.go b/runner/pool/pool.go index d5c197df..55b63692 100644 --- a/runner/pool/pool.go +++ b/runner/pool/pool.go @@ -1607,9 +1607,9 @@ func (r *basePoolManager) consumeQueuedJobs() error { return nil } -func (r *basePoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) error { +func (r *basePoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) { if r.urls.controllerWebhookURL == "" { - return errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty") + return params.HookInfo{}, errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty") } insecureSSL := "0" @@ -1639,3 +1639,7 @@ func (r *basePoolManager) UninstallWebhook(ctx context.Context) error { return r.helper.UninstallHook(ctx, r.urls.controllerWebhookURL) } + +func (r *basePoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) { + return r.helper.GetHookInfo(ctx) +} diff --git a/runner/pool/repository.go b/runner/pool/repository.go index 646b0aab..8910b24d 100644 --- a/runner/pool/repository.go +++ b/runner/pool/repository.go @@ -266,23 +266,22 @@ func (r *repository) listHooks(ctx context.Context) ([]*github.Hook, error) { return allHooks, nil } -func (r *repository) InstallHook(ctx context.Context, req *github.Hook) error { +func (r *repository) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) { allHooks, err := r.listHooks(ctx) if err != nil { - return errors.Wrap(err, "listing hooks") + return params.HookInfo{}, errors.Wrap(err, "listing hooks") } - for _, hook := range allHooks { - if hook.Config["url"] == req.Config["url"] { - return runnerErrors.NewBadRequestError("hook already installed") - } + if err := validateHookRequest(r.cfgInternal.ControllerID, r.cfgInternal.BaseWebhookURL, allHooks, req); err != nil { + return params.HookInfo{}, errors.Wrap(err, "validating hook request") } - _, _, err = r.ghcli.CreateRepoHook(ctx, r.cfg.Owner, r.cfg.Name, req) + hook, _, err := r.ghcli.CreateRepoHook(ctx, r.cfg.Owner, r.cfg.Name, req) if err != nil { - return errors.Wrap(err, "creating repository hook") + return params.HookInfo{}, errors.Wrap(err, "creating repository hook") } - return nil + + return hookToParamsHookInfo(hook), nil } func (r *repository) UninstallHook(ctx context.Context, url string) error { @@ -302,3 +301,18 @@ func (r *repository) UninstallHook(ctx context.Context, url string) error { } return nil } + +func (r *repository) GetHookInfo(ctx context.Context) (params.HookInfo, error) { + allHooks, err := r.listHooks(ctx) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "listing hooks") + } + + for _, hook := range allHooks { + hookInfo := hookToParamsHookInfo(hook) + if strings.EqualFold(hookInfo.URL, r.cfgInternal.ControllerWebhookURL) { + return hookInfo, nil + } + } + return params.HookInfo{}, runnerErrors.NewNotFoundError("hook not found") +} diff --git a/runner/repositories.go b/runner/repositories.go index 3030bdce..08a999ca 100644 --- a/runner/repositories.go +++ b/runner/repositories.go @@ -350,25 +350,26 @@ func (r *Runner) findRepoPoolManager(owner, name string) (common.PoolManager, er return poolManager, nil } -func (r *Runner) InstallRepoWebhook(ctx context.Context, repoID string, param params.InstallWebhookParams) error { +func (r *Runner) InstallRepoWebhook(ctx context.Context, repoID string, param params.InstallWebhookParams) (params.HookInfo, error) { if !auth.IsAdmin(ctx) { - return runnerErrors.ErrUnauthorized + return params.HookInfo{}, runnerErrors.ErrUnauthorized } repo, err := r.store.GetRepositoryByID(ctx, repoID) if err != nil { - return errors.Wrap(err, "fetching repo") + return params.HookInfo{}, errors.Wrap(err, "fetching repo") } poolManager, err := r.poolManagerCtrl.GetRepoPoolManager(repo) if err != nil { - return errors.Wrap(err, "fetching pool manager for repo") + return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for repo") } - if err := poolManager.InstallWebhook(ctx, param); err != nil { - return errors.Wrap(err, "installing webhook") + info, err := poolManager.InstallWebhook(ctx, param) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "installing webhook") } - return nil + return info, nil } func (r *Runner) UninstallRepoWebhook(ctx context.Context, repoID string) error { @@ -391,3 +392,25 @@ func (r *Runner) UninstallRepoWebhook(ctx context.Context, repoID string) error } return nil } + +func (r *Runner) GetRepoWebhookInfo(ctx context.Context, repoID string) (params.HookInfo, error) { + if !auth.IsAdmin(ctx) { + return params.HookInfo{}, runnerErrors.ErrUnauthorized + } + + repo, err := r.store.GetRepositoryByID(ctx, repoID) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "fetching repo") + } + + poolManager, err := r.poolManagerCtrl.GetRepoPoolManager(repo) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for repo") + } + + info, err := poolManager.GetWebhookInfo(ctx) + if err != nil { + return params.HookInfo{}, errors.Wrap(err, "getting webhook info") + } + return info, nil +}