Merge pull request #243 from gabriel-samfira/use-db-for-gh-creds

Move github credentials to the database
This commit is contained in:
Gabriel 2024-05-09 19:36:04 +03:00 committed by GitHub
commit 76d45ad83f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 9205 additions and 882 deletions

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v3
@ -20,7 +20,9 @@ jobs:
uses: canonical/setup-lxd@v0.1.1
- name: Install dependencies
run: sudo apt-get -qq update && sudo apt-get -qq install -y apg coreutils make
run: |
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get -qq update && sudo apt-get -qq install -y apg coreutils make
- name: Set up ngrok
id: ngrok
@ -43,6 +45,7 @@ jobs:
echo "GARM_PASSWORD=$GARM_PASSWORD" >> $GITHUB_ENV
echo "REPO_WEBHOOK_SECRET=$REPO_WEBHOOK_SECRET" >> $GITHUB_ENV
echo "ORG_WEBHOOK_SECRET=$ORG_WEBHOOK_SECRET" >> $GITHUB_ENV
echo "GARM_CHECKOUT_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV
- name: Create logs directory
if: always()
@ -52,7 +55,7 @@ jobs:
run: |
set -o pipefail
set -o errexit
make integration 2>&1 | tee /artifacts-logs/e2e.log
make integration 2>&1
env:
GARM_BASE_URL: ${{ steps.ngrok.outputs.tunnel-url }}
ORG_NAME: gsamfira
@ -66,6 +69,7 @@ jobs:
run: |
sudo systemctl status garm@runner || true
sudo journalctl --no-pager 2>&1 > /artifacts-logs/system.log
sudo journalctl -u garm@runner --no-pager 2>&1 > /artifacts-logs/garm.log
- name: Upload GARM and e2e logs
if: always()

View file

@ -3,7 +3,7 @@ export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit
.ONESHELL:
GEN_PASSWORD=$(shell (apg -n1 -m32))
GEN_PASSWORD=$(shell (/usr/bin/apg -n1 -m32))
IMAGE_TAG = garm-build
USER_ID=$(shell ((docker --version | grep -q podman) && echo "0" || id -u))

View file

@ -35,7 +35,6 @@ The ```GARM``` configuration is a simple ```toml```. The sample config file in [
* [The default section](/doc/config_default.md)
* [Logging](/doc/config_logging.md)
* [Database](/doc/database.md)
* [Github credentials](/doc/github_credentials.md)
* [Providers](/doc/providers.md)
* [Metrics](/doc/config_metrics.md)
* [JWT authentication](/doc/config_jwt_auth.md)

View file

@ -329,27 +329,6 @@ func (a *APIController) FirstRunHandler(w http.ResponseWriter, r *http.Request)
}
}
// swagger:route GET /credentials credentials ListCredentials
//
// List all credentials.
//
// Responses:
// 200: Credentials
// 400: APIErrorResponse
func (a *APIController) ListCredentials(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
creds, err := a.r.ListCredentials(ctx)
if err != nil {
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(creds); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /providers providers ListProviders
//
// List all providers.

View file

@ -0,0 +1,229 @@
package controllers
import (
"encoding/json"
"log/slog"
"math"
"net/http"
"strconv"
"github.com/gorilla/mux"
gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
// swagger:route GET /credentials credentials ListCredentials
// swagger:route GET /github/credentials credentials ListCredentials
//
// List all credentials.
//
// Responses:
// 200: Credentials
// 400: APIErrorResponse
func (a *APIController) ListCredentials(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
creds, err := a.r.ListCredentials(ctx)
if err != nil {
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(creds); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route POST /github/credentials credentials CreateCredentials
//
// Create a GitHub credential.
//
// Parameters:
// + name: Body
// description: Parameters used when creating a GitHub credential.
// type: CreateGithubCredentialsParams
// in: body
// required: true
//
// Responses:
// 200: GithubCredentials
// 400: APIErrorResponse
func (a *APIController) CreateGithubCredential(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var params params.CreateGithubCredentialsParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
cred, err := a.r.CreateGithubCredentials(ctx, params)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to create GitHub credential")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(cred); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /github/credentials/{id} credentials GetCredentials
//
// Get a GitHub credential.
//
// Parameters:
// + name: id
// description: ID of the GitHub credential.
// type: integer
// in: path
// required: true
//
// Responses:
// 200: GithubCredentials
// 400: APIErrorResponse
func (a *APIController) GetGithubCredential(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
idParam, ok := vars["id"]
if !ok {
slog.ErrorContext(ctx, "missing id in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
id, err := strconv.ParseUint(idParam, 10, 64)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if id > math.MaxUint {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "id is too large")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
cred, err := a.r.GetGithubCredentials(ctx, uint(id))
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to get GitHub credential")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(cred); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route DELETE /github/credentials/{id} credentials DeleteCredentials
//
// Delete a GitHub credential.
//
// Parameters:
// + name: id
// description: ID of the GitHub credential.
// type: integer
// in: path
// required: true
//
// Responses:
// default: APIErrorResponse
func (a *APIController) DeleteGithubCredential(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
idParam, ok := vars["id"]
if !ok {
slog.ErrorContext(ctx, "missing id in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
id, err := strconv.ParseUint(idParam, 10, 64)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if id > math.MaxUint {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "id is too large")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if err := a.r.DeleteGithubCredentials(ctx, uint(id)); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to delete GitHub credential")
handleError(ctx, w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// swagger:route PUT /github/credentials/{id} credentials UpdateCredentials
//
// Update a GitHub credential.
//
// Parameters:
// + name: id
// description: ID of the GitHub credential.
// type: integer
// in: path
// required: true
// + name: Body
// description: Parameters used when updating a GitHub credential.
// type: UpdateGithubCredentialsParams
// in: body
// required: true
//
// Responses:
// 200: GithubCredentials
// 400: APIErrorResponse
func (a *APIController) UpdateGithubCredential(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
idParam, ok := vars["id"]
if !ok {
slog.ErrorContext(ctx, "missing id in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
id, err := strconv.ParseUint(idParam, 10, 64)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to parse id")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if id > math.MaxUint {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "id is too large")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
var params params.UpdateGithubCredentialsParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
cred, err := a.r.UpdateGithubCredentials(ctx, uint(id), params)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to update GitHub credential")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(cred); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}

View file

@ -0,0 +1,186 @@
package controllers
import (
"encoding/json"
"log/slog"
"net/http"
"github.com/gorilla/mux"
gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
// swagger:route POST /github/endpoints endpoints CreateGithubEndpoint
//
// Create a GitHub Endpoint.
//
// Parameters:
// + name: Body
// description: Parameters used when creating a GitHub endpoint.
// type: CreateGithubEndpointParams
// in: body
// required: true
//
// Responses:
// 200: GithubEndpoint
// default: APIErrorResponse
func (a *APIController) CreateGithubEndpoint(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var params params.CreateGithubEndpointParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
endpoint, err := a.r.CreateGithubEndpoint(ctx, params)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to create GitHub endpoint")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(endpoint); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /github/endpoints endpoints ListGithubEndpoints
//
// List all GitHub Endpoints.
//
// Responses:
// 200: GithubEndpoints
// default: APIErrorResponse
func (a *APIController) ListGithubEndpoints(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
endpoints, err := a.r.ListGithubEndpoints(ctx)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to list GitHub endpoints")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(endpoints); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route GET /github/endpoints/{name} endpoints GetGithubEndpoint
//
// Get a GitHub Endpoint.
//
// Parameters:
// + name: name
// description: The name of the GitHub endpoint.
// type: string
// in: path
// required: true
//
// Responses:
// 200: GithubEndpoint
// default: APIErrorResponse
func (a *APIController) GetGithubEndpoint(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
name, ok := vars["name"]
if !ok {
slog.ErrorContext(ctx, "missing name in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
endpoint, err := a.r.GetGithubEndpoint(ctx, name)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to get GitHub endpoint")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(endpoint); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route DELETE /github/endpoints/{name} endpoints DeleteGithubEndpoint
//
// Delete a GitHub Endpoint.
//
// Parameters:
// + name: name
// description: The name of the GitHub endpoint.
// type: string
// in: path
// required: true
//
// Responses:
// default: APIErrorResponse
func (a *APIController) DeleteGithubEndpoint(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
name, ok := vars["name"]
if !ok {
slog.ErrorContext(ctx, "missing name in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if err := a.r.DeleteGithubEndpoint(ctx, name); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to delete GitHub endpoint")
handleError(ctx, w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// swagger:route PUT /github/endpoints/{name} endpoints UpdateGithubEndpoint
//
// Update a GitHub Endpoint.
//
// Parameters:
// + name: name
// description: The name of the GitHub endpoint.
// type: string
// in: path
// required: true
// + name: Body
// description: Parameters used when updating a GitHub endpoint.
// type: UpdateGithubEndpointParams
// in: body
// required: true
//
// Responses:
// 200: GithubEndpoint
// default: APIErrorResponse
func (a *APIController) UpdateGithubEndpoint(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
name, ok := vars["name"]
if !ok {
slog.ErrorContext(ctx, "missing name in request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
var params params.UpdateGithubEndpointParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
endpoint, err := a.r.UpdateGithubEndpoint(ctx, name, params)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to update GitHub endpoint")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(endpoint); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}

View file

@ -339,9 +339,7 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/enterprises/", http.HandlerFunc(han.CreateEnterpriseHandler)).Methods("POST", "OPTIONS")
apiRouter.Handle("/enterprises", http.HandlerFunc(han.CreateEnterpriseHandler)).Methods("POST", "OPTIONS")
// Credentials and providers
apiRouter.Handle("/credentials/", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
apiRouter.Handle("/credentials", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
// Providers
apiRouter.Handle("/providers/", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
apiRouter.Handle("/providers", http.HandlerFunc(han.ListProviders)).Methods("GET", "OPTIONS")
@ -349,6 +347,47 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/controller-info/", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/controller-info", http.HandlerFunc(han.ControllerInfoHandler)).Methods("GET", "OPTIONS")
//////////////////////
// Github Endpoints //
//////////////////////
// Create Github Endpoint
apiRouter.Handle("/github/endpoints/", http.HandlerFunc(han.CreateGithubEndpoint)).Methods("POST", "OPTIONS")
apiRouter.Handle("/github/endpoints", http.HandlerFunc(han.CreateGithubEndpoint)).Methods("POST", "OPTIONS")
// List Github Endpoints
apiRouter.Handle("/github/endpoints/", http.HandlerFunc(han.ListGithubEndpoints)).Methods("GET", "OPTIONS")
apiRouter.Handle("/github/endpoints", http.HandlerFunc(han.ListGithubEndpoints)).Methods("GET", "OPTIONS")
// Get Github Endpoint
apiRouter.Handle("/github/endpoints/{name}/", http.HandlerFunc(han.GetGithubEndpoint)).Methods("GET", "OPTIONS")
apiRouter.Handle("/github/endpoints/{name}", http.HandlerFunc(han.GetGithubEndpoint)).Methods("GET", "OPTIONS")
// Delete Github Endpoint
apiRouter.Handle("/github/endpoints/{name}/", http.HandlerFunc(han.DeleteGithubEndpoint)).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/github/endpoints/{name}", http.HandlerFunc(han.DeleteGithubEndpoint)).Methods("DELETE", "OPTIONS")
// Update Github Endpoint
apiRouter.Handle("/github/endpoints/{name}/", http.HandlerFunc(han.UpdateGithubEndpoint)).Methods("PUT", "OPTIONS")
apiRouter.Handle("/github/endpoints/{name}", http.HandlerFunc(han.UpdateGithubEndpoint)).Methods("PUT", "OPTIONS")
////////////////////////
// Github credentials //
////////////////////////
// Legacy credentials path
apiRouter.Handle("/credentials/", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
apiRouter.Handle("/credentials", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
// List Github Credentials
apiRouter.Handle("/github/credentials/", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
apiRouter.Handle("/github/credentials", http.HandlerFunc(han.ListCredentials)).Methods("GET", "OPTIONS")
// Create Github Credentials
apiRouter.Handle("/github/credentials/", http.HandlerFunc(han.CreateGithubCredential)).Methods("POST", "OPTIONS")
apiRouter.Handle("/github/credentials", http.HandlerFunc(han.CreateGithubCredential)).Methods("POST", "OPTIONS")
// Get Github Credential
apiRouter.Handle("/github/credentials/{id}/", http.HandlerFunc(han.GetGithubCredential)).Methods("GET", "OPTIONS")
apiRouter.Handle("/github/credentials/{id}", http.HandlerFunc(han.GetGithubCredential)).Methods("GET", "OPTIONS")
// Delete Github Credential
apiRouter.Handle("/github/credentials/{id}/", http.HandlerFunc(han.DeleteGithubCredential)).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/github/credentials/{id}", http.HandlerFunc(han.DeleteGithubCredential)).Methods("DELETE", "OPTIONS")
// Update Github Credential
apiRouter.Handle("/github/credentials/{id}/", http.HandlerFunc(han.UpdateGithubCredential)).Methods("PUT", "OPTIONS")
apiRouter.Handle("/github/credentials/{id}", http.HandlerFunc(han.UpdateGithubCredential)).Methods("PUT", "OPTIONS")
// Websocket log writer
apiRouter.Handle("/{ws:ws\\/?}", http.HandlerFunc(han.WSHandler)).Methods("GET")

View file

@ -227,3 +227,54 @@ definitions:
import:
package: github.com/cloudbase/garm/apiserver/params
alias: apiserver_params
CreateInstanceParams:
type: object
x-go-type:
type: CreateInstanceParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
UpdateGithubEndpointParams:
type: object
x-go-type:
type: UpdateGithubEndpointParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
GithubEndpoint:
type: object
x-go-type:
type: GithubEndpoint
import:
package: github.com/cloudbase/garm/params
alias: garm_params
GithubEndpoints:
type: array
x-go-type:
type: GithubEndpoints
import:
package: github.com/cloudbase/garm/params
alias: garm_params
items:
$ref: '#/definitions/GithubEndpoint'
CreateGithubEndpointParams:
type: object
x-go-type:
type: CreateGithubEndpointParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
CreateGithubCredentialsParams:
type: object
x-go-type:
type: CreateGithubCredentialsParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
UpdateGithubCredentialsParams:
type: object
x-go-type:
type: UpdateGithubCredentialsParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params

View file

@ -23,6 +23,27 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateEnterpriseParams
CreateGithubCredentialsParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateGithubCredentialsParams
CreateGithubEndpointParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateGithubEndpointParams
CreateInstanceParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateInstanceParams
CreateOrgParams:
type: object
x-go-type:
@ -76,6 +97,22 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: GithubCredentials
GithubEndpoint:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: GithubEndpoint
GithubEndpoints:
items:
$ref: '#/definitions/GithubEndpoint'
type: array
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: GithubEndpoints
HookInfo:
type: object
x-go-type:
@ -214,6 +251,20 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateEntityParams
UpdateGithubCredentialsParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateGithubCredentialsParams
UpdateGithubEndpointParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateGithubEndpointParams
UpdatePoolParams:
type: object
x-go-type:
@ -275,21 +326,6 @@ paths:
summary: Get controller info.
tags:
- controllerInfo
/credentials:
get:
operationId: ListCredentials
responses:
"200":
description: Credentials
schema:
$ref: '#/definitions/Credentials'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List all credentials.
tags:
- credentials
/enterprises:
get:
operationId: ListEnterprises
@ -573,6 +609,212 @@ paths:
summary: Initialize the first run of the controller.
tags:
- first-run
/github/credentials:
get:
operationId: ListCredentials
responses:
"200":
description: Credentials
schema:
$ref: '#/definitions/Credentials'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List all credentials.
tags:
- credentials
post:
operationId: CreateCredentials
parameters:
- description: Parameters used when creating a GitHub credential.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/CreateGithubCredentialsParams'
description: Parameters used when creating a GitHub credential.
type: object
responses:
"200":
description: GithubCredentials
schema:
$ref: '#/definitions/GithubCredentials'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Create a GitHub credential.
tags:
- credentials
/github/credentials/{id}:
delete:
operationId: DeleteCredentials
parameters:
- description: ID of the GitHub credential.
in: path
name: id
required: true
type: integer
responses:
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Delete a GitHub credential.
tags:
- credentials
get:
operationId: GetCredentials
parameters:
- description: ID of the GitHub credential.
in: path
name: id
required: true
type: integer
responses:
"200":
description: GithubCredentials
schema:
$ref: '#/definitions/GithubCredentials'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Get a GitHub credential.
tags:
- credentials
put:
operationId: UpdateCredentials
parameters:
- description: ID of the GitHub credential.
in: path
name: id
required: true
type: integer
- description: Parameters used when updating a GitHub credential.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/UpdateGithubCredentialsParams'
description: Parameters used when updating a GitHub credential.
type: object
responses:
"200":
description: GithubCredentials
schema:
$ref: '#/definitions/GithubCredentials'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Update a GitHub credential.
tags:
- credentials
/github/endpoints:
get:
operationId: ListGithubEndpoints
responses:
"200":
description: GithubEndpoints
schema:
$ref: '#/definitions/GithubEndpoints'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List all GitHub Endpoints.
tags:
- endpoints
post:
operationId: CreateGithubEndpoint
parameters:
- description: Parameters used when creating a GitHub endpoint.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/CreateGithubEndpointParams'
description: Parameters used when creating a GitHub endpoint.
type: object
responses:
"200":
description: GithubEndpoint
schema:
$ref: '#/definitions/GithubEndpoint'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Create a GitHub Endpoint.
tags:
- endpoints
/github/endpoints/{name}:
delete:
operationId: DeleteGithubEndpoint
parameters:
- description: The name of the GitHub endpoint.
in: path
name: name
required: true
type: string
responses:
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Delete a GitHub Endpoint.
tags:
- endpoints
get:
operationId: GetGithubEndpoint
parameters:
- description: The name of the GitHub endpoint.
in: path
name: name
required: true
type: string
responses:
"200":
description: GithubEndpoint
schema:
$ref: '#/definitions/GithubEndpoint'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Get a GitHub Endpoint.
tags:
- endpoints
put:
operationId: UpdateGithubEndpoint
parameters:
- description: The name of the GitHub endpoint.
in: path
name: name
required: true
type: string
- description: Parameters used when updating a GitHub endpoint.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/UpdateGithubEndpointParams'
description: Parameters used when updating a GitHub endpoint.
type: object
responses:
"200":
description: GithubEndpoint
schema:
$ref: '#/definitions/GithubEndpoint'
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Update a GitHub Endpoint.
tags:
- endpoints
/instances:
get:
operationId: ListInstances

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
)
// NewCreateCredentialsParams creates a new CreateCredentialsParams 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 NewCreateCredentialsParams() *CreateCredentialsParams {
return &CreateCredentialsParams{
timeout: cr.DefaultTimeout,
}
}
// NewCreateCredentialsParamsWithTimeout creates a new CreateCredentialsParams object
// with the ability to set a timeout on a request.
func NewCreateCredentialsParamsWithTimeout(timeout time.Duration) *CreateCredentialsParams {
return &CreateCredentialsParams{
timeout: timeout,
}
}
// NewCreateCredentialsParamsWithContext creates a new CreateCredentialsParams object
// with the ability to set a context for a request.
func NewCreateCredentialsParamsWithContext(ctx context.Context) *CreateCredentialsParams {
return &CreateCredentialsParams{
Context: ctx,
}
}
// NewCreateCredentialsParamsWithHTTPClient creates a new CreateCredentialsParams object
// with the ability to set a custom HTTPClient for a request.
func NewCreateCredentialsParamsWithHTTPClient(client *http.Client) *CreateCredentialsParams {
return &CreateCredentialsParams{
HTTPClient: client,
}
}
/*
CreateCredentialsParams contains all the parameters to send to the API endpoint
for the create credentials operation.
Typically these are written to a http.Request.
*/
type CreateCredentialsParams struct {
/* Body.
Parameters used when creating a GitHub credential.
*/
Body garm_params.CreateGithubCredentialsParams
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the create credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateCredentialsParams) WithDefaults() *CreateCredentialsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the create credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateCredentialsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the create credentials params
func (o *CreateCredentialsParams) WithTimeout(timeout time.Duration) *CreateCredentialsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the create credentials params
func (o *CreateCredentialsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the create credentials params
func (o *CreateCredentialsParams) WithContext(ctx context.Context) *CreateCredentialsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the create credentials params
func (o *CreateCredentialsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the create credentials params
func (o *CreateCredentialsParams) WithHTTPClient(client *http.Client) *CreateCredentialsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the create credentials params
func (o *CreateCredentialsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the create credentials params
func (o *CreateCredentialsParams) WithBody(body garm_params.CreateGithubCredentialsParams) *CreateCredentialsParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the create credentials params
func (o *CreateCredentialsParams) SetBody(body garm_params.CreateGithubCredentialsParams) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *CreateCredentialsParams) 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
}

View file

@ -0,0 +1,174 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
)
// CreateCredentialsReader is a Reader for the CreateCredentials structure.
type CreateCredentialsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *CreateCredentialsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewCreateCredentialsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewCreateCredentialsBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[POST /github/credentials] CreateCredentials", response, response.Code())
}
}
// NewCreateCredentialsOK creates a CreateCredentialsOK with default headers values
func NewCreateCredentialsOK() *CreateCredentialsOK {
return &CreateCredentialsOK{}
}
/*
CreateCredentialsOK describes a response with status code 200, with default header values.
GithubCredentials
*/
type CreateCredentialsOK struct {
Payload garm_params.GithubCredentials
}
// IsSuccess returns true when this create credentials o k response has a 2xx status code
func (o *CreateCredentialsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this create credentials o k response has a 3xx status code
func (o *CreateCredentialsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this create credentials o k response has a 4xx status code
func (o *CreateCredentialsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this create credentials o k response has a 5xx status code
func (o *CreateCredentialsOK) IsServerError() bool {
return false
}
// IsCode returns true when this create credentials o k response a status code equal to that given
func (o *CreateCredentialsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the create credentials o k response
func (o *CreateCredentialsOK) Code() int {
return 200
}
func (o *CreateCredentialsOK) Error() string {
return fmt.Sprintf("[POST /github/credentials][%d] createCredentialsOK %+v", 200, o.Payload)
}
func (o *CreateCredentialsOK) String() string {
return fmt.Sprintf("[POST /github/credentials][%d] createCredentialsOK %+v", 200, o.Payload)
}
func (o *CreateCredentialsOK) GetPayload() garm_params.GithubCredentials {
return o.Payload
}
func (o *CreateCredentialsOK) 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
}
// NewCreateCredentialsBadRequest creates a CreateCredentialsBadRequest with default headers values
func NewCreateCredentialsBadRequest() *CreateCredentialsBadRequest {
return &CreateCredentialsBadRequest{}
}
/*
CreateCredentialsBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type CreateCredentialsBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this create credentials bad request response has a 2xx status code
func (o *CreateCredentialsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this create credentials bad request response has a 3xx status code
func (o *CreateCredentialsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this create credentials bad request response has a 4xx status code
func (o *CreateCredentialsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this create credentials bad request response has a 5xx status code
func (o *CreateCredentialsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this create credentials bad request response a status code equal to that given
func (o *CreateCredentialsBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the create credentials bad request response
func (o *CreateCredentialsBadRequest) Code() int {
return 400
}
func (o *CreateCredentialsBadRequest) Error() string {
return fmt.Sprintf("[POST /github/credentials][%d] createCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *CreateCredentialsBadRequest) String() string {
return fmt.Sprintf("[POST /github/credentials][%d] createCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *CreateCredentialsBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *CreateCredentialsBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -30,11 +30,129 @@ type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
CreateCredentials(params *CreateCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateCredentialsOK, error)
DeleteCredentials(params *DeleteCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
GetCredentials(params *GetCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetCredentialsOK, error)
ListCredentials(params *ListCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListCredentialsOK, error)
UpdateCredentials(params *UpdateCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateCredentialsOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
CreateCredentials creates a git hub credential
*/
func (a *Client) CreateCredentials(params *CreateCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateCredentialsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewCreateCredentialsParams()
}
op := &runtime.ClientOperation{
ID: "CreateCredentials",
Method: "POST",
PathPattern: "/github/credentials",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &CreateCredentialsReader{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.(*CreateCredentialsOK)
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 CreateCredentials: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
DeleteCredentials deletes a git hub credential
*/
func (a *Client) DeleteCredentials(params *DeleteCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error {
// TODO: Validate the params before sending
if params == nil {
params = NewDeleteCredentialsParams()
}
op := &runtime.ClientOperation{
ID: "DeleteCredentials",
Method: "DELETE",
PathPattern: "/github/credentials/{id}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &DeleteCredentialsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
_, err := a.transport.Submit(op)
if err != nil {
return err
}
return nil
}
/*
GetCredentials gets a git hub credential
*/
func (a *Client) GetCredentials(params *GetCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetCredentialsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetCredentialsParams()
}
op := &runtime.ClientOperation{
ID: "GetCredentials",
Method: "GET",
PathPattern: "/github/credentials/{id}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &GetCredentialsReader{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.(*GetCredentialsOK)
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 GetCredentials: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
ListCredentials lists all credentials
*/
@ -46,7 +164,7 @@ func (a *Client) ListCredentials(params *ListCredentialsParams, authInfo runtime
op := &runtime.ClientOperation{
ID: "ListCredentials",
Method: "GET",
PathPattern: "/credentials",
PathPattern: "/github/credentials",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
@ -74,6 +192,45 @@ func (a *Client) ListCredentials(params *ListCredentialsParams, authInfo runtime
panic(msg)
}
/*
UpdateCredentials updates a git hub credential
*/
func (a *Client) UpdateCredentials(params *UpdateCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateCredentialsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewUpdateCredentialsParams()
}
op := &runtime.ClientOperation{
ID: "UpdateCredentials",
Method: "PUT",
PathPattern: "/github/credentials/{id}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &UpdateCredentialsReader{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.(*UpdateCredentialsOK)
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 UpdateCredentials: 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

View file

@ -0,0 +1,152 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
"github.com/go-openapi/swag"
)
// NewDeleteCredentialsParams creates a new DeleteCredentialsParams 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 NewDeleteCredentialsParams() *DeleteCredentialsParams {
return &DeleteCredentialsParams{
timeout: cr.DefaultTimeout,
}
}
// NewDeleteCredentialsParamsWithTimeout creates a new DeleteCredentialsParams object
// with the ability to set a timeout on a request.
func NewDeleteCredentialsParamsWithTimeout(timeout time.Duration) *DeleteCredentialsParams {
return &DeleteCredentialsParams{
timeout: timeout,
}
}
// NewDeleteCredentialsParamsWithContext creates a new DeleteCredentialsParams object
// with the ability to set a context for a request.
func NewDeleteCredentialsParamsWithContext(ctx context.Context) *DeleteCredentialsParams {
return &DeleteCredentialsParams{
Context: ctx,
}
}
// NewDeleteCredentialsParamsWithHTTPClient creates a new DeleteCredentialsParams object
// with the ability to set a custom HTTPClient for a request.
func NewDeleteCredentialsParamsWithHTTPClient(client *http.Client) *DeleteCredentialsParams {
return &DeleteCredentialsParams{
HTTPClient: client,
}
}
/*
DeleteCredentialsParams contains all the parameters to send to the API endpoint
for the delete credentials operation.
Typically these are written to a http.Request.
*/
type DeleteCredentialsParams struct {
/* ID.
ID of the GitHub credential.
*/
ID int64
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the delete credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteCredentialsParams) WithDefaults() *DeleteCredentialsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the delete credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteCredentialsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the delete credentials params
func (o *DeleteCredentialsParams) WithTimeout(timeout time.Duration) *DeleteCredentialsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the delete credentials params
func (o *DeleteCredentialsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the delete credentials params
func (o *DeleteCredentialsParams) WithContext(ctx context.Context) *DeleteCredentialsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the delete credentials params
func (o *DeleteCredentialsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the delete credentials params
func (o *DeleteCredentialsParams) WithHTTPClient(client *http.Client) *DeleteCredentialsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the delete credentials params
func (o *DeleteCredentialsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithID adds the id to the delete credentials params
func (o *DeleteCredentialsParams) WithID(id int64) *DeleteCredentialsParams {
o.SetID(id)
return o
}
// SetID adds the id to the delete credentials params
func (o *DeleteCredentialsParams) SetID(id int64) {
o.ID = id
}
// WriteToRequest writes these params to a swagger request
func (o *DeleteCredentialsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param id
if err := r.SetPathParam("id", swag.FormatInt64(o.ID)); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,103 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
)
// DeleteCredentialsReader is a Reader for the DeleteCredentials structure.
type DeleteCredentialsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *DeleteCredentialsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
result := NewDeleteCredentialsDefault(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
}
// NewDeleteCredentialsDefault creates a DeleteCredentialsDefault with default headers values
func NewDeleteCredentialsDefault(code int) *DeleteCredentialsDefault {
return &DeleteCredentialsDefault{
_statusCode: code,
}
}
/*
DeleteCredentialsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type DeleteCredentialsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this delete credentials default response has a 2xx status code
func (o *DeleteCredentialsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this delete credentials default response has a 3xx status code
func (o *DeleteCredentialsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this delete credentials default response has a 4xx status code
func (o *DeleteCredentialsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this delete credentials default response has a 5xx status code
func (o *DeleteCredentialsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this delete credentials default response a status code equal to that given
func (o *DeleteCredentialsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the delete credentials default response
func (o *DeleteCredentialsDefault) Code() int {
return o._statusCode
}
func (o *DeleteCredentialsDefault) Error() string {
return fmt.Sprintf("[DELETE /github/credentials/{id}][%d] DeleteCredentials default %+v", o._statusCode, o.Payload)
}
func (o *DeleteCredentialsDefault) String() string {
return fmt.Sprintf("[DELETE /github/credentials/{id}][%d] DeleteCredentials default %+v", o._statusCode, o.Payload)
}
func (o *DeleteCredentialsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *DeleteCredentialsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,152 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
"github.com/go-openapi/swag"
)
// NewGetCredentialsParams creates a new GetCredentialsParams 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 NewGetCredentialsParams() *GetCredentialsParams {
return &GetCredentialsParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetCredentialsParamsWithTimeout creates a new GetCredentialsParams object
// with the ability to set a timeout on a request.
func NewGetCredentialsParamsWithTimeout(timeout time.Duration) *GetCredentialsParams {
return &GetCredentialsParams{
timeout: timeout,
}
}
// NewGetCredentialsParamsWithContext creates a new GetCredentialsParams object
// with the ability to set a context for a request.
func NewGetCredentialsParamsWithContext(ctx context.Context) *GetCredentialsParams {
return &GetCredentialsParams{
Context: ctx,
}
}
// NewGetCredentialsParamsWithHTTPClient creates a new GetCredentialsParams object
// with the ability to set a custom HTTPClient for a request.
func NewGetCredentialsParamsWithHTTPClient(client *http.Client) *GetCredentialsParams {
return &GetCredentialsParams{
HTTPClient: client,
}
}
/*
GetCredentialsParams contains all the parameters to send to the API endpoint
for the get credentials operation.
Typically these are written to a http.Request.
*/
type GetCredentialsParams struct {
/* ID.
ID of the GitHub credential.
*/
ID int64
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetCredentialsParams) WithDefaults() *GetCredentialsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetCredentialsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get credentials params
func (o *GetCredentialsParams) WithTimeout(timeout time.Duration) *GetCredentialsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the get credentials params
func (o *GetCredentialsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the get credentials params
func (o *GetCredentialsParams) WithContext(ctx context.Context) *GetCredentialsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the get credentials params
func (o *GetCredentialsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the get credentials params
func (o *GetCredentialsParams) WithHTTPClient(client *http.Client) *GetCredentialsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the get credentials params
func (o *GetCredentialsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithID adds the id to the get credentials params
func (o *GetCredentialsParams) WithID(id int64) *GetCredentialsParams {
o.SetID(id)
return o
}
// SetID adds the id to the get credentials params
func (o *GetCredentialsParams) SetID(id int64) {
o.ID = id
}
// WriteToRequest writes these params to a swagger request
func (o *GetCredentialsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param id
if err := r.SetPathParam("id", swag.FormatInt64(o.ID)); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,174 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
)
// GetCredentialsReader is a Reader for the GetCredentials structure.
type GetCredentialsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *GetCredentialsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewGetCredentialsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewGetCredentialsBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[GET /github/credentials/{id}] GetCredentials", response, response.Code())
}
}
// NewGetCredentialsOK creates a GetCredentialsOK with default headers values
func NewGetCredentialsOK() *GetCredentialsOK {
return &GetCredentialsOK{}
}
/*
GetCredentialsOK describes a response with status code 200, with default header values.
GithubCredentials
*/
type GetCredentialsOK struct {
Payload garm_params.GithubCredentials
}
// IsSuccess returns true when this get credentials o k response has a 2xx status code
func (o *GetCredentialsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get credentials o k response has a 3xx status code
func (o *GetCredentialsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get credentials o k response has a 4xx status code
func (o *GetCredentialsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get credentials o k response has a 5xx status code
func (o *GetCredentialsOK) IsServerError() bool {
return false
}
// IsCode returns true when this get credentials o k response a status code equal to that given
func (o *GetCredentialsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the get credentials o k response
func (o *GetCredentialsOK) Code() int {
return 200
}
func (o *GetCredentialsOK) Error() string {
return fmt.Sprintf("[GET /github/credentials/{id}][%d] getCredentialsOK %+v", 200, o.Payload)
}
func (o *GetCredentialsOK) String() string {
return fmt.Sprintf("[GET /github/credentials/{id}][%d] getCredentialsOK %+v", 200, o.Payload)
}
func (o *GetCredentialsOK) GetPayload() garm_params.GithubCredentials {
return o.Payload
}
func (o *GetCredentialsOK) 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
}
// NewGetCredentialsBadRequest creates a GetCredentialsBadRequest with default headers values
func NewGetCredentialsBadRequest() *GetCredentialsBadRequest {
return &GetCredentialsBadRequest{}
}
/*
GetCredentialsBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type GetCredentialsBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this get credentials bad request response has a 2xx status code
func (o *GetCredentialsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get credentials bad request response has a 3xx status code
func (o *GetCredentialsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this get credentials bad request response has a 4xx status code
func (o *GetCredentialsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this get credentials bad request response has a 5xx status code
func (o *GetCredentialsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this get credentials bad request response a status code equal to that given
func (o *GetCredentialsBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the get credentials bad request response
func (o *GetCredentialsBadRequest) Code() int {
return 400
}
func (o *GetCredentialsBadRequest) Error() string {
return fmt.Sprintf("[GET /github/credentials/{id}][%d] getCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *GetCredentialsBadRequest) String() string {
return fmt.Sprintf("[GET /github/credentials/{id}][%d] getCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *GetCredentialsBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *GetCredentialsBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -37,7 +37,7 @@ func (o *ListCredentialsReader) ReadResponse(response runtime.ClientResponse, co
}
return nil, result
default:
return nil, runtime.NewAPIError("[GET /credentials] ListCredentials", response, response.Code())
return nil, runtime.NewAPIError("[GET /github/credentials] ListCredentials", response, response.Code())
}
}
@ -86,11 +86,11 @@ func (o *ListCredentialsOK) Code() int {
}
func (o *ListCredentialsOK) Error() string {
return fmt.Sprintf("[GET /credentials][%d] listCredentialsOK %+v", 200, o.Payload)
return fmt.Sprintf("[GET /github/credentials][%d] listCredentialsOK %+v", 200, o.Payload)
}
func (o *ListCredentialsOK) String() string {
return fmt.Sprintf("[GET /credentials][%d] listCredentialsOK %+v", 200, o.Payload)
return fmt.Sprintf("[GET /github/credentials][%d] listCredentialsOK %+v", 200, o.Payload)
}
func (o *ListCredentialsOK) GetPayload() garm_params.Credentials {
@ -152,11 +152,11 @@ func (o *ListCredentialsBadRequest) Code() int {
}
func (o *ListCredentialsBadRequest) Error() string {
return fmt.Sprintf("[GET /credentials][%d] listCredentialsBadRequest %+v", 400, o.Payload)
return fmt.Sprintf("[GET /github/credentials][%d] listCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *ListCredentialsBadRequest) String() string {
return fmt.Sprintf("[GET /credentials][%d] listCredentialsBadRequest %+v", 400, o.Payload)
return fmt.Sprintf("[GET /github/credentials][%d] listCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *ListCredentialsBadRequest) GetPayload() apiserver_params.APIErrorResponse {

View file

@ -0,0 +1,174 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
"github.com/go-openapi/swag"
garm_params "github.com/cloudbase/garm/params"
)
// NewUpdateCredentialsParams creates a new UpdateCredentialsParams 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 NewUpdateCredentialsParams() *UpdateCredentialsParams {
return &UpdateCredentialsParams{
timeout: cr.DefaultTimeout,
}
}
// NewUpdateCredentialsParamsWithTimeout creates a new UpdateCredentialsParams object
// with the ability to set a timeout on a request.
func NewUpdateCredentialsParamsWithTimeout(timeout time.Duration) *UpdateCredentialsParams {
return &UpdateCredentialsParams{
timeout: timeout,
}
}
// NewUpdateCredentialsParamsWithContext creates a new UpdateCredentialsParams object
// with the ability to set a context for a request.
func NewUpdateCredentialsParamsWithContext(ctx context.Context) *UpdateCredentialsParams {
return &UpdateCredentialsParams{
Context: ctx,
}
}
// NewUpdateCredentialsParamsWithHTTPClient creates a new UpdateCredentialsParams object
// with the ability to set a custom HTTPClient for a request.
func NewUpdateCredentialsParamsWithHTTPClient(client *http.Client) *UpdateCredentialsParams {
return &UpdateCredentialsParams{
HTTPClient: client,
}
}
/*
UpdateCredentialsParams contains all the parameters to send to the API endpoint
for the update credentials operation.
Typically these are written to a http.Request.
*/
type UpdateCredentialsParams struct {
/* Body.
Parameters used when updating a GitHub credential.
*/
Body garm_params.UpdateGithubCredentialsParams
/* ID.
ID of the GitHub credential.
*/
ID int64
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the update credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateCredentialsParams) WithDefaults() *UpdateCredentialsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the update credentials params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateCredentialsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the update credentials params
func (o *UpdateCredentialsParams) WithTimeout(timeout time.Duration) *UpdateCredentialsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the update credentials params
func (o *UpdateCredentialsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the update credentials params
func (o *UpdateCredentialsParams) WithContext(ctx context.Context) *UpdateCredentialsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the update credentials params
func (o *UpdateCredentialsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the update credentials params
func (o *UpdateCredentialsParams) WithHTTPClient(client *http.Client) *UpdateCredentialsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the update credentials params
func (o *UpdateCredentialsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the update credentials params
func (o *UpdateCredentialsParams) WithBody(body garm_params.UpdateGithubCredentialsParams) *UpdateCredentialsParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the update credentials params
func (o *UpdateCredentialsParams) SetBody(body garm_params.UpdateGithubCredentialsParams) {
o.Body = body
}
// WithID adds the id to the update credentials params
func (o *UpdateCredentialsParams) WithID(id int64) *UpdateCredentialsParams {
o.SetID(id)
return o
}
// SetID adds the id to the update credentials params
func (o *UpdateCredentialsParams) SetID(id int64) {
o.ID = id
}
// WriteToRequest writes these params to a swagger request
func (o *UpdateCredentialsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param id
if err := r.SetPathParam("id", swag.FormatInt64(o.ID)); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,174 @@
// Code generated by go-swagger; DO NOT EDIT.
package credentials
// 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"
)
// UpdateCredentialsReader is a Reader for the UpdateCredentials structure.
type UpdateCredentialsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *UpdateCredentialsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewUpdateCredentialsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewUpdateCredentialsBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[PUT /github/credentials/{id}] UpdateCredentials", response, response.Code())
}
}
// NewUpdateCredentialsOK creates a UpdateCredentialsOK with default headers values
func NewUpdateCredentialsOK() *UpdateCredentialsOK {
return &UpdateCredentialsOK{}
}
/*
UpdateCredentialsOK describes a response with status code 200, with default header values.
GithubCredentials
*/
type UpdateCredentialsOK struct {
Payload garm_params.GithubCredentials
}
// IsSuccess returns true when this update credentials o k response has a 2xx status code
func (o *UpdateCredentialsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this update credentials o k response has a 3xx status code
func (o *UpdateCredentialsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this update credentials o k response has a 4xx status code
func (o *UpdateCredentialsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this update credentials o k response has a 5xx status code
func (o *UpdateCredentialsOK) IsServerError() bool {
return false
}
// IsCode returns true when this update credentials o k response a status code equal to that given
func (o *UpdateCredentialsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the update credentials o k response
func (o *UpdateCredentialsOK) Code() int {
return 200
}
func (o *UpdateCredentialsOK) Error() string {
return fmt.Sprintf("[PUT /github/credentials/{id}][%d] updateCredentialsOK %+v", 200, o.Payload)
}
func (o *UpdateCredentialsOK) String() string {
return fmt.Sprintf("[PUT /github/credentials/{id}][%d] updateCredentialsOK %+v", 200, o.Payload)
}
func (o *UpdateCredentialsOK) GetPayload() garm_params.GithubCredentials {
return o.Payload
}
func (o *UpdateCredentialsOK) 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
}
// NewUpdateCredentialsBadRequest creates a UpdateCredentialsBadRequest with default headers values
func NewUpdateCredentialsBadRequest() *UpdateCredentialsBadRequest {
return &UpdateCredentialsBadRequest{}
}
/*
UpdateCredentialsBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type UpdateCredentialsBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this update credentials bad request response has a 2xx status code
func (o *UpdateCredentialsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this update credentials bad request response has a 3xx status code
func (o *UpdateCredentialsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this update credentials bad request response has a 4xx status code
func (o *UpdateCredentialsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this update credentials bad request response has a 5xx status code
func (o *UpdateCredentialsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this update credentials bad request response a status code equal to that given
func (o *UpdateCredentialsBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the update credentials bad request response
func (o *UpdateCredentialsBadRequest) Code() int {
return 400
}
func (o *UpdateCredentialsBadRequest) Error() string {
return fmt.Sprintf("[PUT /github/credentials/{id}][%d] updateCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *UpdateCredentialsBadRequest) String() string {
return fmt.Sprintf("[PUT /github/credentials/{id}][%d] updateCredentialsBadRequest %+v", 400, o.Payload)
}
func (o *UpdateCredentialsBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *UpdateCredentialsBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// NewCreateGithubEndpointParams creates a new CreateGithubEndpointParams 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 NewCreateGithubEndpointParams() *CreateGithubEndpointParams {
return &CreateGithubEndpointParams{
timeout: cr.DefaultTimeout,
}
}
// NewCreateGithubEndpointParamsWithTimeout creates a new CreateGithubEndpointParams object
// with the ability to set a timeout on a request.
func NewCreateGithubEndpointParamsWithTimeout(timeout time.Duration) *CreateGithubEndpointParams {
return &CreateGithubEndpointParams{
timeout: timeout,
}
}
// NewCreateGithubEndpointParamsWithContext creates a new CreateGithubEndpointParams object
// with the ability to set a context for a request.
func NewCreateGithubEndpointParamsWithContext(ctx context.Context) *CreateGithubEndpointParams {
return &CreateGithubEndpointParams{
Context: ctx,
}
}
// NewCreateGithubEndpointParamsWithHTTPClient creates a new CreateGithubEndpointParams object
// with the ability to set a custom HTTPClient for a request.
func NewCreateGithubEndpointParamsWithHTTPClient(client *http.Client) *CreateGithubEndpointParams {
return &CreateGithubEndpointParams{
HTTPClient: client,
}
}
/*
CreateGithubEndpointParams contains all the parameters to send to the API endpoint
for the create github endpoint operation.
Typically these are written to a http.Request.
*/
type CreateGithubEndpointParams struct {
/* Body.
Parameters used when creating a GitHub endpoint.
*/
Body garm_params.CreateGithubEndpointParams
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the create github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateGithubEndpointParams) WithDefaults() *CreateGithubEndpointParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the create github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *CreateGithubEndpointParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the create github endpoint params
func (o *CreateGithubEndpointParams) WithTimeout(timeout time.Duration) *CreateGithubEndpointParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the create github endpoint params
func (o *CreateGithubEndpointParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the create github endpoint params
func (o *CreateGithubEndpointParams) WithContext(ctx context.Context) *CreateGithubEndpointParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the create github endpoint params
func (o *CreateGithubEndpointParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the create github endpoint params
func (o *CreateGithubEndpointParams) WithHTTPClient(client *http.Client) *CreateGithubEndpointParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the create github endpoint params
func (o *CreateGithubEndpointParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the create github endpoint params
func (o *CreateGithubEndpointParams) WithBody(body garm_params.CreateGithubEndpointParams) *CreateGithubEndpointParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the create github endpoint params
func (o *CreateGithubEndpointParams) SetBody(body garm_params.CreateGithubEndpointParams) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *CreateGithubEndpointParams) 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
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// CreateGithubEndpointReader is a Reader for the CreateGithubEndpoint structure.
type CreateGithubEndpointReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *CreateGithubEndpointReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewCreateGithubEndpointOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewCreateGithubEndpointDefault(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
}
}
// NewCreateGithubEndpointOK creates a CreateGithubEndpointOK with default headers values
func NewCreateGithubEndpointOK() *CreateGithubEndpointOK {
return &CreateGithubEndpointOK{}
}
/*
CreateGithubEndpointOK describes a response with status code 200, with default header values.
GithubEndpoint
*/
type CreateGithubEndpointOK struct {
Payload garm_params.GithubEndpoint
}
// IsSuccess returns true when this create github endpoint o k response has a 2xx status code
func (o *CreateGithubEndpointOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this create github endpoint o k response has a 3xx status code
func (o *CreateGithubEndpointOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this create github endpoint o k response has a 4xx status code
func (o *CreateGithubEndpointOK) IsClientError() bool {
return false
}
// IsServerError returns true when this create github endpoint o k response has a 5xx status code
func (o *CreateGithubEndpointOK) IsServerError() bool {
return false
}
// IsCode returns true when this create github endpoint o k response a status code equal to that given
func (o *CreateGithubEndpointOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the create github endpoint o k response
func (o *CreateGithubEndpointOK) Code() int {
return 200
}
func (o *CreateGithubEndpointOK) Error() string {
return fmt.Sprintf("[POST /github/endpoints][%d] createGithubEndpointOK %+v", 200, o.Payload)
}
func (o *CreateGithubEndpointOK) String() string {
return fmt.Sprintf("[POST /github/endpoints][%d] createGithubEndpointOK %+v", 200, o.Payload)
}
func (o *CreateGithubEndpointOK) GetPayload() garm_params.GithubEndpoint {
return o.Payload
}
func (o *CreateGithubEndpointOK) 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
}
// NewCreateGithubEndpointDefault creates a CreateGithubEndpointDefault with default headers values
func NewCreateGithubEndpointDefault(code int) *CreateGithubEndpointDefault {
return &CreateGithubEndpointDefault{
_statusCode: code,
}
}
/*
CreateGithubEndpointDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type CreateGithubEndpointDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this create github endpoint default response has a 2xx status code
func (o *CreateGithubEndpointDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this create github endpoint default response has a 3xx status code
func (o *CreateGithubEndpointDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this create github endpoint default response has a 4xx status code
func (o *CreateGithubEndpointDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this create github endpoint default response has a 5xx status code
func (o *CreateGithubEndpointDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this create github endpoint default response a status code equal to that given
func (o *CreateGithubEndpointDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the create github endpoint default response
func (o *CreateGithubEndpointDefault) Code() int {
return o._statusCode
}
func (o *CreateGithubEndpointDefault) Error() string {
return fmt.Sprintf("[POST /github/endpoints][%d] CreateGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *CreateGithubEndpointDefault) String() string {
return fmt.Sprintf("[POST /github/endpoints][%d] CreateGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *CreateGithubEndpointDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *CreateGithubEndpointDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// NewDeleteGithubEndpointParams creates a new DeleteGithubEndpointParams 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 NewDeleteGithubEndpointParams() *DeleteGithubEndpointParams {
return &DeleteGithubEndpointParams{
timeout: cr.DefaultTimeout,
}
}
// NewDeleteGithubEndpointParamsWithTimeout creates a new DeleteGithubEndpointParams object
// with the ability to set a timeout on a request.
func NewDeleteGithubEndpointParamsWithTimeout(timeout time.Duration) *DeleteGithubEndpointParams {
return &DeleteGithubEndpointParams{
timeout: timeout,
}
}
// NewDeleteGithubEndpointParamsWithContext creates a new DeleteGithubEndpointParams object
// with the ability to set a context for a request.
func NewDeleteGithubEndpointParamsWithContext(ctx context.Context) *DeleteGithubEndpointParams {
return &DeleteGithubEndpointParams{
Context: ctx,
}
}
// NewDeleteGithubEndpointParamsWithHTTPClient creates a new DeleteGithubEndpointParams object
// with the ability to set a custom HTTPClient for a request.
func NewDeleteGithubEndpointParamsWithHTTPClient(client *http.Client) *DeleteGithubEndpointParams {
return &DeleteGithubEndpointParams{
HTTPClient: client,
}
}
/*
DeleteGithubEndpointParams contains all the parameters to send to the API endpoint
for the delete github endpoint operation.
Typically these are written to a http.Request.
*/
type DeleteGithubEndpointParams struct {
/* Name.
The name of the GitHub endpoint.
*/
Name string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the delete github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteGithubEndpointParams) WithDefaults() *DeleteGithubEndpointParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the delete github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteGithubEndpointParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the delete github endpoint params
func (o *DeleteGithubEndpointParams) WithTimeout(timeout time.Duration) *DeleteGithubEndpointParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the delete github endpoint params
func (o *DeleteGithubEndpointParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the delete github endpoint params
func (o *DeleteGithubEndpointParams) WithContext(ctx context.Context) *DeleteGithubEndpointParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the delete github endpoint params
func (o *DeleteGithubEndpointParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the delete github endpoint params
func (o *DeleteGithubEndpointParams) WithHTTPClient(client *http.Client) *DeleteGithubEndpointParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the delete github endpoint params
func (o *DeleteGithubEndpointParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithName adds the name to the delete github endpoint params
func (o *DeleteGithubEndpointParams) WithName(name string) *DeleteGithubEndpointParams {
o.SetName(name)
return o
}
// SetName adds the name to the delete github endpoint params
func (o *DeleteGithubEndpointParams) SetName(name string) {
o.Name = name
}
// WriteToRequest writes these params to a swagger request
func (o *DeleteGithubEndpointParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param name
if err := r.SetPathParam("name", o.Name); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,103 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// DeleteGithubEndpointReader is a Reader for the DeleteGithubEndpoint structure.
type DeleteGithubEndpointReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *DeleteGithubEndpointReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
result := NewDeleteGithubEndpointDefault(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
}
// NewDeleteGithubEndpointDefault creates a DeleteGithubEndpointDefault with default headers values
func NewDeleteGithubEndpointDefault(code int) *DeleteGithubEndpointDefault {
return &DeleteGithubEndpointDefault{
_statusCode: code,
}
}
/*
DeleteGithubEndpointDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type DeleteGithubEndpointDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this delete github endpoint default response has a 2xx status code
func (o *DeleteGithubEndpointDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this delete github endpoint default response has a 3xx status code
func (o *DeleteGithubEndpointDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this delete github endpoint default response has a 4xx status code
func (o *DeleteGithubEndpointDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this delete github endpoint default response has a 5xx status code
func (o *DeleteGithubEndpointDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this delete github endpoint default response a status code equal to that given
func (o *DeleteGithubEndpointDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the delete github endpoint default response
func (o *DeleteGithubEndpointDefault) Code() int {
return o._statusCode
}
func (o *DeleteGithubEndpointDefault) Error() string {
return fmt.Sprintf("[DELETE /github/endpoints/{name}][%d] DeleteGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *DeleteGithubEndpointDefault) String() string {
return fmt.Sprintf("[DELETE /github/endpoints/{name}][%d] DeleteGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *DeleteGithubEndpointDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *DeleteGithubEndpointDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,231 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)
// New creates a new endpoints API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
/*
Client for endpoints 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 {
CreateGithubEndpoint(params *CreateGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateGithubEndpointOK, error)
DeleteGithubEndpoint(params *DeleteGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
GetGithubEndpoint(params *GetGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetGithubEndpointOK, error)
ListGithubEndpoints(params *ListGithubEndpointsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListGithubEndpointsOK, error)
UpdateGithubEndpoint(params *UpdateGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateGithubEndpointOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
CreateGithubEndpoint creates a git hub endpoint
*/
func (a *Client) CreateGithubEndpoint(params *CreateGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*CreateGithubEndpointOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewCreateGithubEndpointParams()
}
op := &runtime.ClientOperation{
ID: "CreateGithubEndpoint",
Method: "POST",
PathPattern: "/github/endpoints",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &CreateGithubEndpointReader{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.(*CreateGithubEndpointOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*CreateGithubEndpointDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
DeleteGithubEndpoint deletes a git hub endpoint
*/
func (a *Client) DeleteGithubEndpoint(params *DeleteGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error {
// TODO: Validate the params before sending
if params == nil {
params = NewDeleteGithubEndpointParams()
}
op := &runtime.ClientOperation{
ID: "DeleteGithubEndpoint",
Method: "DELETE",
PathPattern: "/github/endpoints/{name}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &DeleteGithubEndpointReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
_, err := a.transport.Submit(op)
if err != nil {
return err
}
return nil
}
/*
GetGithubEndpoint gets a git hub endpoint
*/
func (a *Client) GetGithubEndpoint(params *GetGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetGithubEndpointOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetGithubEndpointParams()
}
op := &runtime.ClientOperation{
ID: "GetGithubEndpoint",
Method: "GET",
PathPattern: "/github/endpoints/{name}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &GetGithubEndpointReader{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.(*GetGithubEndpointOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*GetGithubEndpointDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ListGithubEndpoints lists all git hub endpoints
*/
func (a *Client) ListGithubEndpoints(params *ListGithubEndpointsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListGithubEndpointsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListGithubEndpointsParams()
}
op := &runtime.ClientOperation{
ID: "ListGithubEndpoints",
Method: "GET",
PathPattern: "/github/endpoints",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListGithubEndpointsReader{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.(*ListGithubEndpointsOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ListGithubEndpointsDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
UpdateGithubEndpoint updates a git hub endpoint
*/
func (a *Client) UpdateGithubEndpoint(params *UpdateGithubEndpointParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateGithubEndpointOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewUpdateGithubEndpointParams()
}
op := &runtime.ClientOperation{
ID: "UpdateGithubEndpoint",
Method: "PUT",
PathPattern: "/github/endpoints/{name}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &UpdateGithubEndpointReader{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.(*UpdateGithubEndpointOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*UpdateGithubEndpointDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// NewGetGithubEndpointParams creates a new GetGithubEndpointParams 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 NewGetGithubEndpointParams() *GetGithubEndpointParams {
return &GetGithubEndpointParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetGithubEndpointParamsWithTimeout creates a new GetGithubEndpointParams object
// with the ability to set a timeout on a request.
func NewGetGithubEndpointParamsWithTimeout(timeout time.Duration) *GetGithubEndpointParams {
return &GetGithubEndpointParams{
timeout: timeout,
}
}
// NewGetGithubEndpointParamsWithContext creates a new GetGithubEndpointParams object
// with the ability to set a context for a request.
func NewGetGithubEndpointParamsWithContext(ctx context.Context) *GetGithubEndpointParams {
return &GetGithubEndpointParams{
Context: ctx,
}
}
// NewGetGithubEndpointParamsWithHTTPClient creates a new GetGithubEndpointParams object
// with the ability to set a custom HTTPClient for a request.
func NewGetGithubEndpointParamsWithHTTPClient(client *http.Client) *GetGithubEndpointParams {
return &GetGithubEndpointParams{
HTTPClient: client,
}
}
/*
GetGithubEndpointParams contains all the parameters to send to the API endpoint
for the get github endpoint operation.
Typically these are written to a http.Request.
*/
type GetGithubEndpointParams struct {
/* Name.
The name of the GitHub endpoint.
*/
Name string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetGithubEndpointParams) WithDefaults() *GetGithubEndpointParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetGithubEndpointParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get github endpoint params
func (o *GetGithubEndpointParams) WithTimeout(timeout time.Duration) *GetGithubEndpointParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the get github endpoint params
func (o *GetGithubEndpointParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the get github endpoint params
func (o *GetGithubEndpointParams) WithContext(ctx context.Context) *GetGithubEndpointParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the get github endpoint params
func (o *GetGithubEndpointParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the get github endpoint params
func (o *GetGithubEndpointParams) WithHTTPClient(client *http.Client) *GetGithubEndpointParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the get github endpoint params
func (o *GetGithubEndpointParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithName adds the name to the get github endpoint params
func (o *GetGithubEndpointParams) WithName(name string) *GetGithubEndpointParams {
o.SetName(name)
return o
}
// SetName adds the name to the get github endpoint params
func (o *GetGithubEndpointParams) SetName(name string) {
o.Name = name
}
// WriteToRequest writes these params to a swagger request
func (o *GetGithubEndpointParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param name
if err := r.SetPathParam("name", o.Name); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// GetGithubEndpointReader is a Reader for the GetGithubEndpoint structure.
type GetGithubEndpointReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *GetGithubEndpointReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewGetGithubEndpointOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewGetGithubEndpointDefault(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
}
}
// NewGetGithubEndpointOK creates a GetGithubEndpointOK with default headers values
func NewGetGithubEndpointOK() *GetGithubEndpointOK {
return &GetGithubEndpointOK{}
}
/*
GetGithubEndpointOK describes a response with status code 200, with default header values.
GithubEndpoint
*/
type GetGithubEndpointOK struct {
Payload garm_params.GithubEndpoint
}
// IsSuccess returns true when this get github endpoint o k response has a 2xx status code
func (o *GetGithubEndpointOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get github endpoint o k response has a 3xx status code
func (o *GetGithubEndpointOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get github endpoint o k response has a 4xx status code
func (o *GetGithubEndpointOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get github endpoint o k response has a 5xx status code
func (o *GetGithubEndpointOK) IsServerError() bool {
return false
}
// IsCode returns true when this get github endpoint o k response a status code equal to that given
func (o *GetGithubEndpointOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the get github endpoint o k response
func (o *GetGithubEndpointOK) Code() int {
return 200
}
func (o *GetGithubEndpointOK) Error() string {
return fmt.Sprintf("[GET /github/endpoints/{name}][%d] getGithubEndpointOK %+v", 200, o.Payload)
}
func (o *GetGithubEndpointOK) String() string {
return fmt.Sprintf("[GET /github/endpoints/{name}][%d] getGithubEndpointOK %+v", 200, o.Payload)
}
func (o *GetGithubEndpointOK) GetPayload() garm_params.GithubEndpoint {
return o.Payload
}
func (o *GetGithubEndpointOK) 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
}
// NewGetGithubEndpointDefault creates a GetGithubEndpointDefault with default headers values
func NewGetGithubEndpointDefault(code int) *GetGithubEndpointDefault {
return &GetGithubEndpointDefault{
_statusCode: code,
}
}
/*
GetGithubEndpointDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type GetGithubEndpointDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this get github endpoint default response has a 2xx status code
func (o *GetGithubEndpointDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this get github endpoint default response has a 3xx status code
func (o *GetGithubEndpointDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this get github endpoint default response has a 4xx status code
func (o *GetGithubEndpointDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this get github endpoint default response has a 5xx status code
func (o *GetGithubEndpointDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this get github endpoint default response a status code equal to that given
func (o *GetGithubEndpointDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the get github endpoint default response
func (o *GetGithubEndpointDefault) Code() int {
return o._statusCode
}
func (o *GetGithubEndpointDefault) Error() string {
return fmt.Sprintf("[GET /github/endpoints/{name}][%d] GetGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *GetGithubEndpointDefault) String() string {
return fmt.Sprintf("[GET /github/endpoints/{name}][%d] GetGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *GetGithubEndpointDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *GetGithubEndpointDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,128 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// NewListGithubEndpointsParams creates a new ListGithubEndpointsParams 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 NewListGithubEndpointsParams() *ListGithubEndpointsParams {
return &ListGithubEndpointsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListGithubEndpointsParamsWithTimeout creates a new ListGithubEndpointsParams object
// with the ability to set a timeout on a request.
func NewListGithubEndpointsParamsWithTimeout(timeout time.Duration) *ListGithubEndpointsParams {
return &ListGithubEndpointsParams{
timeout: timeout,
}
}
// NewListGithubEndpointsParamsWithContext creates a new ListGithubEndpointsParams object
// with the ability to set a context for a request.
func NewListGithubEndpointsParamsWithContext(ctx context.Context) *ListGithubEndpointsParams {
return &ListGithubEndpointsParams{
Context: ctx,
}
}
// NewListGithubEndpointsParamsWithHTTPClient creates a new ListGithubEndpointsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListGithubEndpointsParamsWithHTTPClient(client *http.Client) *ListGithubEndpointsParams {
return &ListGithubEndpointsParams{
HTTPClient: client,
}
}
/*
ListGithubEndpointsParams contains all the parameters to send to the API endpoint
for the list github endpoints operation.
Typically these are written to a http.Request.
*/
type ListGithubEndpointsParams struct {
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list github endpoints params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListGithubEndpointsParams) WithDefaults() *ListGithubEndpointsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list github endpoints params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListGithubEndpointsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list github endpoints params
func (o *ListGithubEndpointsParams) WithTimeout(timeout time.Duration) *ListGithubEndpointsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list github endpoints params
func (o *ListGithubEndpointsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list github endpoints params
func (o *ListGithubEndpointsParams) WithContext(ctx context.Context) *ListGithubEndpointsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list github endpoints params
func (o *ListGithubEndpointsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list github endpoints params
func (o *ListGithubEndpointsParams) WithHTTPClient(client *http.Client) *ListGithubEndpointsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list github endpoints params
func (o *ListGithubEndpointsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WriteToRequest writes these params to a swagger request
func (o *ListGithubEndpointsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// ListGithubEndpointsReader is a Reader for the ListGithubEndpoints structure.
type ListGithubEndpointsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListGithubEndpointsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListGithubEndpointsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewListGithubEndpointsDefault(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
}
}
// NewListGithubEndpointsOK creates a ListGithubEndpointsOK with default headers values
func NewListGithubEndpointsOK() *ListGithubEndpointsOK {
return &ListGithubEndpointsOK{}
}
/*
ListGithubEndpointsOK describes a response with status code 200, with default header values.
GithubEndpoints
*/
type ListGithubEndpointsOK struct {
Payload garm_params.GithubEndpoints
}
// IsSuccess returns true when this list github endpoints o k response has a 2xx status code
func (o *ListGithubEndpointsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list github endpoints o k response has a 3xx status code
func (o *ListGithubEndpointsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list github endpoints o k response has a 4xx status code
func (o *ListGithubEndpointsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list github endpoints o k response has a 5xx status code
func (o *ListGithubEndpointsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list github endpoints o k response a status code equal to that given
func (o *ListGithubEndpointsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list github endpoints o k response
func (o *ListGithubEndpointsOK) Code() int {
return 200
}
func (o *ListGithubEndpointsOK) Error() string {
return fmt.Sprintf("[GET /github/endpoints][%d] listGithubEndpointsOK %+v", 200, o.Payload)
}
func (o *ListGithubEndpointsOK) String() string {
return fmt.Sprintf("[GET /github/endpoints][%d] listGithubEndpointsOK %+v", 200, o.Payload)
}
func (o *ListGithubEndpointsOK) GetPayload() garm_params.GithubEndpoints {
return o.Payload
}
func (o *ListGithubEndpointsOK) 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
}
// NewListGithubEndpointsDefault creates a ListGithubEndpointsDefault with default headers values
func NewListGithubEndpointsDefault(code int) *ListGithubEndpointsDefault {
return &ListGithubEndpointsDefault{
_statusCode: code,
}
}
/*
ListGithubEndpointsDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type ListGithubEndpointsDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list github endpoints default response has a 2xx status code
func (o *ListGithubEndpointsDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this list github endpoints default response has a 3xx status code
func (o *ListGithubEndpointsDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this list github endpoints default response has a 4xx status code
func (o *ListGithubEndpointsDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this list github endpoints default response has a 5xx status code
func (o *ListGithubEndpointsDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this list github endpoints default response a status code equal to that given
func (o *ListGithubEndpointsDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the list github endpoints default response
func (o *ListGithubEndpointsDefault) Code() int {
return o._statusCode
}
func (o *ListGithubEndpointsDefault) Error() string {
return fmt.Sprintf("[GET /github/endpoints][%d] ListGithubEndpoints default %+v", o._statusCode, o.Payload)
}
func (o *ListGithubEndpointsDefault) String() string {
return fmt.Sprintf("[GET /github/endpoints][%d] ListGithubEndpoints default %+v", o._statusCode, o.Payload)
}
func (o *ListGithubEndpointsDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListGithubEndpointsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// NewUpdateGithubEndpointParams creates a new UpdateGithubEndpointParams 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 NewUpdateGithubEndpointParams() *UpdateGithubEndpointParams {
return &UpdateGithubEndpointParams{
timeout: cr.DefaultTimeout,
}
}
// NewUpdateGithubEndpointParamsWithTimeout creates a new UpdateGithubEndpointParams object
// with the ability to set a timeout on a request.
func NewUpdateGithubEndpointParamsWithTimeout(timeout time.Duration) *UpdateGithubEndpointParams {
return &UpdateGithubEndpointParams{
timeout: timeout,
}
}
// NewUpdateGithubEndpointParamsWithContext creates a new UpdateGithubEndpointParams object
// with the ability to set a context for a request.
func NewUpdateGithubEndpointParamsWithContext(ctx context.Context) *UpdateGithubEndpointParams {
return &UpdateGithubEndpointParams{
Context: ctx,
}
}
// NewUpdateGithubEndpointParamsWithHTTPClient creates a new UpdateGithubEndpointParams object
// with the ability to set a custom HTTPClient for a request.
func NewUpdateGithubEndpointParamsWithHTTPClient(client *http.Client) *UpdateGithubEndpointParams {
return &UpdateGithubEndpointParams{
HTTPClient: client,
}
}
/*
UpdateGithubEndpointParams contains all the parameters to send to the API endpoint
for the update github endpoint operation.
Typically these are written to a http.Request.
*/
type UpdateGithubEndpointParams struct {
/* Body.
Parameters used when updating a GitHub endpoint.
*/
Body garm_params.UpdateGithubEndpointParams
/* Name.
The name of the GitHub endpoint.
*/
Name string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the update github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateGithubEndpointParams) WithDefaults() *UpdateGithubEndpointParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the update github endpoint params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateGithubEndpointParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the update github endpoint params
func (o *UpdateGithubEndpointParams) WithTimeout(timeout time.Duration) *UpdateGithubEndpointParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the update github endpoint params
func (o *UpdateGithubEndpointParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the update github endpoint params
func (o *UpdateGithubEndpointParams) WithContext(ctx context.Context) *UpdateGithubEndpointParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the update github endpoint params
func (o *UpdateGithubEndpointParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the update github endpoint params
func (o *UpdateGithubEndpointParams) WithHTTPClient(client *http.Client) *UpdateGithubEndpointParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the update github endpoint params
func (o *UpdateGithubEndpointParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the update github endpoint params
func (o *UpdateGithubEndpointParams) WithBody(body garm_params.UpdateGithubEndpointParams) *UpdateGithubEndpointParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the update github endpoint params
func (o *UpdateGithubEndpointParams) SetBody(body garm_params.UpdateGithubEndpointParams) {
o.Body = body
}
// WithName adds the name to the update github endpoint params
func (o *UpdateGithubEndpointParams) WithName(name string) *UpdateGithubEndpointParams {
o.SetName(name)
return o
}
// SetName adds the name to the update github endpoint params
func (o *UpdateGithubEndpointParams) SetName(name string) {
o.Name = name
}
// WriteToRequest writes these params to a swagger request
func (o *UpdateGithubEndpointParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param name
if err := r.SetPathParam("name", o.Name); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package endpoints
// 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"
)
// UpdateGithubEndpointReader is a Reader for the UpdateGithubEndpoint structure.
type UpdateGithubEndpointReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *UpdateGithubEndpointReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewUpdateGithubEndpointOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewUpdateGithubEndpointDefault(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
}
}
// NewUpdateGithubEndpointOK creates a UpdateGithubEndpointOK with default headers values
func NewUpdateGithubEndpointOK() *UpdateGithubEndpointOK {
return &UpdateGithubEndpointOK{}
}
/*
UpdateGithubEndpointOK describes a response with status code 200, with default header values.
GithubEndpoint
*/
type UpdateGithubEndpointOK struct {
Payload garm_params.GithubEndpoint
}
// IsSuccess returns true when this update github endpoint o k response has a 2xx status code
func (o *UpdateGithubEndpointOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this update github endpoint o k response has a 3xx status code
func (o *UpdateGithubEndpointOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this update github endpoint o k response has a 4xx status code
func (o *UpdateGithubEndpointOK) IsClientError() bool {
return false
}
// IsServerError returns true when this update github endpoint o k response has a 5xx status code
func (o *UpdateGithubEndpointOK) IsServerError() bool {
return false
}
// IsCode returns true when this update github endpoint o k response a status code equal to that given
func (o *UpdateGithubEndpointOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the update github endpoint o k response
func (o *UpdateGithubEndpointOK) Code() int {
return 200
}
func (o *UpdateGithubEndpointOK) Error() string {
return fmt.Sprintf("[PUT /github/endpoints/{name}][%d] updateGithubEndpointOK %+v", 200, o.Payload)
}
func (o *UpdateGithubEndpointOK) String() string {
return fmt.Sprintf("[PUT /github/endpoints/{name}][%d] updateGithubEndpointOK %+v", 200, o.Payload)
}
func (o *UpdateGithubEndpointOK) GetPayload() garm_params.GithubEndpoint {
return o.Payload
}
func (o *UpdateGithubEndpointOK) 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
}
// NewUpdateGithubEndpointDefault creates a UpdateGithubEndpointDefault with default headers values
func NewUpdateGithubEndpointDefault(code int) *UpdateGithubEndpointDefault {
return &UpdateGithubEndpointDefault{
_statusCode: code,
}
}
/*
UpdateGithubEndpointDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type UpdateGithubEndpointDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this update github endpoint default response has a 2xx status code
func (o *UpdateGithubEndpointDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this update github endpoint default response has a 3xx status code
func (o *UpdateGithubEndpointDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this update github endpoint default response has a 4xx status code
func (o *UpdateGithubEndpointDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this update github endpoint default response has a 5xx status code
func (o *UpdateGithubEndpointDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this update github endpoint default response a status code equal to that given
func (o *UpdateGithubEndpointDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the update github endpoint default response
func (o *UpdateGithubEndpointDefault) Code() int {
return o._statusCode
}
func (o *UpdateGithubEndpointDefault) Error() string {
return fmt.Sprintf("[PUT /github/endpoints/{name}][%d] UpdateGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *UpdateGithubEndpointDefault) String() string {
return fmt.Sprintf("[PUT /github/endpoints/{name}][%d] UpdateGithubEndpoint default %+v", o._statusCode, o.Payload)
}
func (o *UpdateGithubEndpointDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *UpdateGithubEndpointDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -12,6 +12,7 @@ import (
"github.com/cloudbase/garm/client/controller_info"
"github.com/cloudbase/garm/client/credentials"
"github.com/cloudbase/garm/client/endpoints"
"github.com/cloudbase/garm/client/enterprises"
"github.com/cloudbase/garm/client/first_run"
"github.com/cloudbase/garm/client/instances"
@ -68,6 +69,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *GarmAPI {
cli.Transport = transport
cli.ControllerInfo = controller_info.New(transport, formats)
cli.Credentials = credentials.New(transport, formats)
cli.Endpoints = endpoints.New(transport, formats)
cli.Enterprises = enterprises.New(transport, formats)
cli.FirstRun = first_run.New(transport, formats)
cli.Instances = instances.New(transport, formats)
@ -126,6 +128,8 @@ type GarmAPI struct {
Credentials credentials.ClientService
Endpoints endpoints.ClientService
Enterprises enterprises.ClientService
FirstRun first_run.ClientService
@ -154,6 +158,7 @@ func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) {
c.Transport = transport
c.ControllerInfo.SetTransport(transport)
c.Credentials.SetTransport(transport)
c.Endpoints.SetTransport(transport)
c.Enterprises.SetTransport(transport)
c.FirstRun.SetTransport(transport)
c.Instances.SetTransport(transport)

View file

@ -1,76 +0,0 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package cmd
import (
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
apiClientCreds "github.com/cloudbase/garm/client/credentials"
"github.com/cloudbase/garm/params"
)
// credentialsCmd represents the credentials command
var credentialsCmd = &cobra.Command{
Use: "credentials",
Aliases: []string{"creds"},
Short: "List configured credentials",
Long: `List all available credentials configured in the service
config file.
Currently, github personal tokens are configured statically in the config file
of the garm service. This command lists the names of those credentials,
which in turn can be used to define pools of runners within repositories.`,
Run: nil,
}
func init() {
credentialsCmd.AddCommand(
&cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List configured github credentials",
Long: `List the names of the github personal access tokens available to the garm.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
listCredsReq := apiClientCreds.NewListCredentialsParams()
response, err := apiCli.Credentials.ListCredentials(listCredsReq, authToken)
if err != nil {
return err
}
formatGithubCredentials(response.Payload)
return nil
},
})
rootCmd.AddCommand(credentialsCmd)
}
func formatGithubCredentials(creds []params.GithubCredentials) {
t := table.NewWriter()
header := table.Row{"Name", "Description", "Base URL", "API URL", "Upload URL", "Type"}
t.AppendHeader(header)
for _, val := range creds {
t.AppendRow(table.Row{val.Name, val.Description, val.BaseURL, val.APIBaseURL, val.UploadBaseURL, val.AuthType})
t.AppendSeparator()
}
fmt.Println(t.Render())
}

View file

@ -204,7 +204,7 @@ func formatEnterprises(enterprises []params.Enterprise) {
header := table.Row{"ID", "Name", "Credentials name", "Pool Balancer Type", "Pool mgr running"}
t.AppendHeader(header)
for _, val := range enterprises {
t.AppendRow(table.Row{val.ID, val.Name, val.CredentialsName, val.GetBalancerType(), val.PoolManagerStatus.IsRunning})
t.AppendRow(table.Row{val.ID, val.Name, val.Credentials.Name, val.GetBalancerType(), val.PoolManagerStatus.IsRunning})
t.AppendSeparator()
}
fmt.Println(t.Render())
@ -218,7 +218,7 @@ func formatOneEnterprise(enterprise params.Enterprise) {
t.AppendRow(table.Row{"ID", enterprise.ID})
t.AppendRow(table.Row{"Name", enterprise.Name})
t.AppendRow(table.Row{"Pool balancer type", enterprise.GetBalancerType()})
t.AppendRow(table.Row{"Credentials", enterprise.CredentialsName})
t.AppendRow(table.Row{"Credentials", enterprise.Credentials.Name})
t.AppendRow(table.Row{"Pool manager running", enterprise.PoolManagerStatus.IsRunning})
if !enterprise.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", enterprise.PoolManagerStatus.FailureReason})

View file

@ -0,0 +1,30 @@
package cmd
import "github.com/spf13/cobra"
var (
endpointName string
endpointBaseURL string
endpointUploadURL string
endpointAPIBaseURL string
endpointCACertPath string
endpointDescription string
)
// githubCmd represents the the github command. This command has a set
// of subcommands that allow configuring and managing GitHub endpoints
// and credentials.
var githubCmd = &cobra.Command{
Use: "github",
Aliases: []string{"gh"},
SilenceUsage: true,
Short: "Manage GitHub resources",
Long: `Manage GitHub related resources.
This command allows you to configure and manage GitHub endpoints and credentials`,
Run: nil,
}
func init() {
rootCmd.AddCommand(githubCmd)
}

View file

@ -0,0 +1,388 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package cmd
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"strconv"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
apiClientCreds "github.com/cloudbase/garm/client/credentials"
"github.com/cloudbase/garm/params"
)
var (
credentialsName string
credentialsDescription string
credentialsOAuthToken string
credentialsAppInstallationID int64
credentialsAppID int64
credentialsPrivateKeyPath string
credentialsType string
credentialsEndpoint string
)
// credentialsCmd represents the credentials command
var credentialsCmd = &cobra.Command{
Use: "credentials",
Aliases: []string{"creds"},
Short: "List configured credentials. This is an alias for the github credentials command.",
Long: `List all available github credentials.
This command is an alias for the garm-cli github credentials command.`,
Run: nil,
}
// githubCredentialsCmd represents the github credentials command
var githubCredentialsCmd = &cobra.Command{
Use: "credentials",
Aliases: []string{"creds"},
Short: "Manage github credentials",
Long: `Manage GitHub credentials stored in GARM.
This command allows you to add, update, list and delete GitHub credentials.`,
Run: nil,
}
var githubCredentialsListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List configured github credentials",
Long: `List the names of the github personal access tokens available to the garm.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
listCredsReq := apiClientCreds.NewListCredentialsParams()
response, err := apiCli.Credentials.ListCredentials(listCredsReq, authToken)
if err != nil {
return err
}
formatGithubCredentials(response.Payload)
return nil
},
}
var githubCredentialsShowCmd = &cobra.Command{
Use: "show",
Aliases: []string{"get"},
Short: "Show details of a configured github credential",
Long: `Show the details of a configured github credential.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
showCredsReq := apiClientCreds.NewGetCredentialsParams().WithID(credID)
response, err := apiCli.Credentials.GetCredentials(showCredsReq, authToken)
if err != nil {
return err
}
formatOneGithubCredential(response.Payload)
return nil
},
}
var githubCredentialsUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update a github credential",
Long: "Update a github credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
updateParams, err := parseCredentialsUpdateParams()
if err != nil {
return err
}
updateCredsReq := apiClientCreds.NewUpdateCredentialsParams().WithID(credID)
updateCredsReq.Body = updateParams
response, err := apiCli.Credentials.UpdateCredentials(updateCredsReq, authToken)
if err != nil {
return err
}
formatOneGithubCredential(response.Payload)
return nil
},
}
var githubCredentialsDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm"},
Short: "Delete a github credential",
Long: "Delete a github credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
deleteCredsReq := apiClientCreds.NewDeleteCredentialsParams().WithID(credID)
if err := apiCli.Credentials.DeleteCredentials(deleteCredsReq, authToken); err != nil {
return err
}
return nil
},
}
var githubCredentialsAddCmd = &cobra.Command{
Use: "add",
Short: "Add a github credential",
Long: "Add a github credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) > 0 {
return fmt.Errorf("too many arguments")
}
addParams, err := parseCredentialsAddParams()
if err != nil {
return err
}
addCredsReq := apiClientCreds.NewCreateCredentialsParams()
addCredsReq.Body = addParams
response, err := apiCli.Credentials.CreateCredentials(addCredsReq, authToken)
if err != nil {
return err
}
formatOneGithubCredential(response.Payload)
return nil
},
}
func init() {
githubCredentialsUpdateCmd.Flags().StringVar(&credentialsName, "name", "", "Name of the credential")
githubCredentialsUpdateCmd.Flags().StringVar(&credentialsDescription, "description", "", "Description of the credential")
githubCredentialsUpdateCmd.Flags().StringVar(&credentialsOAuthToken, "pat-oauth-token", "", "If the credential is a personal access token, the OAuth token")
githubCredentialsUpdateCmd.Flags().Int64Var(&credentialsAppInstallationID, "app-installation-id", 0, "If the credential is an app, the installation ID")
githubCredentialsUpdateCmd.Flags().Int64Var(&credentialsAppID, "app-id", 0, "If the credential is an app, the app ID")
githubCredentialsUpdateCmd.Flags().StringVar(&credentialsPrivateKeyPath, "private-key-path", "", "If the credential is an app, the path to the private key file")
githubCredentialsUpdateCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "app-installation-id")
githubCredentialsUpdateCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "app-id")
githubCredentialsUpdateCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "private-key-path")
githubCredentialsUpdateCmd.MarkFlagsRequiredTogether("app-installation-id", "app-id", "private-key-path")
githubCredentialsAddCmd.Flags().StringVar(&credentialsName, "name", "", "Name of the credential")
githubCredentialsAddCmd.Flags().StringVar(&credentialsDescription, "description", "", "Description of the credential")
githubCredentialsAddCmd.Flags().StringVar(&credentialsOAuthToken, "pat-oauth-token", "", "If the credential is a personal access token, the OAuth token")
githubCredentialsAddCmd.Flags().Int64Var(&credentialsAppInstallationID, "app-installation-id", 0, "If the credential is an app, the installation ID")
githubCredentialsAddCmd.Flags().Int64Var(&credentialsAppID, "app-id", 0, "If the credential is an app, the app ID")
githubCredentialsAddCmd.Flags().StringVar(&credentialsPrivateKeyPath, "private-key-path", "", "If the credential is an app, the path to the private key file")
githubCredentialsAddCmd.Flags().StringVar(&credentialsType, "auth-type", "", "The type of the credential")
githubCredentialsAddCmd.Flags().StringVar(&credentialsEndpoint, "endpoint", "", "The endpoint to associate the credential with")
githubCredentialsAddCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "app-installation-id")
githubCredentialsAddCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "app-id")
githubCredentialsAddCmd.MarkFlagsMutuallyExclusive("pat-oauth-token", "private-key-path")
githubCredentialsAddCmd.MarkFlagsRequiredTogether("app-installation-id", "app-id", "private-key-path")
githubCredentialsAddCmd.MarkFlagRequired("name")
githubCredentialsAddCmd.MarkFlagRequired("auth-type")
githubCredentialsAddCmd.MarkFlagRequired("description")
githubCredentialsAddCmd.MarkFlagRequired("endpoint")
githubCredentialsCmd.AddCommand(
githubCredentialsListCmd,
githubCredentialsShowCmd,
githubCredentialsUpdateCmd,
githubCredentialsDeleteCmd,
githubCredentialsAddCmd,
)
githubCmd.AddCommand(githubCredentialsCmd)
credentialsCmd.AddCommand(githubCredentialsListCmd)
rootCmd.AddCommand(credentialsCmd)
}
func parsePrivateKeyFromPath(path string) ([]byte, error) {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("private key file not found: %s", credentialsPrivateKeyPath)
}
keyContents, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read private key file: %w", err)
}
pemBlock, _ := pem.Decode(keyContents)
if pemBlock == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if _, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return keyContents, nil
}
func parseCredentialsAddParams() (ret params.CreateGithubCredentialsParams, err error) {
ret.Name = credentialsName
ret.Description = credentialsDescription
ret.AuthType = params.GithubAuthType(credentialsType)
ret.Endpoint = credentialsEndpoint
switch ret.AuthType {
case params.GithubAuthTypePAT:
ret.PAT.OAuth2Token = credentialsOAuthToken
case params.GithubAuthTypeApp:
ret.App.InstallationID = credentialsAppInstallationID
ret.App.AppID = credentialsAppID
keyContents, err := parsePrivateKeyFromPath(credentialsPrivateKeyPath)
if err != nil {
return params.CreateGithubCredentialsParams{}, err
}
ret.App.PrivateKeyBytes = keyContents
default:
return params.CreateGithubCredentialsParams{}, fmt.Errorf("invalid auth type: %s (supported are: app, pat)", credentialsType)
}
return ret, nil
}
func parseCredentialsUpdateParams() (params.UpdateGithubCredentialsParams, error) {
var updateParams params.UpdateGithubCredentialsParams
if credentialsName != "" {
updateParams.Name = &credentialsName
}
if credentialsDescription != "" {
updateParams.Description = &credentialsDescription
}
if credentialsOAuthToken != "" {
updateParams.PAT.OAuth2Token = credentialsOAuthToken
}
if credentialsAppInstallationID != 0 {
updateParams.App.InstallationID = credentialsAppInstallationID
}
if credentialsAppID != 0 {
updateParams.App.AppID = credentialsAppID
}
if credentialsPrivateKeyPath != "" {
keyContents, err := parsePrivateKeyFromPath(credentialsPrivateKeyPath)
if err != nil {
return params.UpdateGithubCredentialsParams{}, err
}
updateParams.App.PrivateKeyBytes = keyContents
}
return updateParams, nil
}
func formatGithubCredentials(creds []params.GithubCredentials) {
t := table.NewWriter()
header := table.Row{"ID", "Name", "Description", "Base URL", "API URL", "Upload URL", "Type"}
t.AppendHeader(header)
for _, val := range creds {
t.AppendRow(table.Row{val.ID, val.Name, val.Description, val.BaseURL, val.APIBaseURL, val.UploadBaseURL, val.AuthType})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneGithubCredential(cred params.GithubCredentials) {
t := table.NewWriter()
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", cred.ID})
t.AppendRow(table.Row{"Name", cred.Name})
t.AppendRow(table.Row{"Description", cred.Description})
t.AppendRow(table.Row{"Base URL", cred.BaseURL})
t.AppendRow(table.Row{"API URL", cred.APIBaseURL})
t.AppendRow(table.Row{"Upload URL", cred.UploadBaseURL})
t.AppendRow(table.Row{"Type", cred.AuthType})
t.AppendRow(table.Row{"Endpoint", cred.Endpoint.Name})
if len(cred.Repositories) > 0 {
t.AppendRow(table.Row{"", ""})
for _, repo := range cred.Repositories {
t.AppendRow(table.Row{"Repositories", repo.String()})
}
}
if len(cred.Organizations) > 0 {
t.AppendRow(table.Row{"", ""})
for _, org := range cred.Organizations {
t.AppendRow(table.Row{"Organizations", org.Name})
}
}
if len(cred.Enterprises) > 0 {
t.AppendRow(table.Row{"", ""})
for _, ent := range cred.Enterprises {
t.AppendRow(table.Row{"Enterprises", ent.Name})
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())
}

View file

@ -0,0 +1,279 @@
package cmd
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
apiClientEndpoints "github.com/cloudbase/garm/client/endpoints"
"github.com/cloudbase/garm/params"
)
var githubEndpointCmd = &cobra.Command{
Use: "endpoint",
SilenceUsage: true,
Short: "Manage GitHub endpoints",
Long: `Manage GitHub endpoints.
This command allows you to configure and manage GitHub endpoints`,
Run: nil,
}
var githubEndpointListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
SilenceUsage: true,
Short: "List GitHub endpoints",
Long: `List all configured GitHub endpoints.`,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
newGHListReq := apiClientEndpoints.NewListGithubEndpointsParams()
response, err := apiCli.Endpoints.ListGithubEndpoints(newGHListReq, authToken)
if err != nil {
return err
}
formatEndpoints(response.Payload)
return nil
},
}
var githubEndpointShowCmd = &cobra.Command{
Use: "show",
Aliases: []string{"get"},
SilenceUsage: true,
Short: "Show GitHub endpoint",
Long: `Show details of a GitHub endpoint.`,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
newGHShowReq := apiClientEndpoints.NewGetGithubEndpointParams()
newGHShowReq.Name = args[0]
response, err := apiCli.Endpoints.GetGithubEndpoint(newGHShowReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
var githubEndpointCreateCmd = &cobra.Command{
Use: "create",
SilenceUsage: true,
Short: "Create GitHub endpoint",
Long: `Create a new GitHub endpoint.`,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
createParams, err := parseCreateParams()
if err != nil {
return err
}
newGHCreateReq := apiClientEndpoints.NewCreateGithubEndpointParams()
newGHCreateReq.Body = createParams
response, err := apiCli.Endpoints.CreateGithubEndpoint(newGHCreateReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
var githubEndpointDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm"},
SilenceUsage: true,
Short: "Delete GitHub endpoint",
Long: "Delete a GitHub endpoint",
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
newGHDeleteReq := apiClientEndpoints.NewDeleteGithubEndpointParams()
newGHDeleteReq.Name = args[0]
if err := apiCli.Endpoints.DeleteGithubEndpoint(newGHDeleteReq, authToken); err != nil {
return err
}
return nil
},
}
var githubEndpointUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update GitHub endpoint",
Long: "Update a GitHub endpoint",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
updateParams := params.UpdateGithubEndpointParams{}
if cmd.Flags().Changed("ca-cert-path") {
cert, err := parseReadAndParsCABundle()
if err != nil {
return err
}
updateParams.CACertBundle = cert
}
if cmd.Flags().Changed("description") {
updateParams.Description = &endpointDescription
}
if cmd.Flags().Changed("base-url") {
updateParams.BaseURL = &endpointBaseURL
}
if cmd.Flags().Changed("upload-url") {
updateParams.UploadBaseURL = &endpointUploadURL
}
if cmd.Flags().Changed("api-base-url") {
updateParams.APIBaseURL = &endpointAPIBaseURL
}
newGHEndpointUpdateReq := apiClientEndpoints.NewUpdateGithubEndpointParams()
newGHEndpointUpdateReq.Name = args[0]
newGHEndpointUpdateReq.Body = updateParams
response, err := apiCli.Endpoints.UpdateGithubEndpoint(newGHEndpointUpdateReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
func init() {
githubEndpointCreateCmd.Flags().StringVar(&endpointName, "name", "", "Name of the GitHub endpoint")
githubEndpointCreateCmd.Flags().StringVar(&endpointDescription, "description", "", "Description for the github endpoint")
githubEndpointCreateCmd.Flags().StringVar(&endpointBaseURL, "base-url", "", "Base URL of the GitHub endpoint")
githubEndpointCreateCmd.Flags().StringVar(&endpointUploadURL, "upload-url", "", "Upload URL of the GitHub endpoint")
githubEndpointCreateCmd.Flags().StringVar(&endpointAPIBaseURL, "api-base-url", "", "API Base URL of the GitHub endpoint")
githubEndpointCreateCmd.Flags().StringVar(&endpointCACertPath, "ca-cert-path", "", "CA Cert Path of the GitHub endpoint")
githubEndpointCreateCmd.MarkFlagRequired("name")
githubEndpointCreateCmd.MarkFlagRequired("base-url")
githubEndpointCreateCmd.MarkFlagRequired("api-base-url")
githubEndpointCreateCmd.MarkFlagRequired("upload-url")
githubEndpointUpdateCmd.Flags().StringVar(&endpointDescription, "description", "", "Description for the github endpoint")
githubEndpointUpdateCmd.Flags().StringVar(&endpointBaseURL, "base-url", "", "Base URL of the GitHub endpoint")
githubEndpointUpdateCmd.Flags().StringVar(&endpointUploadURL, "upload-url", "", "Upload URL of the GitHub endpoint")
githubEndpointUpdateCmd.Flags().StringVar(&endpointAPIBaseURL, "api-base-url", "", "API Base URL of the GitHub endpoint")
githubEndpointUpdateCmd.Flags().StringVar(&endpointCACertPath, "ca-cert-path", "", "CA Cert Path of the GitHub endpoint")
githubEndpointCmd.AddCommand(
githubEndpointListCmd,
githubEndpointShowCmd,
githubEndpointCreateCmd,
githubEndpointDeleteCmd,
githubEndpointUpdateCmd,
)
githubCmd.AddCommand(githubEndpointCmd)
}
func parseReadAndParsCABundle() ([]byte, error) {
if endpointCACertPath == "" {
return nil, nil
}
if _, err := os.Stat(endpointCACertPath); os.IsNotExist(err) {
return nil, fmt.Errorf("CA cert file not found: %s", endpointCACertPath)
}
contents, err := os.ReadFile(endpointCACertPath)
if err != nil {
return nil, err
}
pemBlock, _ := pem.Decode(contents)
if pemBlock == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if _, err := x509.ParseCertificates(pemBlock.Bytes); err != nil {
return nil, fmt.Errorf("failed to parse CA cert bundle: %w", err)
}
return contents, nil
}
func parseCreateParams() (params.CreateGithubEndpointParams, error) {
certBundleBytes, err := parseReadAndParsCABundle()
if err != nil {
return params.CreateGithubEndpointParams{}, err
}
ret := params.CreateGithubEndpointParams{
Name: endpointName,
BaseURL: endpointBaseURL,
UploadBaseURL: endpointUploadURL,
APIBaseURL: endpointAPIBaseURL,
Description: endpointDescription,
CACertBundle: certBundleBytes,
}
return ret, nil
}
func formatEndpoints(endpoints params.GithubEndpoints) {
t := table.NewWriter()
header := table.Row{"Name", "Base URL", "Description"}
t.AppendHeader(header)
for _, val := range endpoints {
t.AppendRow([]interface{}{val.Name, val.BaseURL, val.Description})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneEndpoint(endpoint params.GithubEndpoint) {
t := table.NewWriter()
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow([]interface{}{"Name", endpoint.Name})
t.AppendRow([]interface{}{"Base URL", endpoint.BaseURL})
t.AppendRow([]interface{}{"Upload URL", endpoint.UploadBaseURL})
t.AppendRow([]interface{}{"API Base URL", endpoint.APIBaseURL})
if len(endpoint.CACertBundle) > 0 {
t.AppendRow([]interface{}{"CA Cert Bundle", string(endpoint.CACertBundle)})
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())
}

View file

@ -151,6 +151,8 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), signals...)
defer stop()
ctx = auth.GetAdminContext(ctx)
cfg, err := config.NewConfig(*conf)
if err != nil {
log.Fatalf("Fetching config: %+v", err) //nolint:gocritic
@ -167,6 +169,9 @@ func main() {
}
setupLogging(ctx, logCfg, hub)
// Migrate credentials to the new format. This field will be read
// by the DB migration logic.
cfg.Database.MigrateCredentials = cfg.Github
db, err := database.NewDatabase(ctx, cfg.Database)
if err != nil {
log.Fatal(err)

View file

@ -241,6 +241,14 @@ type GithubApp struct {
InstallationID int64 `toml:"installation_id" json:"installation-id"`
}
func (a *GithubApp) PrivateKeyBytes() ([]byte, error) {
keyBytes, err := os.ReadFile(a.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("reading private_key_path: %w", err)
}
return keyBytes, nil
}
func (a *GithubApp) Validate() error {
if a.AppID == 0 {
return fmt.Errorf("missing app_id")
@ -291,6 +299,13 @@ type Github struct {
App GithubApp `toml:"app" json:"app"`
}
func (g *Github) GetAuthType() GithubAuthType {
if g.AuthType == "" {
return GithubAuthTypePAT
}
return g.AuthType
}
func (g *Github) APIEndpoint() string {
if g.APIBaseURL != "" {
return g.APIBaseURL
@ -472,6 +487,11 @@ type Database struct {
// Don't lose or change this. It will invalidate all encrypted data
// in the DB. This field must be set and must be exactly 32 characters.
Passphrase string `toml:"passphrase"`
// MigrateCredentials is a list of github credentials that need to be migrated
// from the config file to the database. This field will be removed once GARM
// reaches version 0.2.x. It's only meant to be used for the migration process.
MigrateCredentials []Github `toml:"-"`
}
// GormParams returns the database type and connection URI

View file

@ -20,6 +20,23 @@ import (
"github.com/cloudbase/garm/params"
)
type GithubEndpointStore interface {
CreateGithubEndpoint(ctx context.Context, param params.CreateGithubEndpointParams) (params.GithubEndpoint, error)
GetGithubEndpoint(ctx context.Context, name string) (params.GithubEndpoint, error)
ListGithubEndpoints(ctx context.Context) ([]params.GithubEndpoint, error)
UpdateGithubEndpoint(ctx context.Context, name string, param params.UpdateGithubEndpointParams) (params.GithubEndpoint, error)
DeleteGithubEndpoint(ctx context.Context, name string) error
}
type GithubCredentialsStore interface {
CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (params.GithubCredentials, error)
GetGithubCredentials(ctx context.Context, id uint, detailed bool) (params.GithubCredentials, error)
GetGithubCredentialsByName(ctx context.Context, name string, detailed bool) (params.GithubCredentials, error)
ListGithubCredentials(ctx context.Context) ([]params.GithubCredentials, error)
UpdateGithubCredentials(ctx context.Context, id uint, param params.UpdateGithubCredentialsParams) (params.GithubCredentials, error)
DeleteGithubCredentials(ctx context.Context, id uint) error
}
type RepoStore interface {
CreateRepository(ctx context.Context, owner, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Repository, error)
GetRepository(ctx context.Context, owner, name string) (params.Repository, error)
@ -65,6 +82,7 @@ type PoolStore interface {
type UserStore interface {
GetUser(ctx context.Context, user string) (params.User, error)
GetUserByID(ctx context.Context, userID string) (params.User, error)
GetAdminUser(ctx context.Context) (params.User, error)
CreateUser(ctx context.Context, user params.NewUserParams) (params.User, error)
UpdateUser(ctx context.Context, user string, param params.UpdateUserParams) (params.User, error)
@ -121,6 +139,8 @@ type Store interface {
InstanceStore
JobsStore
EntityPools
GithubEndpointStore
GithubCredentialsStore
ControllerInfo() (params.ControllerInfo, error)
InitController() (params.ControllerInfo, error)

View file

@ -134,6 +134,62 @@ func (_m *Store) CreateEntityPool(ctx context.Context, entity params.GithubEntit
return r0, r1
}
// CreateGithubCredentials provides a mock function with given fields: ctx, param
func (_m *Store) CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (params.GithubCredentials, error) {
ret := _m.Called(ctx, param)
if len(ret) == 0 {
panic("no return value specified for CreateGithubCredentials")
}
var r0 params.GithubCredentials
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.CreateGithubCredentialsParams) (params.GithubCredentials, error)); ok {
return rf(ctx, param)
}
if rf, ok := ret.Get(0).(func(context.Context, params.CreateGithubCredentialsParams) params.GithubCredentials); ok {
r0 = rf(ctx, param)
} else {
r0 = ret.Get(0).(params.GithubCredentials)
}
if rf, ok := ret.Get(1).(func(context.Context, params.CreateGithubCredentialsParams) error); ok {
r1 = rf(ctx, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateGithubEndpoint provides a mock function with given fields: ctx, param
func (_m *Store) CreateGithubEndpoint(ctx context.Context, param params.CreateGithubEndpointParams) (params.GithubEndpoint, error) {
ret := _m.Called(ctx, param)
if len(ret) == 0 {
panic("no return value specified for CreateGithubEndpoint")
}
var r0 params.GithubEndpoint
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.CreateGithubEndpointParams) (params.GithubEndpoint, error)); ok {
return rf(ctx, param)
}
if rf, ok := ret.Get(0).(func(context.Context, params.CreateGithubEndpointParams) params.GithubEndpoint); ok {
r0 = rf(ctx, param)
} else {
r0 = ret.Get(0).(params.GithubEndpoint)
}
if rf, ok := ret.Get(1).(func(context.Context, params.CreateGithubEndpointParams) error); ok {
r1 = rf(ctx, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateInstance provides a mock function with given fields: ctx, poolID, param
func (_m *Store) CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error) {
ret := _m.Called(ctx, poolID, param)
@ -328,6 +384,42 @@ func (_m *Store) DeleteEntityPool(ctx context.Context, entity params.GithubEntit
return r0
}
// DeleteGithubCredentials provides a mock function with given fields: ctx, id
func (_m *Store) DeleteGithubCredentials(ctx context.Context, id uint) error {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for DeleteGithubCredentials")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, uint) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteGithubEndpoint provides a mock function with given fields: ctx, name
func (_m *Store) DeleteGithubEndpoint(ctx context.Context, name string) error {
ret := _m.Called(ctx, name)
if len(ret) == 0 {
panic("no return value specified for DeleteGithubEndpoint")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, name)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteInstance provides a mock function with given fields: ctx, poolID, instanceName
func (_m *Store) DeleteInstance(ctx context.Context, poolID string, instanceName string) error {
ret := _m.Called(ctx, poolID, instanceName)
@ -448,6 +540,34 @@ func (_m *Store) FindPoolsMatchingAllTags(ctx context.Context, entityType params
return r0, r1
}
// GetAdminUser provides a mock function with given fields: ctx
func (_m *Store) GetAdminUser(ctx context.Context) (params.User, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetAdminUser")
}
var r0 params.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (params.User, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) params.User); ok {
r0 = rf(ctx)
} else {
r0 = ret.Get(0).(params.User)
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEnterprise provides a mock function with given fields: ctx, name
func (_m *Store) GetEnterprise(ctx context.Context, name string) (params.Enterprise, error) {
ret := _m.Called(ctx, name)
@ -532,6 +652,90 @@ func (_m *Store) GetEntityPool(ctx context.Context, entity params.GithubEntity,
return r0, r1
}
// GetGithubCredentials provides a mock function with given fields: ctx, id, detailed
func (_m *Store) GetGithubCredentials(ctx context.Context, id uint, detailed bool) (params.GithubCredentials, error) {
ret := _m.Called(ctx, id, detailed)
if len(ret) == 0 {
panic("no return value specified for GetGithubCredentials")
}
var r0 params.GithubCredentials
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint, bool) (params.GithubCredentials, error)); ok {
return rf(ctx, id, detailed)
}
if rf, ok := ret.Get(0).(func(context.Context, uint, bool) params.GithubCredentials); ok {
r0 = rf(ctx, id, detailed)
} else {
r0 = ret.Get(0).(params.GithubCredentials)
}
if rf, ok := ret.Get(1).(func(context.Context, uint, bool) error); ok {
r1 = rf(ctx, id, detailed)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetGithubCredentialsByName provides a mock function with given fields: ctx, name, detailed
func (_m *Store) GetGithubCredentialsByName(ctx context.Context, name string, detailed bool) (params.GithubCredentials, error) {
ret := _m.Called(ctx, name, detailed)
if len(ret) == 0 {
panic("no return value specified for GetGithubCredentialsByName")
}
var r0 params.GithubCredentials
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, bool) (params.GithubCredentials, error)); ok {
return rf(ctx, name, detailed)
}
if rf, ok := ret.Get(0).(func(context.Context, string, bool) params.GithubCredentials); ok {
r0 = rf(ctx, name, detailed)
} else {
r0 = ret.Get(0).(params.GithubCredentials)
}
if rf, ok := ret.Get(1).(func(context.Context, string, bool) error); ok {
r1 = rf(ctx, name, detailed)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetGithubEndpoint provides a mock function with given fields: ctx, name
func (_m *Store) GetGithubEndpoint(ctx context.Context, name string) (params.GithubEndpoint, error) {
ret := _m.Called(ctx, name)
if len(ret) == 0 {
panic("no return value specified for GetGithubEndpoint")
}
var r0 params.GithubEndpoint
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (params.GithubEndpoint, error)); ok {
return rf(ctx, name)
}
if rf, ok := ret.Get(0).(func(context.Context, string) params.GithubEndpoint); ok {
r0 = rf(ctx, name)
} else {
r0 = ret.Get(0).(params.GithubEndpoint)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetInstanceByName provides a mock function with given fields: ctx, instanceName
func (_m *Store) GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error) {
ret := _m.Called(ctx, instanceName)
@ -1068,6 +1272,66 @@ func (_m *Store) ListEntityPools(ctx context.Context, entity params.GithubEntity
return r0, r1
}
// ListGithubCredentials provides a mock function with given fields: ctx
func (_m *Store) ListGithubCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListGithubCredentials")
}
var r0 []params.GithubCredentials
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.GithubCredentials, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.GithubCredentials); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.GithubCredentials)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListGithubEndpoints provides a mock function with given fields: ctx
func (_m *Store) ListGithubEndpoints(ctx context.Context) ([]params.GithubEndpoint, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListGithubEndpoints")
}
var r0 []params.GithubEndpoint
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.GithubEndpoint, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.GithubEndpoint); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.GithubEndpoint)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListJobsByStatus provides a mock function with given fields: ctx, status
func (_m *Store) ListJobsByStatus(ctx context.Context, status params.JobStatus) ([]params.Job, error) {
ret := _m.Called(ctx, status)
@ -1308,6 +1572,62 @@ func (_m *Store) UpdateEntityPool(ctx context.Context, entity params.GithubEntit
return r0, r1
}
// UpdateGithubCredentials provides a mock function with given fields: ctx, id, param
func (_m *Store) UpdateGithubCredentials(ctx context.Context, id uint, param params.UpdateGithubCredentialsParams) (params.GithubCredentials, error) {
ret := _m.Called(ctx, id, param)
if len(ret) == 0 {
panic("no return value specified for UpdateGithubCredentials")
}
var r0 params.GithubCredentials
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint, params.UpdateGithubCredentialsParams) (params.GithubCredentials, error)); ok {
return rf(ctx, id, param)
}
if rf, ok := ret.Get(0).(func(context.Context, uint, params.UpdateGithubCredentialsParams) params.GithubCredentials); ok {
r0 = rf(ctx, id, param)
} else {
r0 = ret.Get(0).(params.GithubCredentials)
}
if rf, ok := ret.Get(1).(func(context.Context, uint, params.UpdateGithubCredentialsParams) error); ok {
r1 = rf(ctx, id, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGithubEndpoint provides a mock function with given fields: ctx, name, param
func (_m *Store) UpdateGithubEndpoint(ctx context.Context, name string, param params.UpdateGithubEndpointParams) (params.GithubEndpoint, error) {
ret := _m.Called(ctx, name, param)
if len(ret) == 0 {
panic("no return value specified for UpdateGithubEndpoint")
}
var r0 params.GithubEndpoint
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, params.UpdateGithubEndpointParams) (params.GithubEndpoint, error)); ok {
return rf(ctx, name, param)
}
if rf, ok := ret.Get(0).(func(context.Context, string, params.UpdateGithubEndpointParams) params.GithubEndpoint); ok {
r0 = rf(ctx, name, param)
} else {
r0 = ret.Get(0).(params.GithubEndpoint)
}
if rf, ok := ret.Get(1).(func(context.Context, string, params.UpdateGithubEndpointParams) error); ok {
r1 = rf(ctx, name, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateInstance provides a mock function with given fields: ctx, instanceName, param
func (_m *Store) UpdateInstance(ctx context.Context, instanceName string, param params.UpdateInstanceParams) (params.Instance, error) {
ret := _m.Called(ctx, instanceName, param)

View file

@ -1,3 +1,17 @@
// Copyright 2024 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package sql
import (
@ -12,7 +26,7 @@ import (
"github.com/cloudbase/garm/params"
)
func (s *sqlDatabase) CreateEnterprise(_ context.Context, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Enterprise, error) {
func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Enterprise, error) {
if webhookSecret == "" {
return params.Enterprise{}, errors.New("creating enterprise: missing secret")
}
@ -26,13 +40,33 @@ func (s *sqlDatabase) CreateEnterprise(_ context.Context, name, credentialsName,
CredentialsName: credentialsName,
PoolBalancerType: poolBalancerType,
}
err = s.conn.Transaction(func(tx *gorm.DB) error {
creds, err := s.getGithubCredentialsByName(ctx, tx, credentialsName, false)
if err != nil {
return errors.Wrap(err, "creating enterprise")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
newEnterprise.CredentialsID = &creds.ID
newEnterprise.CredentialsName = creds.Name
newEnterprise.EndpointName = creds.EndpointName
q := s.conn.Create(&newEnterprise)
if q.Error != nil {
return params.Enterprise{}, errors.Wrap(q.Error, "creating enterprise")
q := tx.Create(&newEnterprise)
if q.Error != nil {
return errors.Wrap(q.Error, "creating enterprise")
}
newEnterprise.Credentials = creds
newEnterprise.Endpoint = creds.Endpoint
return nil
})
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
param, err := s.sqlToCommonEnterprise(newEnterprise)
param, err := s.sqlToCommonEnterprise(newEnterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
@ -46,7 +80,7 @@ func (s *sqlDatabase) GetEnterprise(ctx context.Context, name string) (params.En
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
param, err := s.sqlToCommonEnterprise(enterprise)
param, err := s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
@ -54,12 +88,12 @@ func (s *sqlDatabase) GetEnterprise(ctx context.Context, name string) (params.En
}
func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID, "Pools")
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Pools", "Credentials", "Endpoint")
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
param, err := s.sqlToCommonEnterprise(enterprise)
param, err := s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
@ -68,7 +102,11 @@ func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string
func (s *sqlDatabase) ListEnterprises(_ context.Context) ([]params.Enterprise, error) {
var enterprises []Enterprise
q := s.conn.Find(&enterprises)
q := s.conn.
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
Find(&enterprises)
if q.Error != nil {
return []params.Enterprise{}, errors.Wrap(q.Error, "fetching enterprises")
}
@ -76,7 +114,7 @@ func (s *sqlDatabase) ListEnterprises(_ context.Context) ([]params.Enterprise, e
ret := make([]params.Enterprise, len(enterprises))
for idx, val := range enterprises {
var err error
ret[idx], err = s.sqlToCommonEnterprise(val)
ret[idx], err = s.sqlToCommonEnterprise(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprises")
}
@ -86,7 +124,7 @@ func (s *sqlDatabase) ListEnterprises(_ context.Context) ([]params.Enterprise, e
}
func (s *sqlDatabase) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
}
@ -100,33 +138,65 @@ func (s *sqlDatabase) DeleteEnterprise(ctx context.Context, enterpriseID string)
}
func (s *sqlDatabase) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateEntityParams) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
if param.CredentialsName != "" {
enterprise.CredentialsName = param.CredentialsName
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
var enterprise Enterprise
var creds GithubCredentials
err := s.conn.Transaction(func(tx *gorm.DB) error {
var err error
enterprise, err = s.getEnterpriseByID(ctx, tx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "encoding secret")
return errors.Wrap(err, "fetching enterprise")
}
enterprise.WebhookSecret = secret
if enterprise.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "enterprise has no endpoint")
}
if param.CredentialsName != "" {
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
if *creds.EndpointName != *enterprise.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
}
enterprise.CredentialsID = &creds.ID
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return errors.Wrap(err, "encoding secret")
}
enterprise.WebhookSecret = secret
}
if param.PoolBalancerType != "" {
enterprise.PoolBalancerType = param.PoolBalancerType
}
q := tx.Save(&enterprise)
if q.Error != nil {
return errors.Wrap(q.Error, "saving enterprise")
}
if creds.ID != 0 {
enterprise.Credentials = creds
}
return nil
})
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
if param.PoolBalancerType != "" {
enterprise.PoolBalancerType = param.PoolBalancerType
enterprise, err = s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Endpoint", "Credentials")
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
q := s.conn.Save(&enterprise)
if q.Error != nil {
return params.Enterprise{}, errors.Wrap(q.Error, "saving enterprise")
}
newParams, err := s.sqlToCommonEnterprise(enterprise)
newParams, err := s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
@ -136,8 +206,11 @@ func (s *sqlDatabase) UpdateEnterprise(ctx context.Context, enterpriseID string,
func (s *sqlDatabase) getEnterprise(_ context.Context, name string) (Enterprise, error) {
var enterprise Enterprise
q := s.conn.Where("name = ? COLLATE NOCASE", name)
q = q.First(&enterprise)
q := s.conn.Where("name = ? COLLATE NOCASE", name).
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
First(&enterprise)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Enterprise{}, runnerErrors.ErrNotFound
@ -147,14 +220,14 @@ func (s *sqlDatabase) getEnterprise(_ context.Context, name string) (Enterprise,
return enterprise, nil
}
func (s *sqlDatabase) getEnterpriseByID(_ context.Context, id string, preload ...string) (Enterprise, error) {
func (s *sqlDatabase) getEnterpriseByID(_ context.Context, tx *gorm.DB, id string, preload ...string) (Enterprise, error) {
u, err := uuid.Parse(id)
if err != nil {
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var enterprise Enterprise
q := s.conn
q := tx
if len(preload) > 0 {
for _, field := range preload {
q = q.Preload(field)

View file

@ -29,6 +29,7 @@ import (
"gorm.io/gorm/logger"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
@ -49,6 +50,13 @@ type EnterpriseTestSuite struct {
Store dbCommon.Store
StoreSQLMocked *sqlDatabase
Fixtures *EnterpriseTestFixtures
adminCtx context.Context
adminUserID string
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *EnterpriseTestSuite) equalInstancesByName(expected, actual []params.Instance) {
@ -77,18 +85,27 @@ func (s *EnterpriseTestSuite) SetupTest() {
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
s.adminUserID = auth.UserID(adminCtx)
s.Require().NotEmpty(s.adminUserID)
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some enterprise objects in the database, for testing purposes
enterprises := []params.Enterprise{}
for i := 1; i <= 3; i++ {
enterprise, err := db.CreateEnterprise(
context.Background(),
s.adminCtx,
fmt.Sprintf("test-enterprise-%d", i),
fmt.Sprintf("test-creds-%d", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%d", i),
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (test-enterprise-%d)", i))
s.FailNow(fmt.Sprintf("failed to create database object (test-enterprise-%d): %q", i, err))
}
enterprises = append(enterprises, enterprise)
@ -124,7 +141,7 @@ func (s *EnterpriseTestSuite) SetupTest() {
Enterprises: enterprises,
CreateEnterpriseParams: params.CreateEnterpriseParams{
Name: "new-test-enterprise",
CredentialsName: "new-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "new-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -143,7 +160,7 @@ func (s *EnterpriseTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-update-creds",
CredentialsName: s.secondaryTestCreds.Name,
WebhookSecret: "test-update-repo-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -160,7 +177,7 @@ func (s *EnterpriseTestSuite) SetupTest() {
func (s *EnterpriseTestSuite) TestCreateEnterprise() {
// call tested function
enterprise, err := s.Store.CreateEnterprise(
context.Background(),
s.adminCtx,
s.Fixtures.CreateEnterpriseParams.Name,
s.Fixtures.CreateEnterpriseParams.CredentialsName,
s.Fixtures.CreateEnterpriseParams.WebhookSecret,
@ -168,12 +185,12 @@ func (s *EnterpriseTestSuite) TestCreateEnterprise() {
// assertions
s.Require().Nil(err)
storeEnterprise, err := s.Store.GetEnterpriseByID(context.Background(), enterprise.ID)
storeEnterprise, err := s.Store.GetEnterpriseByID(s.adminCtx, enterprise.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get enterprise by id: %v", err))
}
s.Require().Equal(storeEnterprise.Name, enterprise.Name)
s.Require().Equal(storeEnterprise.CredentialsName, enterprise.CredentialsName)
s.Require().Equal(storeEnterprise.Credentials.Name, enterprise.Credentials.Name)
s.Require().Equal(storeEnterprise.WebhookSecret, enterprise.WebhookSecret)
}
@ -191,7 +208,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseInvalidDBPassphrase() {
}
_, err = sqlDB.CreateEnterprise(
context.Background(),
s.adminCtx,
s.Fixtures.CreateEnterpriseParams.Name,
s.Fixtures.CreateEnterpriseParams.CredentialsName,
s.Fixtures.CreateEnterpriseParams.WebhookSecret,
@ -203,25 +220,33 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseInvalidDBPassphrase() {
func (s *EnterpriseTestSuite) TestCreateEnterpriseDBCreateErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.Fixtures.Enterprises[0].CredentialsName, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).AddRow(s.testCreds.ID, s.testCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.testCreds.Endpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(regexp.QuoteMeta("INSERT INTO `enterprises`")).
WillReturnError(fmt.Errorf("creating enterprise mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.CreateEnterprise(
context.Background(),
s.adminCtx,
s.Fixtures.CreateEnterpriseParams.Name,
s.Fixtures.CreateEnterpriseParams.CredentialsName,
s.Fixtures.CreateEnterpriseParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("creating enterprise: creating enterprise mock error", err.Error())
s.Require().Equal("creating enterprise: creating enterprise: creating enterprise mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *EnterpriseTestSuite) TestGetEnterprise() {
enterprise, err := s.Store.GetEnterprise(context.Background(), s.Fixtures.Enterprises[0].Name)
enterprise, err := s.Store.GetEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].Name)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Enterprises[0].Name, enterprise.Name)
@ -229,14 +254,14 @@ func (s *EnterpriseTestSuite) TestGetEnterprise() {
}
func (s *EnterpriseTestSuite) TestGetEnterpriseCaseInsensitive() {
enterprise, err := s.Store.GetEnterprise(context.Background(), "TeSt-eNtErPriSe-1")
enterprise, err := s.Store.GetEnterprise(s.adminCtx, "TeSt-eNtErPriSe-1")
s.Require().Nil(err)
s.Require().Equal("test-enterprise-1", enterprise.Name)
}
func (s *EnterpriseTestSuite) TestGetEnterpriseNotFound() {
_, err := s.Store.GetEnterprise(context.Background(), "dummy-name")
_, err := s.Store.GetEnterprise(s.adminCtx, "dummy-name")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: not found", err.Error())
@ -248,7 +273,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseDBDecryptingErr() {
WithArgs(s.Fixtures.Enterprises[0].Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(s.Fixtures.Enterprises[0].Name))
_, err := s.StoreSQLMocked.GetEnterprise(context.Background(), s.Fixtures.Enterprises[0].Name)
_, err := s.StoreSQLMocked.GetEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].Name)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -256,7 +281,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseDBDecryptingErr() {
}
func (s *EnterpriseTestSuite) TestListEnterprises() {
enterprises, err := s.Store.ListEnterprises(context.Background())
enterprises, err := s.Store.ListEnterprises(s.adminCtx)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), s.Fixtures.Enterprises, enterprises)
@ -267,7 +292,7 @@ func (s *EnterpriseTestSuite) TestListEnterprisesDBFetchErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE `enterprises`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListEnterprises(context.Background())
_, err := s.StoreSQLMocked.ListEnterprises(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -275,16 +300,16 @@ func (s *EnterpriseTestSuite) TestListEnterprisesDBFetchErr() {
}
func (s *EnterpriseTestSuite) TestDeleteEnterprise() {
err := s.Store.DeleteEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID)
err := s.Store.DeleteEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.Require().Nil(err)
_, err = s.Store.GetEnterpriseByID(context.Background(), s.Fixtures.Enterprises[0].ID)
_, err = s.Store.GetEnterpriseByID(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterpriseInvalidEnterpriseID() {
err := s.Store.DeleteEnterprise(context.Background(), "dummy-enterprise-id")
err := s.Store.DeleteEnterprise(s.adminCtx, "dummy-enterprise-id")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
@ -302,86 +327,119 @@ func (s *EnterpriseTestSuite) TestDeleteEnterpriseDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked delete enterprise error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeleteEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID)
err := s.StoreSQLMocked.DeleteEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("deleting enterprise: mocked delete enterprise error", err.Error())
s.assertSQLMockExpectations()
}
func (s *EnterpriseTestSuite) TestUpdateEnterprise() {
enterprise, err := s.Store.UpdateEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
enterprise, err := s.Store.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, enterprise.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, enterprise.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, enterprise.WebhookSecret)
}
func (s *EnterpriseTestSuite) TestUpdateEnterpriseInvalidEnterpriseID() {
_, err := s.Store.UpdateEnterprise(context.Background(), "dummy-enterprise-id", s.Fixtures.UpdateRepoParams)
_, err := s.Store.UpdateEnterprise(s.adminCtx, "dummy-enterprise-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
s.Require().Equal("updating enterprise: fetching enterprise: parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBEncryptErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE id = ? AND `enterprises`.`deleted_at` IS NULL ORDER BY `enterprises`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Enterprises[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Enterprises[0].ID))
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("updating enterprise: encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBSaveErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE id = ? AND `enterprises`.`deleted_at` IS NULL ORDER BY `enterprises`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Enterprises[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Enterprises[0].ID))
s.Fixtures.SQLMock.ExpectBegin()
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(("UPDATE `enterprises` SET")).
WillReturnError(fmt.Errorf("saving enterprise mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving enterprise: saving enterprise mock error", err.Error())
s.Require().Equal("updating enterprise: saving enterprise: saving enterprise mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBDecryptingErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.UpdateRepoParams.WebhookSecret = webhookSecret
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE id = ? AND `enterprises`.`deleted_at` IS NULL ORDER BY `enterprises`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Enterprises[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Enterprises[0].ID))
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Enterprises[0].ID, s.Fixtures.Enterprises[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateEnterprise(context.Background(), s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("updating enterprise: encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *EnterpriseTestSuite) TestGetEnterpriseByID() {
enterprise, err := s.Store.GetEnterpriseByID(context.Background(), s.Fixtures.Enterprises[0].ID)
enterprise, err := s.Store.GetEnterpriseByID(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Enterprises[0].ID, enterprise.ID)
}
func (s *EnterpriseTestSuite) TestGetEnterpriseByIDInvalidEnterpriseID() {
_, err := s.Store.GetEnterpriseByID(context.Background(), "dummy-enterprise-id")
_, err := s.Store.GetEnterpriseByID(s.adminCtx, "dummy-enterprise-id")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
@ -397,7 +455,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseByIDDBDecryptingErr() {
WithArgs(s.Fixtures.Enterprises[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"enterprise_id"}).AddRow(s.Fixtures.Enterprises[0].ID))
_, err := s.StoreSQLMocked.GetEnterpriseByID(context.Background(), s.Fixtures.Enterprises[0].ID)
_, err := s.StoreSQLMocked.GetEnterpriseByID(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -407,11 +465,11 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseByIDDBDecryptingErr() {
func (s *EnterpriseTestSuite) TestCreateEnterprisePool() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().Nil(err)
enterprise, err := s.Store.GetEnterpriseByID(context.Background(), s.Fixtures.Enterprises[0].ID)
enterprise, err := s.Store.GetEnterpriseByID(s.adminCtx, s.Fixtures.Enterprises[0].ID)
if err != nil {
s.FailNow(fmt.Sprintf("cannot get enterprise by ID: %v", err))
}
@ -426,7 +484,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolMissingTags() {
s.Fixtures.CreatePoolParams.Tags = []string{}
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("no tags specified", err.Error())
@ -437,7 +495,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
_, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -455,7 +513,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBCreateErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("checking pool existence: mocked creating pool error", err.Error())
@ -484,7 +542,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseDBPoolAlreadyExistErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal(runnerErrors.NewConflictError("pool with the same image and flavor already exists on this provider"), err)
@ -511,7 +569,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBFetchTagErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
@ -546,7 +604,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBAddingPoolErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
@ -585,7 +643,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBSaveTagErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
@ -633,7 +691,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBFetchPoolErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
@ -646,14 +704,14 @@ func (s *EnterpriseTestSuite) TestListEnterprisePools() {
s.Require().Nil(err)
for i := 1; i <= 2; i++ {
s.Fixtures.CreatePoolParams.Flavor = fmt.Sprintf("test-flavor-%v", i)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
enterprisePools = append(enterprisePools, pool)
}
pools, err := s.Store.ListEntityPools(context.Background(), entity)
pools, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().Nil(err)
garmTesting.EqualDBEntityID(s.T(), enterprisePools, pools)
@ -664,7 +722,7 @@ func (s *EnterpriseTestSuite) TestListEnterprisePoolsInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
_, err := s.Store.ListEntityPools(context.Background(), entity)
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
@ -673,12 +731,12 @@ func (s *EnterpriseTestSuite) TestListEnterprisePoolsInvalidEnterpriseID() {
func (s *EnterpriseTestSuite) TestGetEnterprisePool() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
enterprisePool, err := s.Store.GetEntityPool(context.Background(), entity, pool.ID)
enterprisePool, err := s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
s.Require().Equal(enterprisePool.ID, pool.ID)
@ -689,7 +747,7 @@ func (s *EnterpriseTestSuite) TestGetEnterprisePoolInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
_, err := s.Store.GetEntityPool(context.Background(), entity, "dummy-pool-id")
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
@ -698,15 +756,15 @@ func (s *EnterpriseTestSuite) TestGetEnterprisePoolInvalidEnterpriseID() {
func (s *EnterpriseTestSuite) TestDeleteEnterprisePool() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
err = s.Store.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.Store.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(context.Background(), entity, pool.ID)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
}
@ -715,7 +773,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
err := s.Store.DeleteEntityPool(context.Background(), entity, "dummy-pool-id")
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -724,7 +782,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolInvalidEnterpriseID() {
func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolDBDeleteErr() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
@ -736,7 +794,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked deleting pool error"))
s.Fixtures.SQLMock.ExpectRollback()
err = s.StoreSQLMocked.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
s.assertSQLMockExpectations()
@ -745,21 +803,21 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolDBDeleteErr() {
func (s *EnterpriseTestSuite) TestListEnterpriseInstances() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
poolInstances := []params.Instance{}
for i := 1; i <= 3; i++ {
s.Fixtures.CreateInstanceParams.Name = fmt.Sprintf("test-enterprise-%v", i)
instance, err := s.Store.CreateInstance(context.Background(), pool.ID, s.Fixtures.CreateInstanceParams)
instance, err := s.Store.CreateInstance(s.adminCtx, pool.ID, s.Fixtures.CreateInstanceParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create instance: %s", err))
}
poolInstances = append(poolInstances, instance)
}
instances, err := s.Store.ListEntityInstances(context.Background(), entity)
instances, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().Nil(err)
s.equalInstancesByName(poolInstances, instances)
@ -770,7 +828,7 @@ func (s *EnterpriseTestSuite) TestListEnterpriseInstancesInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
_, err := s.Store.ListEntityInstances(context.Background(), entity)
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
@ -779,12 +837,12 @@ func (s *EnterpriseTestSuite) TestListEnterpriseInstancesInvalidEnterpriseID() {
func (s *EnterpriseTestSuite) TestUpdateEnterprisePool() {
entity, err := s.Fixtures.Enterprises[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create enterprise pool: %v", err))
}
pool, err = s.Store.UpdateEntityPool(context.Background(), entity, pool.ID, s.Fixtures.UpdatePoolParams)
pool, err = s.Store.UpdateEntityPool(s.adminCtx, entity, pool.ID, s.Fixtures.UpdatePoolParams)
s.Require().Nil(err)
s.Require().Equal(*s.Fixtures.UpdatePoolParams.MaxRunners, pool.MaxRunners)
@ -798,7 +856,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
ID: "dummy-enterprise-id",
EntityType: params.GithubEntityTypeEnterprise,
}
_, err := s.Store.UpdateEntityPool(context.Background(), entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())

533
database/sql/github.go Normal file
View file

@ -0,0 +1,533 @@
// Copyright 2024 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package sql
import (
"context"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm-provider-common/util"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
)
const (
defaultGithubEndpoint string = "github.com"
)
func (s *sqlDatabase) sqlToCommonGithubCredentials(creds GithubCredentials) (params.GithubCredentials, error) {
if len(creds.Payload) == 0 {
return params.GithubCredentials{}, errors.New("empty credentials payload")
}
data, err := util.Unseal(creds.Payload, []byte(s.cfg.Passphrase))
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "unsealing credentials")
}
ep, err := s.sqlToCommonGithubEndpoint(creds.Endpoint)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "converting github endpoint")
}
commonCreds := params.GithubCredentials{
ID: creds.ID,
Name: creds.Name,
Description: creds.Description,
APIBaseURL: creds.Endpoint.APIBaseURL,
BaseURL: creds.Endpoint.BaseURL,
UploadBaseURL: creds.Endpoint.UploadBaseURL,
CABundle: creds.Endpoint.CACertBundle,
AuthType: creds.AuthType,
Endpoint: ep,
CredentialsPayload: data,
}
for _, repo := range creds.Repositories {
commonRepo, err := s.sqlToCommonRepository(repo, false)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "converting github repository")
}
commonCreds.Repositories = append(commonCreds.Repositories, commonRepo)
}
for _, org := range creds.Organizations {
commonOrg, err := s.sqlToCommonOrganization(org, false)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "converting github organization")
}
commonCreds.Organizations = append(commonCreds.Organizations, commonOrg)
}
for _, ent := range creds.Enterprises {
commonEnt, err := s.sqlToCommonEnterprise(ent, false)
if err != nil {
return params.GithubCredentials{}, errors.Wrapf(err, "converting github enterprise: %s", ent.Name)
}
commonCreds.Enterprises = append(commonCreds.Enterprises, commonEnt)
}
return commonCreds, nil
}
func (s *sqlDatabase) sqlToCommonGithubEndpoint(ep GithubEndpoint) (params.GithubEndpoint, error) {
return params.GithubEndpoint{
Name: ep.Name,
Description: ep.Description,
APIBaseURL: ep.APIBaseURL,
BaseURL: ep.BaseURL,
UploadBaseURL: ep.UploadBaseURL,
CACertBundle: ep.CACertBundle,
}, nil
}
func getUIDFromContext(ctx context.Context) (uuid.UUID, error) {
userID := auth.UserID(ctx)
if userID == "" {
return uuid.Nil, errors.Wrap(runnerErrors.ErrUnauthorized, "getting UID from context")
}
asUUID, err := uuid.Parse(userID)
if err != nil {
return uuid.Nil, errors.Wrap(runnerErrors.ErrUnauthorized, "parsing UID from context")
}
return asUUID, nil
}
func (s *sqlDatabase) CreateGithubEndpoint(_ context.Context, param params.CreateGithubEndpointParams) (params.GithubEndpoint, error) {
var endpoint GithubEndpoint
err := s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ?", param.Name).First(&endpoint).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "github endpoint already exists")
}
endpoint = GithubEndpoint{
Name: param.Name,
Description: param.Description,
APIBaseURL: param.APIBaseURL,
BaseURL: param.BaseURL,
UploadBaseURL: param.UploadBaseURL,
CACertBundle: param.CACertBundle,
}
if err := tx.Create(&endpoint).Error; err != nil {
return errors.Wrap(err, "creating github endpoint")
}
return nil
})
if err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "creating github endpoint")
}
return s.sqlToCommonGithubEndpoint(endpoint)
}
func (s *sqlDatabase) ListGithubEndpoints(_ context.Context) ([]params.GithubEndpoint, error) {
var endpoints []GithubEndpoint
err := s.conn.Find(&endpoints).Error
if err != nil {
return nil, errors.Wrap(err, "fetching github endpoints")
}
var ret []params.GithubEndpoint
for _, ep := range endpoints {
commonEp, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return nil, errors.Wrap(err, "converting github endpoint")
}
ret = append(ret, commonEp)
}
return ret, nil
}
func (s *sqlDatabase) UpdateGithubEndpoint(_ context.Context, name string, param params.UpdateGithubEndpointParams) (params.GithubEndpoint, error) {
if name == defaultGithubEndpoint {
return params.GithubEndpoint{}, errors.Wrap(runnerErrors.ErrBadRequest, "cannot update default github endpoint")
}
var endpoint GithubEndpoint
err := s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ?", name).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
}
return errors.Wrap(err, "fetching github endpoint")
}
if param.APIBaseURL != nil {
endpoint.APIBaseURL = *param.APIBaseURL
}
if param.BaseURL != nil {
endpoint.BaseURL = *param.BaseURL
}
if param.UploadBaseURL != nil {
endpoint.UploadBaseURL = *param.UploadBaseURL
}
if param.CACertBundle != nil {
endpoint.CACertBundle = param.CACertBundle
}
if param.Description != nil {
endpoint.Description = *param.Description
}
if err := tx.Save(&endpoint).Error; err != nil {
return errors.Wrap(err, "updating github endpoint")
}
return nil
})
if err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "updating github endpoint")
}
return s.sqlToCommonGithubEndpoint(endpoint)
}
func (s *sqlDatabase) GetGithubEndpoint(_ context.Context, name string) (params.GithubEndpoint, error) {
var endpoint GithubEndpoint
err := s.conn.Where("name = ?", name).First(&endpoint).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.GithubEndpoint{}, errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
}
return params.GithubEndpoint{}, errors.Wrap(err, "fetching github endpoint")
}
return s.sqlToCommonGithubEndpoint(endpoint)
}
func (s *sqlDatabase) DeleteGithubEndpoint(_ context.Context, name string) error {
if name == defaultGithubEndpoint {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete default github endpoint")
}
err := s.conn.Transaction(func(tx *gorm.DB) error {
var endpoint GithubEndpoint
if err := tx.Where("name = ?", name).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching github endpoint")
}
var credsCount int64
if err := tx.Model(&GithubCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github credentials")
}
}
var repoCnt int64
if err := tx.Model(&Repository{}).Where("endpoint_name = ?", endpoint.Name).Count(&repoCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github repositories")
}
}
var orgCnt int64
if err := tx.Model(&Organization{}).Where("endpoint_name = ?", endpoint.Name).Count(&orgCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github organizations")
}
}
var entCnt int64
if err := tx.Model(&Enterprise{}).Where("endpoint_name = ?", endpoint.Name).Count(&entCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github enterprises")
}
}
if credsCount > 0 || repoCnt > 0 || orgCnt > 0 || entCnt > 0 {
return errors.New("cannot delete endpoint with associated entities")
}
if err := tx.Unscoped().Delete(&endpoint).Error; err != nil {
return errors.Wrap(err, "deleting github endpoint")
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting github endpoint")
}
return nil
}
func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (params.GithubCredentials, error) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "creating github credentials")
}
if param.Endpoint == "" {
return params.GithubCredentials{}, errors.Wrap(runnerErrors.ErrBadRequest, "endpoint name is required")
}
var creds GithubCredentials
err = s.conn.Transaction(func(tx *gorm.DB) error {
var endpoint GithubEndpoint
if err := tx.Where("name = ?", param.Endpoint).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
}
return errors.Wrap(err, "fetching github endpoint")
}
if err := tx.Where("name = ? and user_id = ?", param.Name, userID).First(&creds).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "github credentials already exists")
}
var data []byte
var err error
switch param.AuthType {
case params.GithubAuthTypePAT:
data, err = s.marshalAndSeal(param.PAT)
case params.GithubAuthTypeApp:
data, err = s.marshalAndSeal(param.App)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
}
creds = GithubCredentials{
Name: param.Name,
Description: param.Description,
EndpointName: &endpoint.Name,
AuthType: param.AuthType,
Payload: data,
UserID: &userID,
}
if err := tx.Create(&creds).Error; err != nil {
return errors.Wrap(err, "creating github credentials")
}
// Skip making an extra query.
creds.Endpoint = endpoint
return nil
})
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "creating github credentials")
}
return s.sqlToCommonGithubCredentials(creds)
}
func (s *sqlDatabase) getGithubCredentialsByName(ctx context.Context, tx *gorm.DB, name string, detailed bool) (GithubCredentials, error) {
var creds GithubCredentials
q := tx.Preload("Endpoint")
if detailed {
q = q.
Preload("Repositories").
Preload("Organizations").
Preload("Enterprises")
}
userID, err := getUIDFromContext(ctx)
if err != nil {
return GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
}
q = q.Where("user_id = ?", userID)
err = q.Where("name = ?", name).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return GithubCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
}
return GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
}
return creds, nil
}
func (s *sqlDatabase) GetGithubCredentialsByName(ctx context.Context, name string, detailed bool) (params.GithubCredentials, error) {
creds, err := s.getGithubCredentialsByName(ctx, s.conn, name, detailed)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
}
return s.sqlToCommonGithubCredentials(creds)
}
func (s *sqlDatabase) GetGithubCredentials(ctx context.Context, id uint, detailed bool) (params.GithubCredentials, error) {
var creds GithubCredentials
q := s.conn.Preload("Endpoint")
if detailed {
q = q.
Preload("Repositories").
Preload("Organizations").
Preload("Enterprises")
}
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
}
q = q.Where("user_id = ?", userID)
}
err := q.Where("id = ?", id).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.GithubCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
}
return params.GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
}
return s.sqlToCommonGithubCredentials(creds)
}
func (s *sqlDatabase) ListGithubCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
q := s.conn.Preload("Endpoint")
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
}
q = q.Where("user_id = ?", userID)
}
var creds []GithubCredentials
err := q.Preload("Endpoint").Find(&creds).Error
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
}
var ret []params.GithubCredentials
for _, c := range creds {
commonCreds, err := s.sqlToCommonGithubCredentials(c)
if err != nil {
return nil, errors.Wrap(err, "converting github credentials")
}
ret = append(ret, commonCreds)
}
return ret, nil
}
func (s *sqlDatabase) UpdateGithubCredentials(ctx context.Context, id uint, param params.UpdateGithubCredentialsParams) (params.GithubCredentials, error) {
var creds GithubCredentials
err := s.conn.Transaction(func(tx *gorm.DB) error {
q := tx.Preload("Endpoint")
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "updating github credentials")
}
q = q.Where("user_id = ?", userID)
}
if err := q.Where("id = ?", id).First(&creds).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
}
return errors.Wrap(err, "fetching github credentials")
}
if param.Name != nil {
creds.Name = *param.Name
}
if param.Description != nil {
creds.Description = *param.Description
}
var data []byte
var err error
switch creds.AuthType {
case params.GithubAuthTypePAT:
if param.PAT != nil {
data, err = s.marshalAndSeal(param.PAT)
}
if param.App != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update app credentials for PAT")
}
case params.GithubAuthTypeApp:
if param.App != nil {
data, err = s.marshalAndSeal(param.App)
}
if param.PAT != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update PAT credentials for app")
}
default:
// This should never happen, unless there was a bug in the DB migration code,
// or the DB was manually modified.
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
}
if len(data) > 0 {
creds.Payload = data
}
if err := tx.Save(&creds).Error; err != nil {
return errors.Wrap(err, "updating github credentials")
}
return nil
})
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "updating github credentials")
}
return s.sqlToCommonGithubCredentials(creds)
}
func (s *sqlDatabase) DeleteGithubCredentials(ctx context.Context, id uint) error {
err := s.conn.Transaction(func(tx *gorm.DB) error {
q := tx.Where("id = ?", id).
Preload("Repositories").
Preload("Organizations").
Preload("Enterprises")
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "deleting github credentials")
}
q = q.Where("user_id = ?", userID)
}
var creds GithubCredentials
err := q.First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching github credentials")
}
if len(creds.Repositories) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with repositories")
}
if len(creds.Organizations) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with organizations")
}
if len(creds.Enterprises) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with enterprises")
}
if err := tx.Unscoped().Delete(&creds).Error; err != nil {
return errors.Wrap(err, "deleting github credentials")
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting github credentials")
}
return nil
}

880
database/sql/github_test.go Normal file
View file

@ -0,0 +1,880 @@
// Copyright 2024 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package sql
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
const (
testUploadBaseURL string = "https://uploads.example.com"
testBaseURL string = "https://example.com"
testAPIBaseURL string = "https://api.example.com"
testEndpointName string = "test-endpoint"
testEndpointDescription string = "test description"
testCredsName string = "test-creds"
testCredsDescription string = "test creds"
)
type GithubTestSuite struct {
suite.Suite
db common.Store
}
func (s *GithubTestSuite) SetupTest() {
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.db = db
}
func (s *GithubTestSuite) TestDefaultEndpointGetsCreatedAutomatically() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
endpoint, err := s.db.GetGithubEndpoint(ctx, defaultGithubEndpoint)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
}
func (s *GithubTestSuite) TestDeletingDefaultEndpointFails() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
err := s.db.DeleteGithubEndpoint(ctx, defaultGithubEndpoint)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
}
func (s *GithubTestSuite) TestCreatingEndpoint() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
s.Require().Equal(testEndpointName, endpoint.Name)
}
func (s *GithubTestSuite) TestCreatingDuplicateEndpointFails() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
_, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
_, err = s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrDuplicateEntity)
}
func (s *GithubTestSuite) TestGetEndpoint() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
endpoint, err := s.db.GetGithubEndpoint(ctx, defaultGithubEndpoint)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
s.Require().Equal(defaultGithubEndpoint, endpoint.Name)
}
func (s *GithubTestSuite) TestGetNonExistingEndpointFailsWithNotFoundError() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
_, err := s.db.GetGithubEndpoint(ctx, "non-existing")
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestDeletingNonExistingEndpointIsANoop() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
err := s.db.DeleteGithubEndpoint(ctx, "non-existing")
s.Require().NoError(err)
}
func (s *GithubTestSuite) TestDeletingEndpoint() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
err = s.db.DeleteGithubEndpoint(ctx, testEndpointName)
s.Require().NoError(err)
_, err = s.db.GetGithubEndpoint(ctx, testEndpointName)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestUpdateEndpoint() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
newDescription := "new description"
newAPIBaseURL := "https://new-api.example.com"
newUploadBaseURL := "https://new-uploads.example.com"
newBaseURL := "https://new.example.com"
caCertBundle, err := os.ReadFile("../../testdata/certs/srv-pub.pem")
s.Require().NoError(err)
updateEpParams := params.UpdateGithubEndpointParams{
Description: &newDescription,
APIBaseURL: &newAPIBaseURL,
UploadBaseURL: &newUploadBaseURL,
BaseURL: &newBaseURL,
CACertBundle: caCertBundle,
}
updatedEndpoint, err := s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().NoError(err)
s.Require().NotNil(updatedEndpoint)
s.Require().Equal(newDescription, updatedEndpoint.Description)
s.Require().Equal(newAPIBaseURL, updatedEndpoint.APIBaseURL)
s.Require().Equal(newUploadBaseURL, updatedEndpoint.UploadBaseURL)
s.Require().Equal(newBaseURL, updatedEndpoint.BaseURL)
s.Require().Equal(caCertBundle, updatedEndpoint.CACertBundle)
}
func (s *GithubTestSuite) TestUpdatingNonExistingEndpointReturnsNotFoundError() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
newDescription := "test"
updateEpParams := params.UpdateGithubEndpointParams{
Description: &newDescription,
}
_, err := s.db.UpdateGithubEndpoint(ctx, "non-existing", updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestListEndpoints() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
_, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
endpoints, err := s.db.ListGithubEndpoints(ctx)
s.Require().NoError(err)
s.Require().Len(endpoints, 2)
}
func (s *GithubTestSuite) TestCreateCredentialsFailsWithUnauthorizedForAnonUser() {
ctx := context.Background()
_, err := s.db.CreateGithubCredentials(ctx, params.CreateGithubCredentialsParams{})
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *GithubTestSuite) TestCreateCredentialsFailsWhenEndpointNameIsEmpty() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
_, err := s.db.CreateGithubCredentials(ctx, params.CreateGithubCredentialsParams{})
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().Regexp("endpoint name is required", err.Error())
}
func (s *GithubTestSuite) TestCreateCredentialsFailsWhenEndpointDoesNotExist() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
_, err := s.db.CreateGithubCredentials(ctx, params.CreateGithubCredentialsParams{Endpoint: "non-existing"})
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
s.Require().Regexp("endpoint not found", err.Error())
}
func (s *GithubTestSuite) TestCreateCredentialsFailsWhenAuthTypeIsInvalid() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
_, err := s.db.CreateGithubCredentials(ctx, params.CreateGithubCredentialsParams{Endpoint: defaultGithubEndpoint, AuthType: "invalid"})
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().Regexp("invalid auth type", err.Error())
}
func (s *GithubTestSuite) TestCreateCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
s.Require().Equal(credParams.Name, creds.Name)
s.Require().Equal(credParams.Description, creds.Description)
s.Require().Equal(credParams.Endpoint, creds.Endpoint.Name)
s.Require().Equal(credParams.AuthType, creds.AuthType)
}
func (s *GithubTestSuite) TestCreateCredentialsFailsOnDuplicateCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "testuser", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
_, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
// Creating creds with the same parameters should fail for the same user.
_, err = s.db.CreateGithubCredentials(ctx, credParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrDuplicateEntity)
// Creating creds with the same parameters should work for different users.
_, err = s.db.CreateGithubCredentials(testUserCtx, credParams)
s.Require().NoError(err)
}
func (s *GithubTestSuite) TestNormalUsersCanOnlySeeTheirOwnCredentialsAdminCanSeeAll() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "testuser1", s.db, s.T())
testUser2 := garmTesting.CreateGARMTestUser(ctx, "testuser2", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
testUser2Ctx := auth.PopulateContext(context.Background(), testUser2)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
credParams.Name = "test-creds2"
creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds2)
credParams.Name = "test-creds3"
creds3, err := s.db.CreateGithubCredentials(testUser2Ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds3)
credsList, err := s.db.ListGithubCredentials(ctx)
s.Require().NoError(err)
s.Require().Len(credsList, 3)
credsList, err = s.db.ListGithubCredentials(testUserCtx)
s.Require().NoError(err)
s.Require().Len(credsList, 1)
s.Require().Equal("test-creds2", credsList[0].Name)
credsList, err = s.db.ListGithubCredentials(testUser2Ctx)
s.Require().NoError(err)
s.Require().Len(credsList, 1)
s.Require().Equal("test-creds3", credsList[0].Name)
}
func (s *GithubTestSuite) TestGetGithubCredentialsFailsWhenCredentialsDontExist() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
_, err := s.db.GetGithubCredentials(ctx, 1, true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
_, err = s.db.GetGithubCredentialsByName(ctx, "non-existing", true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestGetGithubCredentialsByNameReturnsOnlyCurrentUserCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "test-user1", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds2)
creds2Get, err := s.db.GetGithubCredentialsByName(testUserCtx, testCredsName, true)
s.Require().NoError(err)
s.Require().NotNil(creds2)
s.Require().Equal(testCredsName, creds2Get.Name)
s.Require().Equal(creds2.ID, creds2Get.ID)
credsGet, err := s.db.GetGithubCredentialsByName(ctx, testCredsName, true)
s.Require().NoError(err)
s.Require().NotNil(creds)
s.Require().Equal(testCredsName, credsGet.Name)
s.Require().Equal(creds.ID, credsGet.ID)
// Admin can get any creds by ID
credsGet, err = s.db.GetGithubCredentials(ctx, creds2.ID, true)
s.Require().NoError(err)
s.Require().NotNil(creds2)
s.Require().Equal(creds2.ID, credsGet.ID)
// Normal user cannot get other user creds by ID
_, err = s.db.GetGithubCredentials(testUserCtx, creds.ID, true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestGetGithubCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
creds2, err := s.db.GetGithubCredentialsByName(ctx, testCredsName, true)
s.Require().NoError(err)
s.Require().NotNil(creds2)
s.Require().Equal(creds.Name, creds2.Name)
s.Require().Equal(creds.ID, creds2.ID)
creds2, err = s.db.GetGithubCredentials(ctx, creds.ID, true)
s.Require().NoError(err)
s.Require().NotNil(creds2)
s.Require().Equal(creds.Name, creds2.Name)
s.Require().Equal(creds.ID, creds2.ID)
}
func (s *GithubTestSuite) TestDeleteGithubCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
err = s.db.DeleteGithubCredentials(ctx, creds.ID)
s.Require().NoError(err)
_, err = s.db.GetGithubCredentials(ctx, creds.ID, true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestDeleteGithubCredentialsByNonAdminUser() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "test-user4", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test-creds4",
},
}
// Create creds as admin
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
// Deleting non existent creds will return a nil error. For the test user
// the creds created by the admin should not be visible, which leads to not found
// which in turn returns no error.
err = s.db.DeleteGithubCredentials(testUserCtx, creds.ID)
s.Require().NoError(err)
// Check that the creds created by the admin are still there.
credsGet, err := s.db.GetGithubCredentials(ctx, creds.ID, true)
s.Require().NoError(err)
s.Require().NotNil(credsGet)
s.Require().Equal(creds.ID, credsGet.ID)
// Create the same creds with the test user.
creds2, err := s.db.CreateGithubCredentials(testUserCtx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds2)
// Remove creds created by test user.
err = s.db.DeleteGithubCredentials(testUserCtx, creds2.ID)
s.Require().NoError(err)
// The creds created by the test user should be gone.
_, err = s.db.GetGithubCredentials(testUserCtx, creds2.ID, true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestDeleteCredentialsFailsIfReposOrgsOrEntitiesUseIt() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
repo, err := s.db.CreateRepository(ctx, "test-owner", "test-repo", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotNil(repo)
err = s.db.DeleteGithubCredentials(ctx, creds.ID)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
err = s.db.DeleteRepository(ctx, repo.ID)
s.Require().NoError(err)
org, err := s.db.CreateOrganization(ctx, "test-org", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotNil(org)
err = s.db.DeleteGithubCredentials(ctx, creds.ID)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
err = s.db.DeleteOrganization(ctx, org.ID)
s.Require().NoError(err)
enterprise, err := s.db.CreateEnterprise(ctx, "test-enterprise", creds.Name, "superSecret@123BlaBla", params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
s.Require().NotNil(enterprise)
err = s.db.DeleteGithubCredentials(ctx, creds.ID)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
err = s.db.DeleteEnterprise(ctx, enterprise.ID)
s.Require().NoError(err)
err = s.db.DeleteGithubCredentials(ctx, creds.ID)
s.Require().NoError(err)
_, err = s.db.GetGithubCredentials(ctx, creds.ID, true)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestUpdateCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
newDescription := "new description"
newName := "new-name"
newToken := "new-token"
updateCredParams := params.UpdateGithubCredentialsParams{
Description: &newDescription,
Name: &newName,
PAT: &params.GithubPAT{
OAuth2Token: newToken,
},
}
updatedCreds, err := s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams)
s.Require().NoError(err)
s.Require().NotNil(updatedCreds)
s.Require().Equal(newDescription, updatedCreds.Description)
s.Require().Equal(newName, updatedCreds.Name)
}
func (s *GithubTestSuite) TestUpdateGithubCredentialsFailIfWrongCredentialTypeIsPassed() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
updateCredParams := params.UpdateGithubCredentialsParams{
App: &params.GithubApp{
AppID: 1,
InstallationID: 2,
PrivateKeyBytes: []byte("test"),
},
}
_, err = s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github credentials: cannot update app credentials for PAT: invalid request")
credParamsWithApp := params.CreateGithubCredentialsParams{
Name: "test-credsApp",
Description: "test credsApp",
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypeApp,
App: params.GithubApp{
AppID: 1,
InstallationID: 2,
PrivateKeyBytes: []byte("test"),
},
}
credsApp, err := s.db.CreateGithubCredentials(ctx, credParamsWithApp)
s.Require().NoError(err)
s.Require().NotNil(credsApp)
updateCredParams = params.UpdateGithubCredentialsParams{
PAT: &params.GithubPAT{
OAuth2Token: "test",
},
}
_, err = s.db.UpdateGithubCredentials(ctx, credsApp.ID, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github credentials: cannot update PAT credentials for app: invalid request")
}
func (s *GithubTestSuite) TestUpdateCredentialsFailsForNonExistingCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
updateCredParams := params.UpdateGithubCredentialsParams{
Description: nil,
}
_, err := s.db.UpdateGithubCredentials(ctx, 1, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestUpdateCredentialsFailsIfCredentialsAreOwnedByNonAdminUser() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "test-user5", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test-creds5",
},
}
creds, err := s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
newDescription := "new description2"
updateCredParams := params.UpdateGithubCredentialsParams{
Description: &newDescription,
}
_, err = s.db.UpdateGithubCredentials(testUserCtx, creds.ID, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestAdminUserCanUpdateAnyGithubCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
testUser := garmTesting.CreateGARMTestUser(ctx, "test-user5", s.db, s.T())
testUserCtx := auth.PopulateContext(context.Background(), testUser)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: defaultGithubEndpoint,
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test-creds5",
},
}
creds, err := s.db.CreateGithubCredentials(testUserCtx, credParams)
s.Require().NoError(err)
s.Require().NotNil(creds)
newDescription := "new description2"
updateCredParams := params.UpdateGithubCredentialsParams{
Description: &newDescription,
}
newCreds, err := s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams)
s.Require().NoError(err)
s.Require().Equal(newDescription, newCreds.Description)
}
func TestGithubTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(GithubTestSuite))
}
func TestCredentialsAndEndpointMigration(t *testing.T) {
cfg := garmTesting.GetTestSqliteDBConfig(t)
// Copy the sample DB
data, err := os.ReadFile("../../testdata/db/v0.1.4/garm.db")
if err != nil {
t.Fatalf("failed to read test data: %s", err)
}
if cfg.SQLite.DBFile == "" {
t.Fatalf("DB file not set")
}
if err := os.WriteFile(cfg.SQLite.DBFile, data, 0o600); err != nil {
t.Fatalf("failed to write test data: %s", err)
}
// define some credentials
credentials := []config.Github{
{
Name: "test-creds",
Description: "test creds",
AuthType: config.GithubAuthTypePAT,
PAT: config.GithubPAT{
OAuth2Token: "test",
},
},
{
Name: "ghes-test",
Description: "ghes creds",
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
AuthType: config.GithubAuthTypeApp,
App: config.GithubApp{
AppID: 1,
InstallationID: 99,
PrivateKeyPath: "../../testdata/certs/srv-key.pem",
},
},
}
// Set the config credentials in the cfg. This is what happens in the main function.
// of GARM as well.
cfg.MigrateCredentials = credentials
db, err := NewSQLDatabase(context.Background(), cfg)
if err != nil {
t.Fatalf("failed to create db connection: %s", err)
}
// We expect that 2 endpoints will exist in the migrated DB and 2 credentials.
ctx := garmTesting.ImpersonateAdminContext(context.Background(), db, t)
endpoints, err := db.ListGithubEndpoints(ctx)
if err != nil {
t.Fatalf("failed to list endpoints: %s", err)
}
if len(endpoints) != 2 {
t.Fatalf("expected 2 endpoints, got %d", len(endpoints))
}
if endpoints[0].Name != defaultGithubEndpoint {
t.Fatalf("expected default endpoint to exist, got %s", endpoints[0].Name)
}
if endpoints[1].Name != "example.com" {
t.Fatalf("expected example.com endpoint to exist, got %s", endpoints[1].Name)
}
if endpoints[1].UploadBaseURL != testUploadBaseURL {
t.Fatalf("expected upload base URL to be %s, got %s", testUploadBaseURL, endpoints[1].UploadBaseURL)
}
if endpoints[1].BaseURL != testBaseURL {
t.Fatalf("expected base URL to be %s, got %s", testBaseURL, endpoints[1].BaseURL)
}
if endpoints[1].APIBaseURL != testAPIBaseURL {
t.Fatalf("expected API base URL to be %s, got %s", testAPIBaseURL, endpoints[1].APIBaseURL)
}
creds, err := db.ListGithubCredentials(ctx)
if err != nil {
t.Fatalf("failed to list credentials: %s", err)
}
if len(creds) != 2 {
t.Fatalf("expected 2 credentials, got %d", len(creds))
}
if creds[0].Name != "test-creds" {
t.Fatalf("expected test-creds to exist, got %s", creds[0].Name)
}
if creds[1].Name != "ghes-test" {
t.Fatalf("expected ghes-test to exist, got %s", creds[1].Name)
}
if creds[0].Endpoint.Name != defaultGithubEndpoint {
t.Fatalf("expected test-creds to be associated with default endpoint, got %s", creds[0].Endpoint.Name)
}
if creds[1].Endpoint.Name != "example.com" {
t.Fatalf("expected ghes-test to be associated with example.com endpoint, got %s", creds[1].Endpoint.Name)
}
if creds[0].AuthType != params.GithubAuthTypePAT {
t.Fatalf("expected test-creds to have PAT auth type, got %s", creds[0].AuthType)
}
if creds[1].AuthType != params.GithubAuthTypeApp {
t.Fatalf("expected ghes-test to have App auth type, got %s", creds[1].AuthType)
}
if len(creds[0].CredentialsPayload) == 0 {
t.Fatalf("expected test-creds to have credentials payload, got empty")
}
var pat params.GithubPAT
if err := json.Unmarshal(creds[0].CredentialsPayload, &pat); err != nil {
t.Fatalf("failed to unmarshal test-creds credentials payload: %s", err)
}
if pat.OAuth2Token != "test" {
t.Fatalf("expected test-creds to have PAT token test, got %s", pat.OAuth2Token)
}
var app params.GithubApp
if err := json.Unmarshal(creds[1].CredentialsPayload, &app); err != nil {
t.Fatalf("failed to unmarshal ghes-test credentials payload: %s", err)
}
if app.AppID != 1 {
t.Fatalf("expected ghes-test to have app ID 1, got %d", app.AppID)
}
if app.InstallationID != 99 {
t.Fatalf("expected ghes-test to have installation ID 99, got %d", app.InstallationID)
}
if app.PrivateKeyBytes == nil {
t.Fatalf("expected ghes-test to have private key bytes, got nil")
}
certBundle, err := credentials[1].App.PrivateKeyBytes()
if err != nil {
t.Fatalf("failed to read CA cert bundle: %s", err)
}
if !bytes.Equal(app.PrivateKeyBytes, certBundle) {
t.Fatalf("expected ghes-test private key to be equal to the CA cert bundle")
}
}

View file

@ -48,6 +48,7 @@ type InstancesTestSuite struct {
Store dbCommon.Store
StoreSQLMocked *sqlDatabase
Fixtures *InstancesTestFixtures
adminCtx context.Context
}
func (s *InstancesTestSuite) equalInstancesByName(expected, actual []params.Instance) {
@ -76,8 +77,14 @@ func (s *InstancesTestSuite) SetupTest() {
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
githubEndpoint := garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
creds := garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), githubEndpoint)
// create an organization for testing purposes
org, err := s.Store.CreateOrganization(context.Background(), "test-org", "test-creds", "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
org, err := s.Store.CreateOrganization(s.adminCtx, "test-org", creds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create org: %s", err))
}
@ -94,7 +101,7 @@ func (s *InstancesTestSuite) SetupTest() {
}
entity, err := org.GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, createPoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, createPoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create org pool: %s", err))
}
@ -103,7 +110,7 @@ func (s *InstancesTestSuite) SetupTest() {
instances := []params.Instance{}
for i := 1; i <= 3; i++ {
instance, err := db.CreateInstance(
context.Background(),
s.adminCtx,
pool.ID,
params.CreateInstanceParams{
Name: fmt.Sprintf("test-instance-%d", i),
@ -179,11 +186,11 @@ func (s *InstancesTestSuite) SetupTest() {
func (s *InstancesTestSuite) TestCreateInstance() {
// call tested function
instance, err := s.Store.CreateInstance(context.Background(), s.Fixtures.Pool.ID, s.Fixtures.CreateInstanceParams)
instance, err := s.Store.CreateInstance(s.adminCtx, s.Fixtures.Pool.ID, s.Fixtures.CreateInstanceParams)
// assertions
s.Require().Nil(err)
storeInstance, err := s.Store.GetInstanceByName(context.Background(), s.Fixtures.CreateInstanceParams.Name)
storeInstance, err := s.Store.GetInstanceByName(s.adminCtx, s.Fixtures.CreateInstanceParams.Name)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get instance: %v", err))
}
@ -195,7 +202,7 @@ func (s *InstancesTestSuite) TestCreateInstance() {
}
func (s *InstancesTestSuite) TestCreateInstanceInvalidPoolID() {
_, err := s.Store.CreateInstance(context.Background(), "dummy-pool-id", params.CreateInstanceParams{})
_, err := s.Store.CreateInstance(s.adminCtx, "dummy-pool-id", params.CreateInstanceParams{})
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
}
@ -216,7 +223,7 @@ func (s *InstancesTestSuite) TestCreateInstanceDBCreateErr() {
WillReturnError(fmt.Errorf("mocked insert instance error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.CreateInstance(context.Background(), pool.ID, s.Fixtures.CreateInstanceParams)
_, err := s.StoreSQLMocked.CreateInstance(s.adminCtx, pool.ID, s.Fixtures.CreateInstanceParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -226,7 +233,7 @@ func (s *InstancesTestSuite) TestCreateInstanceDBCreateErr() {
func (s *InstancesTestSuite) TestGetPoolInstanceByName() {
storeInstance := s.Fixtures.Instances[0] // this is already created in `SetupTest()`
instance, err := s.Store.GetPoolInstanceByName(context.Background(), s.Fixtures.Pool.ID, storeInstance.Name)
instance, err := s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Nil(err)
s.Require().Equal(storeInstance.Name, instance.Name)
@ -237,7 +244,7 @@ func (s *InstancesTestSuite) TestGetPoolInstanceByName() {
}
func (s *InstancesTestSuite) TestGetPoolInstanceByNameNotFound() {
_, err := s.Store.GetPoolInstanceByName(context.Background(), s.Fixtures.Pool.ID, "not-existent-instance-name")
_, err := s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, "not-existent-instance-name")
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
}
@ -245,7 +252,7 @@ func (s *InstancesTestSuite) TestGetPoolInstanceByNameNotFound() {
func (s *InstancesTestSuite) TestGetInstanceByName() {
storeInstance := s.Fixtures.Instances[1]
instance, err := s.Store.GetInstanceByName(context.Background(), storeInstance.Name)
instance, err := s.Store.GetInstanceByName(s.adminCtx, storeInstance.Name)
s.Require().Nil(err)
s.Require().Equal(storeInstance.Name, instance.Name)
@ -256,7 +263,7 @@ func (s *InstancesTestSuite) TestGetInstanceByName() {
}
func (s *InstancesTestSuite) TestGetInstanceByNameFetchInstanceFailed() {
_, err := s.Store.GetInstanceByName(context.Background(), "not-existent-instance-name")
_, err := s.Store.GetInstanceByName(s.adminCtx, "not-existent-instance-name")
s.Require().Equal("fetching instance: fetching instance by name: not found", err.Error())
}
@ -264,16 +271,16 @@ func (s *InstancesTestSuite) TestGetInstanceByNameFetchInstanceFailed() {
func (s *InstancesTestSuite) TestDeleteInstance() {
storeInstance := s.Fixtures.Instances[0]
err := s.Store.DeleteInstance(context.Background(), s.Fixtures.Pool.ID, storeInstance.Name)
err := s.Store.DeleteInstance(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Nil(err)
_, err = s.Store.GetPoolInstanceByName(context.Background(), s.Fixtures.Pool.ID, storeInstance.Name)
_, err = s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
}
func (s *InstancesTestSuite) TestDeleteInstanceInvalidPoolID() {
err := s.Store.DeleteInstance(context.Background(), "dummy-pool-id", "dummy-instance-name")
err := s.Store.DeleteInstance(s.adminCtx, "dummy-pool-id", "dummy-instance-name")
s.Require().Equal("deleting instance: fetching pool: parsing id: invalid request", err.Error())
}
@ -309,7 +316,7 @@ func (s *InstancesTestSuite) TestDeleteInstanceDBRecordNotFoundErr() {
WillReturnError(gorm.ErrRecordNotFound)
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeleteInstance(context.Background(), pool.ID, instance.Name)
err := s.StoreSQLMocked.DeleteInstance(s.adminCtx, pool.ID, instance.Name)
s.assertSQLMockExpectations()
s.Require().Nil(err)
@ -346,7 +353,7 @@ func (s *InstancesTestSuite) TestDeleteInstanceDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked delete instance error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeleteInstance(context.Background(), pool.ID, instance.Name)
err := s.StoreSQLMocked.DeleteInstance(s.adminCtx, pool.ID, instance.Name)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -357,10 +364,10 @@ func (s *InstancesTestSuite) TestAddInstanceEvent() {
storeInstance := s.Fixtures.Instances[0]
statusMsg := "test-status-message"
err := s.Store.AddInstanceEvent(context.Background(), storeInstance.Name, params.StatusEvent, params.EventInfo, statusMsg)
err := s.Store.AddInstanceEvent(s.adminCtx, storeInstance.Name, params.StatusEvent, params.EventInfo, statusMsg)
s.Require().Nil(err)
instance, err := s.Store.GetInstanceByName(context.Background(), storeInstance.Name)
instance, err := s.Store.GetInstanceByName(s.adminCtx, storeInstance.Name)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get db instance: %s", err))
}
@ -398,7 +405,7 @@ func (s *InstancesTestSuite) TestAddInstanceEventDBUpdateErr() {
WillReturnError(fmt.Errorf("mocked add status message error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.AddInstanceEvent(context.Background(), instance.Name, params.StatusEvent, params.EventInfo, statusMsg)
err := s.StoreSQLMocked.AddInstanceEvent(s.adminCtx, instance.Name, params.StatusEvent, params.EventInfo, statusMsg)
s.Require().NotNil(err)
s.Require().Equal("adding status message: mocked add status message error", err.Error())
@ -406,7 +413,7 @@ func (s *InstancesTestSuite) TestAddInstanceEventDBUpdateErr() {
}
func (s *InstancesTestSuite) TestUpdateInstance() {
instance, err := s.Store.UpdateInstance(context.Background(), s.Fixtures.Instances[0].Name, s.Fixtures.UpdateInstanceParams)
instance, err := s.Store.UpdateInstance(s.adminCtx, s.Fixtures.Instances[0].Name, s.Fixtures.UpdateInstanceParams)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateInstanceParams.ProviderID, instance.ProviderID)
@ -443,7 +450,7 @@ func (s *InstancesTestSuite) TestUpdateInstanceDBUpdateInstanceErr() {
WillReturnError(fmt.Errorf("mocked update instance error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateInstance(context.Background(), instance.Name, s.Fixtures.UpdateInstanceParams)
_, err := s.StoreSQLMocked.UpdateInstance(s.adminCtx, instance.Name, s.Fixtures.UpdateInstanceParams)
s.Require().NotNil(err)
s.Require().Equal("updating instance: mocked update instance error", err.Error())
@ -489,7 +496,7 @@ func (s *InstancesTestSuite) TestUpdateInstanceDBUpdateAddressErr() {
WillReturnError(fmt.Errorf("update addresses mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateInstance(context.Background(), instance.Name, s.Fixtures.UpdateInstanceParams)
_, err := s.StoreSQLMocked.UpdateInstance(s.adminCtx, instance.Name, s.Fixtures.UpdateInstanceParams)
s.Require().NotNil(err)
s.Require().Equal("updating addresses: update addresses mock error", err.Error())
@ -497,20 +504,20 @@ func (s *InstancesTestSuite) TestUpdateInstanceDBUpdateAddressErr() {
}
func (s *InstancesTestSuite) TestListPoolInstances() {
instances, err := s.Store.ListPoolInstances(context.Background(), s.Fixtures.Pool.ID)
instances, err := s.Store.ListPoolInstances(s.adminCtx, s.Fixtures.Pool.ID)
s.Require().Nil(err)
s.equalInstancesByName(s.Fixtures.Instances, instances)
}
func (s *InstancesTestSuite) TestListPoolInstancesInvalidPoolID() {
_, err := s.Store.ListPoolInstances(context.Background(), "dummy-pool-id")
_, err := s.Store.ListPoolInstances(s.adminCtx, "dummy-pool-id")
s.Require().Equal("parsing id: invalid request", err.Error())
}
func (s *InstancesTestSuite) TestListAllInstances() {
instances, err := s.Store.ListAllInstances(context.Background())
instances, err := s.Store.ListAllInstances(s.adminCtx)
s.Require().Nil(err)
s.equalInstancesByName(s.Fixtures.Instances, instances)
@ -521,7 +528,7 @@ func (s *InstancesTestSuite) TestListAllInstancesDBFetchErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `instances` WHERE `instances`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetch instances mock error"))
_, err := s.StoreSQLMocked.ListAllInstances(context.Background())
_, err := s.StoreSQLMocked.ListAllInstances(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -529,14 +536,14 @@ func (s *InstancesTestSuite) TestListAllInstancesDBFetchErr() {
}
func (s *InstancesTestSuite) TestPoolInstanceCount() {
instancesCount, err := s.Store.PoolInstanceCount(context.Background(), s.Fixtures.Pool.ID)
instancesCount, err := s.Store.PoolInstanceCount(s.adminCtx, s.Fixtures.Pool.ID)
s.Require().Nil(err)
s.Require().Equal(int64(len(s.Fixtures.Instances)), instancesCount)
}
func (s *InstancesTestSuite) TestPoolInstanceCountInvalidPoolID() {
_, err := s.Store.PoolInstanceCount(context.Background(), "dummy-pool-id")
_, err := s.Store.PoolInstanceCount(s.adminCtx, "dummy-pool-id")
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
}
@ -553,7 +560,7 @@ func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
WithArgs(pool.ID).
WillReturnError(fmt.Errorf("count mock error"))
_, err := s.StoreSQLMocked.PoolInstanceCount(context.Background(), pool.ID)
_, err := s.StoreSQLMocked.PoolInstanceCount(s.adminCtx, pool.ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)

View file

@ -89,35 +89,56 @@ type Pool struct {
type Repository struct {
Base
CredentialsName string
CredentialsName string
CredentialsID *uint `gorm:"index"`
Credentials GithubCredentials `gorm:"foreignKey:CredentialsID;constraint:OnDelete:SET NULL"`
Owner string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
Name string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:RepoID"`
Jobs []WorkflowJob `gorm:"foreignKey:RepoID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
}
type Organization struct {
Base
CredentialsName string
CredentialsName string
CredentialsID *uint `gorm:"index"`
Credentials GithubCredentials `gorm:"foreignKey:CredentialsID;constraint:OnDelete:SET NULL"`
Name string `gorm:"index:idx_org_name_nocase,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:OrgID"`
Jobs []WorkflowJob `gorm:"foreignKey:OrgID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
}
type Enterprise struct {
Base
CredentialsName string
CredentialsName string
CredentialsID *uint `gorm:"index"`
Credentials GithubCredentials `gorm:"foreignKey:CredentialsID;constraint:OnDelete:SET NULL"`
Name string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:EnterpriseID"`
Jobs []WorkflowJob `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:SET NULL"`
PoolBalancerType params.PoolBalancerType `gorm:"type:varchar(64)"`
EndpointName *string `gorm:"index"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
}
type Address struct {
@ -246,3 +267,35 @@ type WorkflowJob struct {
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
type GithubEndpoint struct {
Name string `gorm:"type:varchar(64) collate nocase;primary_key;"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Description string `gorm:"type:text"`
APIBaseURL string `gorm:"type:text collate nocase"`
UploadBaseURL string `gorm:"type:text collate nocase"`
BaseURL string `gorm:"type:text collate nocase"`
CACertBundle []byte `gorm:"type:longblob"`
}
type GithubCredentials struct {
gorm.Model
Name string `gorm:"index:idx_github_credentials,unique;type:varchar(64) collate nocase"`
UserID *uuid.UUID `gorm:"index:idx_github_credentials,unique"`
User User `gorm:"foreignKey:UserID"`
Description string `gorm:"type:text"`
AuthType params.GithubAuthType `gorm:"index"`
Payload []byte `gorm:"type:longblob"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName"`
EndpointName *string `gorm:"index"`
Repositories []Repository `gorm:"foreignKey:CredentialsID"`
Organizations []Organization `gorm:"foreignKey:CredentialsID"`
Enterprises []Enterprise `gorm:"foreignKey:CredentialsID"`
}

View file

@ -27,13 +27,13 @@ import (
"github.com/cloudbase/garm/params"
)
func (s *sqlDatabase) CreateOrganization(_ context.Context, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Organization, error) {
func (s *sqlDatabase) CreateOrganization(ctx context.Context, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Organization, error) {
if webhookSecret == "" {
return params.Organization{}, errors.New("creating org: missing secret")
}
secret, err := util.Seal([]byte(webhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return params.Organization{}, fmt.Errorf("failed to encrypt string")
return params.Organization{}, errors.Wrap(err, "encoding secret")
}
newOrg := Organization{
Name: name,
@ -42,12 +42,33 @@ func (s *sqlDatabase) CreateOrganization(_ context.Context, name, credentialsNam
PoolBalancerType: poolBalancerType,
}
q := s.conn.Create(&newOrg)
if q.Error != nil {
return params.Organization{}, errors.Wrap(q.Error, "creating org")
err = s.conn.Transaction(func(tx *gorm.DB) error {
creds, err := s.getGithubCredentialsByName(ctx, tx, credentialsName, false)
if err != nil {
return errors.Wrap(err, "creating org")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
newOrg.CredentialsID = &creds.ID
newOrg.CredentialsName = creds.Name
newOrg.EndpointName = creds.EndpointName
q := tx.Create(&newOrg)
if q.Error != nil {
return errors.Wrap(q.Error, "creating org")
}
newOrg.Credentials = creds
newOrg.Endpoint = creds.Endpoint
return nil
})
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
}
param, err := s.sqlToCommonOrganization(newOrg)
param, err := s.sqlToCommonOrganization(newOrg, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
}
@ -62,7 +83,7 @@ func (s *sqlDatabase) GetOrganization(ctx context.Context, name string) (params.
return params.Organization{}, errors.Wrap(err, "fetching org")
}
param, err := s.sqlToCommonOrganization(org)
param, err := s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}
@ -72,7 +93,11 @@ func (s *sqlDatabase) GetOrganization(ctx context.Context, name string) (params.
func (s *sqlDatabase) ListOrganizations(_ context.Context) ([]params.Organization, error) {
var orgs []Organization
q := s.conn.Find(&orgs)
q := s.conn.
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
Find(&orgs)
if q.Error != nil {
return []params.Organization{}, errors.Wrap(q.Error, "fetching org from database")
}
@ -80,7 +105,7 @@ func (s *sqlDatabase) ListOrganizations(_ context.Context) ([]params.Organizatio
ret := make([]params.Organization, len(orgs))
for idx, val := range orgs {
var err error
ret[idx], err = s.sqlToCommonOrganization(val)
ret[idx], err = s.sqlToCommonOrganization(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching org")
}
@ -90,7 +115,7 @@ func (s *sqlDatabase) ListOrganizations(_ context.Context) ([]params.Organizatio
}
func (s *sqlDatabase) DeleteOrganization(ctx context.Context, orgID string) error {
org, err := s.getOrgByID(ctx, orgID)
org, err := s.getOrgByID(ctx, s.conn, orgID)
if err != nil {
return errors.Wrap(err, "fetching org")
}
@ -104,33 +129,66 @@ func (s *sqlDatabase) DeleteOrganization(ctx context.Context, orgID string) erro
}
func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, param params.UpdateEntityParams) (params.Organization, error) {
org, err := s.getOrgByID(ctx, orgID)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}
if param.CredentialsName != "" {
org.CredentialsName = param.CredentialsName
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
var org Organization
var creds GithubCredentials
err := s.conn.Transaction(func(tx *gorm.DB) error {
var err error
org, err = s.getOrgByID(ctx, tx, orgID)
if err != nil {
return params.Organization{}, fmt.Errorf("saving org: failed to encrypt string: %w", err)
return errors.Wrap(err, "fetching org")
}
org.WebhookSecret = secret
if org.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "org has no endpoint")
}
if param.CredentialsName != "" {
org.CredentialsName = param.CredentialsName
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
if *creds.EndpointName != *org.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
}
org.CredentialsID = &creds.ID
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return fmt.Errorf("saving org: failed to encrypt string: %w", err)
}
org.WebhookSecret = secret
}
if param.PoolBalancerType != "" {
org.PoolBalancerType = param.PoolBalancerType
}
q := tx.Save(&org)
if q.Error != nil {
return errors.Wrap(q.Error, "saving org")
}
if creds.ID != 0 {
org.Credentials = creds
}
return nil
})
if err != nil {
return params.Organization{}, errors.Wrap(err, "saving org")
}
if param.PoolBalancerType != "" {
org.PoolBalancerType = param.PoolBalancerType
org, err = s.getOrgByID(ctx, s.conn, orgID, "Endpoint", "Credentials")
if err != nil {
return params.Organization{}, errors.Wrap(err, "updating enterprise")
}
q := s.conn.Save(&org)
if q.Error != nil {
return params.Organization{}, errors.Wrap(q.Error, "saving org")
}
newParams, err := s.sqlToCommonOrganization(org)
newParams, err := s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "saving org")
}
@ -138,26 +196,26 @@ func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, para
}
func (s *sqlDatabase) GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error) {
org, err := s.getOrgByID(ctx, orgID, "Pools")
org, err := s.getOrgByID(ctx, s.conn, orgID, "Pools", "Credentials", "Endpoint")
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}
param, err := s.sqlToCommonOrganization(org)
param, err := s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching enterprise")
return params.Organization{}, errors.Wrap(err, "fetching org")
}
return param, nil
}
func (s *sqlDatabase) getOrgByID(_ context.Context, id string, preload ...string) (Organization, error) {
func (s *sqlDatabase) getOrgByID(_ context.Context, db *gorm.DB, id string, preload ...string) (Organization, error) {
u, err := uuid.Parse(id)
if err != nil {
return Organization{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var org Organization
q := s.conn
q := db
if len(preload) > 0 {
for _, field := range preload {
q = q.Preload(field)
@ -177,8 +235,11 @@ func (s *sqlDatabase) getOrgByID(_ context.Context, id string, preload ...string
func (s *sqlDatabase) getOrg(_ context.Context, name string) (Organization, error) {
var org Organization
q := s.conn.Where("name = ? COLLATE NOCASE", name)
q = q.First(&org)
q := s.conn.Where("name = ? COLLATE NOCASE", name).
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
First(&org)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Organization{}, runnerErrors.ErrNotFound

View file

@ -29,6 +29,7 @@ import (
"gorm.io/gorm/logger"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
@ -49,6 +50,13 @@ type OrgTestSuite struct {
Store dbCommon.Store
StoreSQLMocked *sqlDatabase
Fixtures *OrgTestFixtures
adminCtx context.Context
adminUserID string
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *OrgTestSuite) equalInstancesByName(expected, actual []params.Instance) {
@ -71,24 +79,34 @@ func (s *OrgTestSuite) assertSQLMockExpectations() {
func (s *OrgTestSuite) SetupTest() {
// create testing sqlite database
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
dbConfig := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := NewSQLDatabase(context.Background(), dbConfig)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
s.adminUserID = auth.UserID(adminCtx)
s.Require().NotEmpty(s.adminUserID)
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some organization objects in the database, for testing purposes
orgs := []params.Organization{}
for i := 1; i <= 3; i++ {
org, err := db.CreateOrganization(
context.Background(),
s.adminCtx,
fmt.Sprintf("test-org-%d", i),
fmt.Sprintf("test-creds-%d", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%d", i),
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (test-org-%d)", i))
s.FailNow(fmt.Sprintf("failed to create database object (test-org-%d): %q", i, err))
}
orgs = append(orgs, org)
@ -114,7 +132,7 @@ func (s *OrgTestSuite) SetupTest() {
}
s.StoreSQLMocked = &sqlDatabase{
conn: gormConn,
cfg: garmTesting.GetTestSqliteDBConfig(s.T()),
cfg: dbConfig,
}
// setup test fixtures
@ -123,8 +141,8 @@ func (s *OrgTestSuite) SetupTest() {
fixtures := &OrgTestFixtures{
Orgs: orgs,
CreateOrgParams: params.CreateOrgParams{
Name: "new-test-org",
CredentialsName: "new-creds",
Name: s.testCreds.Name,
CredentialsName: s.testCreds.Name,
WebhookSecret: "new-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -143,7 +161,7 @@ func (s *OrgTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-update-creds",
CredentialsName: s.secondaryTestCreds.Name,
WebhookSecret: "test-update-repo-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -160,7 +178,7 @@ func (s *OrgTestSuite) SetupTest() {
func (s *OrgTestSuite) TestCreateOrganization() {
// call tested function
org, err := s.Store.CreateOrganization(
context.Background(),
s.adminCtx,
s.Fixtures.CreateOrgParams.Name,
s.Fixtures.CreateOrgParams.CredentialsName,
s.Fixtures.CreateOrgParams.WebhookSecret,
@ -168,12 +186,12 @@ func (s *OrgTestSuite) TestCreateOrganization() {
// assertions
s.Require().Nil(err)
storeOrg, err := s.Store.GetOrganizationByID(context.Background(), org.ID)
storeOrg, err := s.Store.GetOrganizationByID(s.adminCtx, org.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get organization by id: %v", err))
}
s.Require().Equal(storeOrg.Name, org.Name)
s.Require().Equal(storeOrg.CredentialsName, org.CredentialsName)
s.Require().Equal(storeOrg.Credentials.Name, org.Credentials.Name)
s.Require().Equal(storeOrg.WebhookSecret, org.WebhookSecret)
}
@ -191,37 +209,46 @@ func (s *OrgTestSuite) TestCreateOrganizationInvalidDBPassphrase() {
}
_, err = sqlDB.CreateOrganization(
context.Background(),
s.adminCtx,
s.Fixtures.CreateOrgParams.Name,
s.Fixtures.CreateOrgParams.CredentialsName,
s.Fixtures.CreateOrgParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("failed to encrypt string", err.Error())
s.Require().Equal("encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationDBCreateErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.Fixtures.Orgs[0].CredentialsName, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.testCreds.ID, s.githubEndpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.githubEndpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(regexp.QuoteMeta("INSERT INTO `organizations`")).
WillReturnError(fmt.Errorf("creating org mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.CreateOrganization(
context.Background(),
s.adminCtx,
s.Fixtures.CreateOrgParams.Name,
s.Fixtures.CreateOrgParams.CredentialsName,
s.Fixtures.CreateOrgParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("creating org: creating org mock error", err.Error())
s.Require().Equal("creating org: creating org: creating org mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *OrgTestSuite) TestGetOrganization() {
org, err := s.Store.GetOrganization(context.Background(), s.Fixtures.Orgs[0].Name)
org, err := s.Store.GetOrganization(s.adminCtx, s.Fixtures.Orgs[0].Name)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Orgs[0].Name, org.Name)
@ -229,14 +256,14 @@ func (s *OrgTestSuite) TestGetOrganization() {
}
func (s *OrgTestSuite) TestGetOrganizationCaseInsensitive() {
org, err := s.Store.GetOrganization(context.Background(), "TeSt-oRg-1")
org, err := s.Store.GetOrganization(s.adminCtx, "TeSt-oRg-1")
s.Require().Nil(err)
s.Require().Equal("test-org-1", org.Name)
}
func (s *OrgTestSuite) TestGetOrganizationNotFound() {
_, err := s.Store.GetOrganization(context.Background(), "dummy-name")
_, err := s.Store.GetOrganization(s.adminCtx, "dummy-name")
s.Require().NotNil(err)
s.Require().Equal("fetching org: not found", err.Error())
@ -248,7 +275,7 @@ func (s *OrgTestSuite) TestGetOrganizationDBDecryptingErr() {
WithArgs(s.Fixtures.Orgs[0].Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(s.Fixtures.Orgs[0].Name))
_, err := s.StoreSQLMocked.GetOrganization(context.Background(), s.Fixtures.Orgs[0].Name)
_, err := s.StoreSQLMocked.GetOrganization(s.adminCtx, s.Fixtures.Orgs[0].Name)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -256,7 +283,7 @@ func (s *OrgTestSuite) TestGetOrganizationDBDecryptingErr() {
}
func (s *OrgTestSuite) TestListOrganizations() {
orgs, err := s.Store.ListOrganizations(context.Background())
orgs, err := s.Store.ListOrganizations(s.adminCtx)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), s.Fixtures.Orgs, orgs)
@ -267,7 +294,7 @@ func (s *OrgTestSuite) TestListOrganizationsDBFetchErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE `organizations`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListOrganizations(context.Background())
_, err := s.StoreSQLMocked.ListOrganizations(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -275,16 +302,16 @@ func (s *OrgTestSuite) TestListOrganizationsDBFetchErr() {
}
func (s *OrgTestSuite) TestDeleteOrganization() {
err := s.Store.DeleteOrganization(context.Background(), s.Fixtures.Orgs[0].ID)
err := s.Store.DeleteOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.Require().Nil(err)
_, err = s.Store.GetOrganizationByID(context.Background(), s.Fixtures.Orgs[0].ID)
_, err = s.Store.GetOrganizationByID(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching org: not found", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationInvalidOrgID() {
err := s.Store.DeleteOrganization(context.Background(), "dummy-org-id")
err := s.Store.DeleteOrganization(s.adminCtx, "dummy-org-id")
s.Require().NotNil(err)
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
@ -302,7 +329,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked delete org error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeleteOrganization(context.Background(), s.Fixtures.Orgs[0].ID)
err := s.StoreSQLMocked.DeleteOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -310,78 +337,111 @@ func (s *OrgTestSuite) TestDeleteOrganizationDBDeleteErr() {
}
func (s *OrgTestSuite) TestUpdateOrganization() {
org, err := s.Store.UpdateOrganization(context.Background(), s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
org, err := s.Store.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, org.WebhookSecret)
}
func (s *OrgTestSuite) TestUpdateOrganizationInvalidOrgID() {
_, err := s.Store.UpdateOrganization(context.Background(), "dummy-org-id", s.Fixtures.UpdateRepoParams)
_, err := s.Store.UpdateOrganization(s.adminCtx, "dummy-org-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
s.Require().Equal("saving org: fetching org: parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestUpdateOrganizationDBEncryptErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE id = ? AND `organizations`.`deleted_at` IS NULL ORDER BY `organizations`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Orgs[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Orgs[0].ID))
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateOrganization(context.Background(), s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *OrgTestSuite) TestUpdateOrganizationDBSaveErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE id = ? AND `organizations`.`deleted_at` IS NULL ORDER BY `organizations`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Orgs[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Orgs[0].ID))
s.Fixtures.SQLMock.ExpectBegin()
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(("UPDATE `organizations` SET")).
WillReturnError(fmt.Errorf("saving org mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateOrganization(context.Background(), s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving org: saving org mock error", err.Error())
s.Require().Equal("saving org: saving org: saving org mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *OrgTestSuite) TestUpdateOrganizationDBDecryptingErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.UpdateRepoParams.WebhookSecret = webhookSecret
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE id = ? AND `organizations`.`deleted_at` IS NULL ORDER BY `organizations`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Orgs[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Orgs[0].ID))
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Orgs[0].ID, s.Fixtures.Orgs[0].Endpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateOrganization(context.Background(), s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *OrgTestSuite) TestGetOrganizationByID() {
org, err := s.Store.GetOrganizationByID(context.Background(), s.Fixtures.Orgs[0].ID)
org, err := s.Store.GetOrganizationByID(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Orgs[0].ID, org.ID)
}
func (s *OrgTestSuite) TestGetOrganizationByIDInvalidOrgID() {
_, err := s.Store.GetOrganizationByID(context.Background(), "dummy-org-id")
_, err := s.Store.GetOrganizationByID(s.adminCtx, "dummy-org-id")
s.Require().NotNil(err)
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
@ -397,21 +457,21 @@ func (s *OrgTestSuite) TestGetOrganizationByIDDBDecryptingErr() {
WithArgs(s.Fixtures.Orgs[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"org_id"}).AddRow(s.Fixtures.Orgs[0].ID))
_, err := s.StoreSQLMocked.GetOrganizationByID(context.Background(), s.Fixtures.Orgs[0].ID)
_, err := s.StoreSQLMocked.GetOrganizationByID(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: missing secret", err.Error())
s.Require().Equal("fetching org: missing secret", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationPool() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().Nil(err)
org, err := s.Store.GetOrganizationByID(context.Background(), s.Fixtures.Orgs[0].ID)
org, err := s.Store.GetOrganizationByID(s.adminCtx, s.Fixtures.Orgs[0].ID)
if err != nil {
s.FailNow(fmt.Sprintf("cannot get org by ID: %v", err))
}
@ -426,7 +486,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolMissingTags() {
s.Fixtures.CreatePoolParams.Tags = []string{}
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("no tags specified", err.Error())
@ -437,7 +497,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
_, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -455,7 +515,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBCreateErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("checking pool existence: mocked creating pool error", err.Error())
@ -484,7 +544,7 @@ func (s *OrgTestSuite) TestCreateOrganizationDBPoolAlreadyExistErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal(runnerErrors.NewConflictError("pool with the same image and flavor already exists on this provider"), err)
@ -511,7 +571,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBFetchTagErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
@ -547,7 +607,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBAddingPoolErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
@ -586,7 +646,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBSaveTagErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
@ -635,7 +695,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBFetchPoolErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
@ -648,13 +708,13 @@ func (s *OrgTestSuite) TestListOrgPools() {
s.Require().Nil(err)
for i := 1; i <= 2; i++ {
s.Fixtures.CreatePoolParams.Flavor = fmt.Sprintf("test-flavor-%v", i)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
orgPools = append(orgPools, pool)
}
pools, err := s.Store.ListEntityPools(context.Background(), entity)
pools, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().Nil(err)
garmTesting.EqualDBEntityID(s.T(), orgPools, pools)
@ -665,7 +725,7 @@ func (s *OrgTestSuite) TestListOrgPoolsInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
_, err := s.Store.ListEntityPools(context.Background(), entity)
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
@ -674,12 +734,12 @@ func (s *OrgTestSuite) TestListOrgPoolsInvalidOrgID() {
func (s *OrgTestSuite) TestGetOrganizationPool() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
orgPool, err := s.Store.GetEntityPool(context.Background(), entity, pool.ID)
orgPool, err := s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
s.Require().Equal(orgPool.ID, pool.ID)
@ -690,7 +750,7 @@ func (s *OrgTestSuite) TestGetOrganizationPoolInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
_, err := s.Store.GetEntityPool(context.Background(), entity, "dummy-pool-id")
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
@ -699,15 +759,15 @@ func (s *OrgTestSuite) TestGetOrganizationPoolInvalidOrgID() {
func (s *OrgTestSuite) TestDeleteOrganizationPool() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
err = s.Store.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.Store.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(context.Background(), entity, pool.ID)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
}
@ -716,7 +776,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
err := s.Store.DeleteEntityPool(context.Background(), entity, "dummy-pool-id")
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -726,7 +786,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolDBDeleteErr() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
@ -738,7 +798,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked deleting pool error"))
s.Fixtures.SQLMock.ExpectRollback()
err = s.StoreSQLMocked.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
@ -748,21 +808,21 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolDBDeleteErr() {
func (s *OrgTestSuite) TestListOrgInstances() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
poolInstances := []params.Instance{}
for i := 1; i <= 3; i++ {
s.Fixtures.CreateInstanceParams.Name = fmt.Sprintf("test-org-%v", i)
instance, err := s.Store.CreateInstance(context.Background(), pool.ID, s.Fixtures.CreateInstanceParams)
instance, err := s.Store.CreateInstance(s.adminCtx, pool.ID, s.Fixtures.CreateInstanceParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create instance: %s", err))
}
poolInstances = append(poolInstances, instance)
}
instances, err := s.Store.ListEntityInstances(context.Background(), entity)
instances, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().Nil(err)
s.equalInstancesByName(poolInstances, instances)
@ -773,7 +833,7 @@ func (s *OrgTestSuite) TestListOrgInstancesInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
_, err := s.Store.ListEntityInstances(context.Background(), entity)
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
@ -782,12 +842,12 @@ func (s *OrgTestSuite) TestListOrgInstancesInvalidOrgID() {
func (s *OrgTestSuite) TestUpdateOrganizationPool() {
entity, err := s.Fixtures.Orgs[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create org pool: %v", err))
}
pool, err = s.Store.UpdateEntityPool(context.Background(), entity, pool.ID, s.Fixtures.UpdatePoolParams)
pool, err = s.Store.UpdateEntityPool(s.adminCtx, entity, pool.ID, s.Fixtures.UpdatePoolParams)
s.Require().Nil(err)
s.Require().Equal(*s.Fixtures.UpdatePoolParams.MaxRunners, pool.MaxRunners)
@ -801,7 +861,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
ID: "dummy-org-id",
EntityType: params.GithubEntityTypeOrganization,
}
_, err := s.Store.UpdateEntityPool(context.Background(), entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())

View file

@ -43,6 +43,7 @@ type PoolsTestSuite struct {
Store dbCommon.Store
StoreSQLMocked *sqlDatabase
Fixtures *PoolsTestFixtures
adminCtx context.Context
}
func (s *PoolsTestSuite) assertSQLMockExpectations() {
@ -60,8 +61,14 @@ func (s *PoolsTestSuite) SetupTest() {
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
githubEndpoint := garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
creds := garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), githubEndpoint)
// create an organization for testing purposes
org, err := s.Store.CreateOrganization(context.Background(), "test-org", "test-creds", "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
org, err := s.Store.CreateOrganization(s.adminCtx, "test-org", creds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create org: %s", err))
}
@ -72,7 +79,7 @@ func (s *PoolsTestSuite) SetupTest() {
orgPools := []params.Pool{}
for i := 1; i <= 3; i++ {
pool, err := db.CreateEntityPool(
context.Background(),
s.adminCtx,
entity,
params.CreatePoolParams{
ProviderName: "test-provider",
@ -122,7 +129,7 @@ func (s *PoolsTestSuite) SetupTest() {
}
func (s *PoolsTestSuite) TestListAllPools() {
pools, err := s.Store.ListAllPools(context.Background())
pools, err := s.Store.ListAllPools(s.adminCtx)
s.Require().Nil(err)
garmTesting.EqualDBEntityID(s.T(), s.Fixtures.Pools, pools)
@ -133,7 +140,7 @@ func (s *PoolsTestSuite) TestListAllPoolsDBFetchErr() {
ExpectQuery(regexp.QuoteMeta("SELECT `pools`.`id`,`pools`.`created_at`,`pools`.`updated_at`,`pools`.`deleted_at`,`pools`.`provider_name`,`pools`.`runner_prefix`,`pools`.`max_runners`,`pools`.`min_idle_runners`,`pools`.`runner_bootstrap_timeout`,`pools`.`image`,`pools`.`flavor`,`pools`.`os_type`,`pools`.`os_arch`,`pools`.`enabled`,`pools`.`git_hub_runner_group`,`pools`.`repo_id`,`pools`.`org_id`,`pools`.`enterprise_id`,`pools`.`priority` FROM `pools` WHERE `pools`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("mocked fetching all pools error"))
_, err := s.StoreSQLMocked.ListAllPools(context.Background())
_, err := s.StoreSQLMocked.ListAllPools(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
@ -141,29 +148,29 @@ func (s *PoolsTestSuite) TestListAllPoolsDBFetchErr() {
}
func (s *PoolsTestSuite) TestGetPoolByID() {
pool, err := s.Store.GetPoolByID(context.Background(), s.Fixtures.Pools[0].ID)
pool, err := s.Store.GetPoolByID(s.adminCtx, s.Fixtures.Pools[0].ID)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Pools[0].ID, pool.ID)
}
func (s *PoolsTestSuite) TestGetPoolByIDInvalidPoolID() {
_, err := s.Store.GetPoolByID(context.Background(), "dummy-pool-id")
_, err := s.Store.GetPoolByID(s.adminCtx, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool by ID: parsing id: invalid request", err.Error())
}
func (s *PoolsTestSuite) TestDeletePoolByID() {
err := s.Store.DeletePoolByID(context.Background(), s.Fixtures.Pools[0].ID)
err := s.Store.DeletePoolByID(s.adminCtx, s.Fixtures.Pools[0].ID)
s.Require().Nil(err)
_, err = s.Store.GetPoolByID(context.Background(), s.Fixtures.Pools[0].ID)
_, err = s.Store.GetPoolByID(s.adminCtx, s.Fixtures.Pools[0].ID)
s.Require().Equal("fetching pool by ID: not found", err.Error())
}
func (s *PoolsTestSuite) TestDeletePoolByIDInvalidPoolID() {
err := s.Store.DeletePoolByID(context.Background(), "dummy-pool-id")
err := s.Store.DeletePoolByID(s.adminCtx, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool by ID: parsing id: invalid request", err.Error())
@ -180,7 +187,7 @@ func (s *PoolsTestSuite) TestDeletePoolByIDDBRemoveErr() {
WillReturnError(fmt.Errorf("mocked removing pool error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeletePoolByID(context.Background(), s.Fixtures.Pools[0].ID)
err := s.StoreSQLMocked.DeletePoolByID(s.adminCtx, s.Fixtures.Pools[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)

View file

@ -27,7 +27,7 @@ import (
"github.com/cloudbase/garm/params"
)
func (s *sqlDatabase) CreateRepository(_ context.Context, owner, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Repository, error) {
func (s *sqlDatabase) CreateRepository(ctx context.Context, owner, name, credentialsName, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Repository, error) {
if webhookSecret == "" {
return params.Repository{}, errors.New("creating repo: missing secret")
}
@ -35,20 +35,40 @@ func (s *sqlDatabase) CreateRepository(_ context.Context, owner, name, credentia
if err != nil {
return params.Repository{}, fmt.Errorf("failed to encrypt string")
}
newRepo := Repository{
Name: name,
Owner: owner,
WebhookSecret: secret,
CredentialsName: credentialsName,
PoolBalancerType: poolBalancerType,
}
err = s.conn.Transaction(func(tx *gorm.DB) error {
creds, err := s.getGithubCredentialsByName(ctx, tx, credentialsName, false)
if err != nil {
return errors.Wrap(err, "creating repository")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
newRepo.CredentialsID = &creds.ID
newRepo.CredentialsName = creds.Name
newRepo.EndpointName = creds.EndpointName
q := s.conn.Create(&newRepo)
if q.Error != nil {
return params.Repository{}, errors.Wrap(q.Error, "creating repository")
q := tx.Create(&newRepo)
if q.Error != nil {
return errors.Wrap(q.Error, "creating repository")
}
newRepo.Credentials = creds
newRepo.Endpoint = creds.Endpoint
return nil
})
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
}
param, err := s.sqlToCommonRepository(newRepo)
param, err := s.sqlToCommonRepository(newRepo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
}
@ -62,7 +82,7 @@ func (s *sqlDatabase) GetRepository(ctx context.Context, owner, name string) (pa
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
param, err := s.sqlToCommonRepository(repo)
param, err := s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
@ -72,7 +92,11 @@ func (s *sqlDatabase) GetRepository(ctx context.Context, owner, name string) (pa
func (s *sqlDatabase) ListRepositories(_ context.Context) ([]params.Repository, error) {
var repos []Repository
q := s.conn.Find(&repos)
q := s.conn.
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
Find(&repos)
if q.Error != nil {
return []params.Repository{}, errors.Wrap(q.Error, "fetching user from database")
}
@ -80,7 +104,7 @@ func (s *sqlDatabase) ListRepositories(_ context.Context) ([]params.Repository,
ret := make([]params.Repository, len(repos))
for idx, val := range repos {
var err error
ret[idx], err = s.sqlToCommonRepository(val)
ret[idx], err = s.sqlToCommonRepository(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching repositories")
}
@ -90,7 +114,7 @@ func (s *sqlDatabase) ListRepositories(_ context.Context) ([]params.Repository,
}
func (s *sqlDatabase) DeleteRepository(ctx context.Context, repoID string) error {
repo, err := s.getRepoByID(ctx, repoID)
repo, err := s.getRepoByID(ctx, s.conn, repoID)
if err != nil {
return errors.Wrap(err, "fetching repo")
}
@ -104,33 +128,65 @@ func (s *sqlDatabase) DeleteRepository(ctx context.Context, repoID string) error
}
func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param params.UpdateEntityParams) (params.Repository, error) {
repo, err := s.getRepoByID(ctx, repoID)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
if param.CredentialsName != "" {
repo.CredentialsName = param.CredentialsName
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
var repo Repository
var creds GithubCredentials
err := s.conn.Transaction(func(tx *gorm.DB) error {
var err error
repo, err = s.getRepoByID(ctx, tx, repoID)
if err != nil {
return params.Repository{}, fmt.Errorf("saving repo: failed to encrypt string: %w", err)
return errors.Wrap(err, "fetching repo")
}
repo.WebhookSecret = secret
if repo.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "repository has no endpoint")
}
if param.CredentialsName != "" {
repo.CredentialsName = param.CredentialsName
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
}
if *creds.EndpointName != *repo.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
}
repo.CredentialsID = &creds.ID
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return fmt.Errorf("saving repo: failed to encrypt string: %w", err)
}
repo.WebhookSecret = secret
}
if param.PoolBalancerType != "" {
repo.PoolBalancerType = param.PoolBalancerType
}
q := tx.Save(&repo)
if q.Error != nil {
return errors.Wrap(q.Error, "saving repo")
}
if creds.ID != 0 {
repo.Credentials = creds
}
return nil
})
if err != nil {
return params.Repository{}, errors.Wrap(err, "saving repo")
}
if param.PoolBalancerType != "" {
repo.PoolBalancerType = param.PoolBalancerType
repo, err = s.getRepoByID(ctx, s.conn, repoID, "Endpoint", "Credentials")
if err != nil {
return params.Repository{}, errors.Wrap(err, "updating enterprise")
}
q := s.conn.Save(&repo)
if q.Error != nil {
return params.Repository{}, errors.Wrap(q.Error, "saving repo")
}
newParams, err := s.sqlToCommonRepository(repo)
newParams, err := s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "saving repo")
}
@ -138,12 +194,12 @@ func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param
}
func (s *sqlDatabase) GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error) {
repo, err := s.getRepoByID(ctx, repoID, "Pools")
repo, err := s.getRepoByID(ctx, s.conn, repoID, "Pools", "Credentials", "Endpoint")
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
param, err := s.sqlToCommonRepository(repo)
param, err := s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
@ -154,6 +210,9 @@ func (s *sqlDatabase) getRepo(_ context.Context, owner, name string) (Repository
var repo Repository
q := s.conn.Where("name = ? COLLATE NOCASE and owner = ? COLLATE NOCASE", name, owner).
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
First(&repo)
q = q.First(&repo)
@ -192,14 +251,14 @@ func (s *sqlDatabase) getEntityPoolByUniqueFields(tx *gorm.DB, entity params.Git
return Pool{}, nil
}
func (s *sqlDatabase) getRepoByID(_ context.Context, id string, preload ...string) (Repository, error) {
func (s *sqlDatabase) getRepoByID(_ context.Context, tx *gorm.DB, id string, preload ...string) (Repository, error) {
u, err := uuid.Parse(id)
if err != nil {
return Repository{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var repo Repository
q := s.conn
q := tx
if len(preload) > 0 {
for _, field := range preload {
q = q.Preload(field)

View file

@ -28,6 +28,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/cloudbase/garm/auth"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
@ -48,6 +49,13 @@ type RepoTestSuite struct {
Store dbCommon.Store
StoreSQLMocked *sqlDatabase
Fixtures *RepoTestFixtures
adminCtx context.Context
adminUserID string
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *RepoTestSuite) equalReposByName(expected, actual []params.Repository) {
@ -87,14 +95,23 @@ func (s *RepoTestSuite) SetupTest() {
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.adminCtx = adminCtx
s.adminUserID = auth.UserID(adminCtx)
s.Require().NotEmpty(s.adminUserID)
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some repository objects in the database, for testing purposes
repos := []params.Repository{}
for i := 1; i <= 3; i++ {
repo, err := db.CreateRepository(
context.Background(),
adminCtx,
fmt.Sprintf("test-owner-%d", i),
fmt.Sprintf("test-repo-%d", i),
fmt.Sprintf("test-creds-%d", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%d", i),
params.PoolBalancerTypeRoundRobin,
)
@ -136,7 +153,7 @@ func (s *RepoTestSuite) SetupTest() {
CreateRepoParams: params.CreateRepoParams{
Owner: "test-owner-repo",
Name: "test-repo",
CredentialsName: "test-creds-repo",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -155,7 +172,7 @@ func (s *RepoTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-update-creds",
CredentialsName: s.secondaryTestCreds.Name,
WebhookSecret: "test-update-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -172,7 +189,7 @@ func (s *RepoTestSuite) SetupTest() {
func (s *RepoTestSuite) TestCreateRepository() {
// call tested function
repo, err := s.Store.CreateRepository(
context.Background(),
s.adminCtx,
s.Fixtures.CreateRepoParams.Owner,
s.Fixtures.CreateRepoParams.Name,
s.Fixtures.CreateRepoParams.CredentialsName,
@ -182,13 +199,13 @@ func (s *RepoTestSuite) TestCreateRepository() {
// assertions
s.Require().Nil(err)
storeRepo, err := s.Store.GetRepositoryByID(context.Background(), repo.ID)
storeRepo, err := s.Store.GetRepositoryByID(s.adminCtx, repo.ID)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get repository by id: %v", err))
}
s.Require().Equal(storeRepo.Owner, repo.Owner)
s.Require().Equal(storeRepo.Name, repo.Name)
s.Require().Equal(storeRepo.CredentialsName, repo.CredentialsName)
s.Require().Equal(storeRepo.Credentials.Name, repo.Credentials.Name)
s.Require().Equal(storeRepo.WebhookSecret, repo.WebhookSecret)
}
@ -206,7 +223,7 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidDBPassphrase() {
}
_, err = sqlDB.CreateRepository(
context.Background(),
s.adminCtx,
s.Fixtures.CreateRepoParams.Owner,
s.Fixtures.CreateRepoParams.Name,
s.Fixtures.CreateRepoParams.CredentialsName,
@ -220,13 +237,22 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidDBPassphrase() {
func (s *RepoTestSuite) TestCreateRepositoryInvalidDBCreateErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.Fixtures.Repos[0].CredentialsName, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.testCreds.ID, s.githubEndpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.githubEndpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(regexp.QuoteMeta("INSERT INTO `repositories`")).
WillReturnError(fmt.Errorf("creating repo mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.CreateRepository(
context.Background(),
s.adminCtx,
s.Fixtures.CreateRepoParams.Owner,
s.Fixtures.CreateRepoParams.Name,
s.Fixtures.CreateRepoParams.CredentialsName,
@ -234,13 +260,13 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidDBCreateErr() {
params.PoolBalancerTypeRoundRobin,
)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("creating repository: creating repo mock error", err.Error())
s.Require().Equal("creating repository: creating repository: creating repo mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestGetRepository() {
repo, err := s.Store.GetRepository(context.Background(), s.Fixtures.Repos[0].Owner, s.Fixtures.Repos[0].Name)
repo, err := s.Store.GetRepository(s.adminCtx, s.Fixtures.Repos[0].Owner, s.Fixtures.Repos[0].Name)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Repos[0].Owner, repo.Owner)
@ -249,7 +275,7 @@ func (s *RepoTestSuite) TestGetRepository() {
}
func (s *RepoTestSuite) TestGetRepositoryCaseInsensitive() {
repo, err := s.Store.GetRepository(context.Background(), "TeSt-oWnEr-1", "TeSt-rEpO-1")
repo, err := s.Store.GetRepository(s.adminCtx, "TeSt-oWnEr-1", "TeSt-rEpO-1")
s.Require().Nil(err)
s.Require().Equal("test-owner-1", repo.Owner)
@ -257,7 +283,7 @@ func (s *RepoTestSuite) TestGetRepositoryCaseInsensitive() {
}
func (s *RepoTestSuite) TestGetRepositoryNotFound() {
_, err := s.Store.GetRepository(context.Background(), "dummy-owner", "dummy-name")
_, err := s.Store.GetRepository(s.adminCtx, "dummy-owner", "dummy-name")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: not found", err.Error())
@ -273,15 +299,15 @@ func (s *RepoTestSuite) TestGetRepositoryDBDecryptingErr() {
WithArgs(s.Fixtures.Repos[0].Name, s.Fixtures.Repos[0].Owner, 1).
WillReturnRows(sqlmock.NewRows([]string{"name", "owner"}).AddRow(s.Fixtures.Repos[0].Name, s.Fixtures.Repos[0].Owner))
_, err := s.StoreSQLMocked.GetRepository(context.Background(), s.Fixtures.Repos[0].Owner, s.Fixtures.Repos[0].Name)
_, err := s.StoreSQLMocked.GetRepository(s.adminCtx, s.Fixtures.Repos[0].Owner, s.Fixtures.Repos[0].Name)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching repo: missing secret", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestListRepositories() {
repos, err := s.Store.ListRepositories((context.Background()))
repos, err := s.Store.ListRepositories(s.adminCtx)
s.Require().Nil(err)
s.equalReposByName(s.Fixtures.Repos, repos)
@ -292,11 +318,11 @@ func (s *RepoTestSuite) TestListRepositoriesDBFetchErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE `repositories`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListRepositories(context.Background())
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching user from database: fetching user from database mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestListRepositoriesDBDecryptingErr() {
@ -306,24 +332,24 @@ func (s *RepoTestSuite) TestListRepositoriesDBDecryptingErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE `repositories`.`deleted_at` IS NULL")).
WillReturnRows(sqlmock.NewRows([]string{"id", "webhook_secret"}).AddRow(s.Fixtures.Repos[0].ID, s.Fixtures.Repos[0].WebhookSecret))
_, err := s.StoreSQLMocked.ListRepositories(context.Background())
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching repositories: decrypting secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestDeleteRepository() {
err := s.Store.DeleteRepository(context.Background(), s.Fixtures.Repos[0].ID)
err := s.Store.DeleteRepository(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().Nil(err)
_, err = s.Store.GetRepositoryByID(context.Background(), s.Fixtures.Repos[0].ID)
_, err = s.Store.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching repo: not found", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryInvalidRepoID() {
err := s.Store.DeleteRepository(context.Background(), "dummy-repo-id")
err := s.Store.DeleteRepository(s.adminCtx, "dummy-repo-id")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
@ -341,85 +367,118 @@ func (s *RepoTestSuite) TestDeleteRepositoryDBRemoveErr() {
WillReturnError(fmt.Errorf("mocked deleting repo error"))
s.Fixtures.SQLMock.ExpectRollback()
err := s.StoreSQLMocked.DeleteRepository(context.Background(), s.Fixtures.Repos[0].ID)
err := s.StoreSQLMocked.DeleteRepository(s.adminCtx, s.Fixtures.Repos[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("deleting repo: mocked deleting repo error", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestUpdateRepository() {
repo, err := s.Store.UpdateRepository(context.Background(), s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
repo, err := s.Store.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, repo.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, repo.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, repo.WebhookSecret)
}
func (s *RepoTestSuite) TestUpdateRepositoryInvalidRepoID() {
_, err := s.Store.UpdateRepository(context.Background(), "dummy-repo-id", s.Fixtures.UpdateRepoParams)
_, err := s.Store.UpdateRepository(s.adminCtx, "dummy-repo-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
s.Require().Equal("saving repo: fetching repo: parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestUpdateRepositoryDBEncryptErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE id = ? AND `repositories`.`deleted_at` IS NULL ORDER BY `repositories`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Repos[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Repos[0].ID))
_, err := s.StoreSQLMocked.UpdateRepository(context.Background(), s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestUpdateRepositoryDBSaveErr() {
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE id = ? AND `repositories`.`deleted_at` IS NULL ORDER BY `repositories`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Repos[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Repos[0].ID))
s.Fixtures.SQLMock.ExpectBegin()
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.
ExpectExec(("UPDATE `repositories` SET")).
WillReturnError(fmt.Errorf("saving repo mock error"))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateRepository(context.Background(), s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving repo: saving repo mock error", err.Error())
s.Require().Equal("saving repo: saving repo: saving repo mock error", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestUpdateRepositoryDBDecryptingErr() {
s.StoreSQLMocked.cfg.Passphrase = wrongPassphrase
s.Fixtures.UpdateRepoParams.WebhookSecret = webhookSecret
s.Fixtures.SQLMock.ExpectBegin()
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE id = ? AND `repositories`.`deleted_at` IS NULL ORDER BY `repositories`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Repos[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Repos[0].ID))
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.Fixtures.Repos[0].ID, s.githubEndpoint.Name))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_credentials` WHERE user_id = ? AND name = ? AND `github_credentials`.`deleted_at` IS NULL ORDER BY `github_credentials`.`id` LIMIT ?")).
WithArgs(s.adminUserID, s.secondaryTestCreds.Name, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "endpoint_name"}).
AddRow(s.secondaryTestCreds.ID, s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `github_endpoints` WHERE `github_endpoints`.`name` = ? AND `github_endpoints`.`deleted_at` IS NULL")).
WithArgs(s.testCreds.Endpoint.Name).
WillReturnRows(sqlmock.NewRows([]string{"name"}).
AddRow(s.secondaryTestCreds.Endpoint.Name))
s.Fixtures.SQLMock.ExpectRollback()
_, err := s.StoreSQLMocked.UpdateRepository(context.Background(), s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestGetRepositoryByID() {
repo, err := s.Store.GetRepositoryByID(context.Background(), s.Fixtures.Repos[0].ID)
repo, err := s.Store.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.Repos[0].ID, repo.ID)
}
func (s *RepoTestSuite) TestGetRepositoryByIDInvalidRepoID() {
_, err := s.Store.GetRepositoryByID(context.Background(), "dummy-repo-id")
_, err := s.Store.GetRepositoryByID(s.adminCtx, "dummy-repo-id")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
@ -435,20 +494,20 @@ func (s *RepoTestSuite) TestGetRepositoryByIDDBDecryptingErr() {
WithArgs(s.Fixtures.Repos[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"repo_id"}).AddRow(s.Fixtures.Repos[0].ID))
_, err := s.StoreSQLMocked.GetRepositoryByID(context.Background(), s.Fixtures.Repos[0].ID)
_, err := s.StoreSQLMocked.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching repo: missing secret", err.Error())
s.assertSQLMockExpectations()
}
func (s *RepoTestSuite) TestCreateRepositoryPool() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().Nil(err)
repo, err := s.Store.GetRepositoryByID(context.Background(), s.Fixtures.Repos[0].ID)
repo, err := s.Store.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
if err != nil {
s.FailNow(fmt.Sprintf("cannot get repo by ID: %v", err))
}
@ -463,7 +522,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolMissingTags() {
s.Fixtures.CreatePoolParams.Tags = []string{}
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("no tags specified", err.Error())
@ -474,7 +533,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
_, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -492,7 +551,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBCreateErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("checking pool existence: mocked creating pool error", err.Error())
@ -522,7 +581,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBPoolAlreadyExistErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("pool with the same image and flavor already exists on this provider", err.Error())
@ -550,7 +609,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBFetchTagErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
@ -587,7 +646,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBAddingPoolErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
@ -627,7 +686,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBSaveTagErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
s.assertSQLMockExpectations()
@ -675,7 +734,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBFetchPoolErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
_, err = s.StoreSQLMocked.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
@ -688,14 +747,14 @@ func (s *RepoTestSuite) TestListRepoPools() {
repoPools := []params.Pool{}
for i := 1; i <= 2; i++ {
s.Fixtures.CreatePoolParams.Flavor = fmt.Sprintf("test-flavor-%d", i)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
repoPools = append(repoPools, pool)
}
pools, err := s.Store.ListEntityPools(context.Background(), entity)
pools, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().Nil(err)
garmTesting.EqualDBEntityID(s.T(), repoPools, pools)
@ -706,7 +765,7 @@ func (s *RepoTestSuite) TestListRepoPoolsInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
_, err := s.Store.ListEntityPools(context.Background(), entity)
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
@ -715,12 +774,12 @@ func (s *RepoTestSuite) TestListRepoPoolsInvalidRepoID() {
func (s *RepoTestSuite) TestGetRepositoryPool() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
repoPool, err := s.Store.GetEntityPool(context.Background(), entity, pool.ID)
repoPool, err := s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
s.Require().Equal(repoPool.ID, pool.ID)
@ -731,7 +790,7 @@ func (s *RepoTestSuite) TestGetRepositoryPoolInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
_, err := s.Store.GetEntityPool(context.Background(), entity, "dummy-pool-id")
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
@ -740,15 +799,15 @@ func (s *RepoTestSuite) TestGetRepositoryPoolInvalidRepoID() {
func (s *RepoTestSuite) TestDeleteRepositoryPool() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
err = s.Store.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.Store.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(context.Background(), entity, pool.ID)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
}
@ -757,7 +816,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
err := s.Store.DeleteEntityPool(context.Background(), entity, "dummy-pool-id")
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
@ -767,7 +826,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolDBDeleteErr() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
@ -779,7 +838,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolDBDeleteErr() {
WillReturnError(fmt.Errorf("mocked deleting pool error"))
s.Fixtures.SQLMock.ExpectRollback()
err = s.StoreSQLMocked.DeleteEntityPool(context.Background(), entity, pool.ID)
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
s.assertSQLMockExpectations()
@ -788,21 +847,21 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolDBDeleteErr() {
func (s *RepoTestSuite) TestListRepoInstances() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
pool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
pool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
poolInstances := []params.Instance{}
for i := 1; i <= 3; i++ {
s.Fixtures.CreateInstanceParams.Name = fmt.Sprintf("test-repo-%d", i)
instance, err := s.Store.CreateInstance(context.Background(), pool.ID, s.Fixtures.CreateInstanceParams)
instance, err := s.Store.CreateInstance(s.adminCtx, pool.ID, s.Fixtures.CreateInstanceParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create instance: %s", err))
}
poolInstances = append(poolInstances, instance)
}
instances, err := s.Store.ListEntityInstances(context.Background(), entity)
instances, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().Nil(err)
s.equalInstancesByID(poolInstances, instances)
@ -813,7 +872,7 @@ func (s *RepoTestSuite) TestListRepoInstancesInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
_, err := s.Store.ListEntityInstances(context.Background(), entity)
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
@ -822,12 +881,12 @@ func (s *RepoTestSuite) TestListRepoInstancesInvalidRepoID() {
func (s *RepoTestSuite) TestUpdateRepositoryPool() {
entity, err := s.Fixtures.Repos[0].GetEntity()
s.Require().Nil(err)
repoPool, err := s.Store.CreateEntityPool(context.Background(), entity, s.Fixtures.CreatePoolParams)
repoPool, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
if err != nil {
s.FailNow(fmt.Sprintf("cannot create repo pool: %v", err))
}
pool, err := s.Store.UpdateEntityPool(context.Background(), entity, repoPool.ID, s.Fixtures.UpdatePoolParams)
pool, err := s.Store.UpdateEntityPool(s.adminCtx, entity, repoPool.ID, s.Fixtures.UpdatePoolParams)
s.Require().Nil(err)
s.Require().Equal(*s.Fixtures.UpdatePoolParams.MaxRunners, pool.MaxRunners)
@ -841,7 +900,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
ID: "dummy-repo-id",
EntityType: params.GithubEntityTypeRepository,
}
_, err := s.Store.UpdateEntityPool(context.Background(), entity, "dummy-repo-id", s.Fixtures.UpdatePoolParams)
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-repo-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())

View file

@ -18,6 +18,7 @@ import (
"context"
"fmt"
"log/slog"
"net/url"
"strings"
"github.com/pkg/errors"
@ -26,8 +27,12 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util/appdefaults"
)
// newDBConn returns a new gorm db connection, given the config
@ -190,6 +195,163 @@ func (s *sqlDatabase) cascadeMigration() error {
return nil
}
func (s *sqlDatabase) ensureGithubEndpoint() error {
// Create the default Github endpoint.
createEndpointParams := params.CreateGithubEndpointParams{
Name: "github.com",
Description: "The github.com endpoint",
APIBaseURL: appdefaults.GithubDefaultBaseURL,
BaseURL: appdefaults.DefaultGithubURL,
UploadBaseURL: appdefaults.GithubDefaultUploadBaseURL,
}
if _, err := s.CreateGithubEndpoint(context.Background(), createEndpointParams); err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
return errors.Wrap(err, "creating default github endpoint")
}
}
return nil
}
func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
s.conn.Exec("PRAGMA foreign_keys = OFF")
defer s.conn.Exec("PRAGMA foreign_keys = ON")
adminUser, err := s.GetAdminUser(s.ctx)
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
// Admin user doesn't exist. This is a new deploy. Nothing to migrate.
return nil
}
return errors.Wrap(err, "getting admin user")
}
// Impersonate the admin user. We're migrating from config credentials to
// database credentials. At this point, there is no other user than the admin
// user. GARM is not yet multi-user, so it's safe to assume we only have this
// one user.
adminCtx := context.Background()
adminCtx = auth.PopulateContext(adminCtx, adminUser)
slog.Info("migrating credentials to DB")
slog.Info("creating github endpoints table")
if err := s.conn.AutoMigrate(&GithubEndpoint{}); err != nil {
return errors.Wrap(err, "migrating github endpoints")
}
defer func() {
if err != nil {
slog.With(slog.Any("error", err)).Error("rolling back github github endpoints table")
s.conn.Migrator().DropTable(&GithubEndpoint{})
}
}()
slog.Info("creating github credentials table")
if err := s.conn.AutoMigrate(&GithubCredentials{}); err != nil {
return errors.Wrap(err, "migrating github credentials")
}
defer func() {
if err != nil {
slog.With(slog.Any("error", err)).Error("rolling back github github credentials table")
s.conn.Migrator().DropTable(&GithubCredentials{})
}
}()
// Nothing to migrate.
if len(s.cfg.MigrateCredentials) == 0 {
return nil
}
slog.Info("importing credentials from config")
for _, cred := range s.cfg.MigrateCredentials {
slog.Info("importing credential", "name", cred.Name)
parsed, err := url.Parse(cred.BaseEndpoint())
if err != nil {
return errors.Wrap(err, "parsing base URL")
}
certBundle, err := cred.CACertBundle()
if err != nil {
return errors.Wrap(err, "getting CA cert bundle")
}
hostname := parsed.Hostname()
createParams := params.CreateGithubEndpointParams{
Name: hostname,
Description: fmt.Sprintf("Endpoint for %s", hostname),
APIBaseURL: cred.APIEndpoint(),
BaseURL: cred.BaseEndpoint(),
UploadBaseURL: cred.UploadEndpoint(),
CACertBundle: certBundle,
}
var endpoint params.GithubEndpoint
endpoint, err = s.GetGithubEndpoint(adminCtx, hostname)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "getting github endpoint")
}
endpoint, err = s.CreateGithubEndpoint(adminCtx, createParams)
if err != nil {
return errors.Wrap(err, "creating default github endpoint")
}
}
credParams := params.CreateGithubCredentialsParams{
Name: cred.Name,
Description: cred.Description,
Endpoint: endpoint.Name,
AuthType: params.GithubAuthType(cred.GetAuthType()),
}
switch credParams.AuthType {
case params.GithubAuthTypeApp:
keyBytes, err := cred.App.PrivateKeyBytes()
if err != nil {
return errors.Wrap(err, "getting private key bytes")
}
credParams.App = params.GithubApp{
AppID: cred.App.AppID,
InstallationID: cred.App.InstallationID,
PrivateKeyBytes: keyBytes,
}
if err := credParams.App.Validate(); err != nil {
return errors.Wrap(err, "validating app credentials")
}
case params.GithubAuthTypePAT:
token := cred.PAT.OAuth2Token
if token == "" {
token = cred.OAuth2Token
}
if token == "" {
return errors.New("missing OAuth2 token")
}
credParams.PAT = params.GithubPAT{
OAuth2Token: token,
}
}
creds, err := s.CreateGithubCredentials(adminCtx, credParams)
if err != nil {
return errors.Wrap(err, "creating github credentials")
}
if err := s.conn.Exec("update repositories set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating repositories")
}
if err := s.conn.Exec("update organizations set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating organizations")
}
if err := s.conn.Exec("update enterprises set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating enterprises")
}
}
return nil
}
func (s *sqlDatabase) migrateDB() error {
if s.conn.Migrator().HasIndex(&Organization{}, "idx_organizations_name") {
if err := s.conn.Migrator().DropIndex(&Organization{}, "idx_organizations_name"); err != nil {
@ -234,7 +396,15 @@ func (s *sqlDatabase) migrateDB() error {
}
}
var needsCredentialMigration bool
if !s.conn.Migrator().HasTable(&GithubCredentials{}) || !s.conn.Migrator().HasTable(&GithubEndpoint{}) {
needsCredentialMigration = true
}
s.conn.Exec("PRAGMA foreign_keys = OFF")
if err := s.conn.AutoMigrate(
&User{},
&GithubEndpoint{},
&GithubCredentials{},
&Tag{},
&Pool{},
&Repository{},
@ -244,11 +414,20 @@ func (s *sqlDatabase) migrateDB() error {
&InstanceStatusUpdate{},
&Instance{},
&ControllerInfo{},
&User{},
&WorkflowJob{},
); err != nil {
return errors.Wrap(err, "running auto migrate")
}
s.conn.Exec("PRAGMA foreign_keys = ON")
if err := s.ensureGithubEndpoint(); err != nil {
return errors.Wrap(err, "ensuring github endpoint")
}
if needsCredentialMigration {
if err := s.migrateCredentialsToDB(); err != nil {
return errors.Wrap(err, "migrating credentials")
}
}
return nil
}

View file

@ -67,6 +67,10 @@ func (s *sqlDatabase) CreateUser(_ context.Context, user params.NewUserParams) (
return params.User{}, runnerErrors.NewConflictError("email already exists")
}
if s.HasAdminUser(context.Background()) && user.IsAdmin {
return params.User{}, runnerErrors.NewBadRequestError("admin user already exists")
}
newUser := User{
Username: user.Username,
Password: user.Password,
@ -129,3 +133,16 @@ func (s *sqlDatabase) UpdateUser(_ context.Context, user string, param params.Up
return s.sqlToParamsUser(dbUser), nil
}
// GetAdminUser returns the system admin user. This is only for internal use.
func (s *sqlDatabase) GetAdminUser(_ context.Context) (params.User, error) {
var user User
q := s.conn.Model(&User{}).Where("is_admin = ?", true).First(&user)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return params.User{}, runnerErrors.ErrNotFound
}
return params.User{}, errors.Wrap(q.Error, "fetching admin user")
}
return s.sqlToParamsUser(user), nil
}

View file

@ -105,7 +105,7 @@ func (s *sqlDatabase) sqlAddressToParamsAddress(addr Address) commonParams.Addre
}
}
func (s *sqlDatabase) sqlToCommonOrganization(org Organization) (params.Organization, error) {
func (s *sqlDatabase) sqlToCommonOrganization(org Organization, detailed bool) (params.Organization, error) {
if len(org.WebhookSecret) == 0 {
return params.Organization{}, errors.New("missing secret")
}
@ -114,13 +114,30 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization) (params.Organiza
return params.Organization{}, errors.Wrap(err, "decrypting secret")
}
endpoint, err := s.sqlToCommonGithubEndpoint(org.Endpoint)
if err != nil {
return params.Organization{}, errors.Wrap(err, "converting endpoint")
}
ret := params.Organization{
ID: org.ID.String(),
Name: org.Name,
CredentialsName: org.CredentialsName,
CredentialsName: org.Credentials.Name,
Pools: make([]params.Pool, len(org.Pools)),
WebhookSecret: string(secret),
PoolBalancerType: org.PoolBalancerType,
Endpoint: endpoint,
}
if org.CredentialsID != nil {
ret.CredentialsID = *org.CredentialsID
}
if detailed {
creds, err := s.sqlToCommonGithubCredentials(org.Credentials)
if err != nil {
return params.Organization{}, errors.Wrap(err, "converting credentials")
}
ret.Credentials = creds
}
if ret.PoolBalancerType == "" {
@ -137,7 +154,7 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization) (params.Organiza
return ret, nil
}
func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise) (params.Enterprise, error) {
func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise, detailed bool) (params.Enterprise, error) {
if len(enterprise.WebhookSecret) == 0 {
return params.Enterprise{}, errors.New("missing secret")
}
@ -146,13 +163,30 @@ func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise) (params.Enter
return params.Enterprise{}, errors.Wrap(err, "decrypting secret")
}
endpoint, err := s.sqlToCommonGithubEndpoint(enterprise.Endpoint)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "converting endpoint")
}
ret := params.Enterprise{
ID: enterprise.ID.String(),
Name: enterprise.Name,
CredentialsName: enterprise.CredentialsName,
CredentialsName: enterprise.Credentials.Name,
Pools: make([]params.Pool, len(enterprise.Pools)),
WebhookSecret: string(secret),
PoolBalancerType: enterprise.PoolBalancerType,
Endpoint: endpoint,
}
if enterprise.CredentialsID != nil {
ret.CredentialsID = *enterprise.CredentialsID
}
if detailed {
creds, err := s.sqlToCommonGithubCredentials(enterprise.Credentials)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "converting credentials")
}
ret.Credentials = creds
}
if ret.PoolBalancerType == "" {
@ -230,7 +264,7 @@ func (s *sqlDatabase) sqlToCommonTags(tag Tag) params.Tag {
}
}
func (s *sqlDatabase) sqlToCommonRepository(repo Repository) (params.Repository, error) {
func (s *sqlDatabase) sqlToCommonRepository(repo Repository, detailed bool) (params.Repository, error) {
if len(repo.WebhookSecret) == 0 {
return params.Repository{}, errors.New("missing secret")
}
@ -238,15 +272,31 @@ func (s *sqlDatabase) sqlToCommonRepository(repo Repository) (params.Repository,
if err != nil {
return params.Repository{}, errors.Wrap(err, "decrypting secret")
}
endpoint, err := s.sqlToCommonGithubEndpoint(repo.Endpoint)
if err != nil {
return params.Repository{}, errors.Wrap(err, "converting endpoint")
}
ret := params.Repository{
ID: repo.ID.String(),
Name: repo.Name,
Owner: repo.Owner,
CredentialsName: repo.CredentialsName,
CredentialsName: repo.Credentials.Name,
Pools: make([]params.Pool, len(repo.Pools)),
WebhookSecret: string(secret),
PoolBalancerType: repo.PoolBalancerType,
Endpoint: endpoint,
}
if repo.CredentialsID != nil {
ret.CredentialsID = *repo.CredentialsID
}
if detailed {
creds, err := s.sqlToCommonGithubCredentials(repo.Credentials)
if err != nil {
return params.Repository{}, errors.Wrap(err, "converting credentials")
}
ret.Credentials = creds
}
if ret.PoolBalancerType == "" {

View file

@ -1,8 +1,32 @@
# Configuring github credentials
# Configuring github endpoints and credentials
The ```github``` config section holds credentials and API endpoint information for accessing the GitHub APIs. Credentials are tied to the instance of GitHub you're using. Whether you're using [github.com](https://github.com) or your own deployment of GitHub Enterprise server, this section is how ```garm``` knows where it should create the runners.
Starting with version `v0.1.5`, GARM saves github endpoints and github credentials in the database.
Tying the API endpoint info to the credentials allows us to use the same ```garm``` installation with both [github.com](https://github.com) and private deployments. All you have to do is to add the needed endpoint info (see bellow).
<!-- TOC -->
- [Configuring github endpoints and credentials](#configuring-github-endpoints-and-credentials)
- [Listing GitHub endpoints](#listing-github-endpoints)
- [Adding GitHub credentials](#adding-github-credentials)
- [Listing GitHub credentials](#listing-github-credentials)
- [Deleting GitHub credentials](#deleting-github-credentials)
<!-- /TOC -->
## Listing GitHub endpoints
To list the available GitHub endpoints, you can use the following command:
```bash
ubuntu@garm:~/garm$ garm-cli github endpoint list
+------------+--------------------------+-------------------------------+
| NAME | BASE URL | DESCRIPTION |
+------------+--------------------------+-------------------------------+
| github.com | https://github.com | The github.com endpoint |
+------------+--------------------------+-------------------------------+
| example | https://ghes.example.com | Just an example ghes endpoint |
+------------+--------------------------+-------------------------------+
```
## Adding GitHub credentials
GARM has the option to use both [Personal Access Tokens (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) or a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app).
@ -28,55 +52,44 @@ If you plan to use github apps, you'll need to select the following permissions:
**Note** :warning:: Github Apps are not available at the enterprise level.
The resulting credentials (app or PAT) must be configured in the ```[[github]]``` section of the config. Sample as follows:
To add a new GitHub credential, you can use the following command:
```toml
# 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"
```bash
garm-cli github credentials add \
--name gabriel \
--description "GitHub PAT for user gabriel" \
--auth-type pat \
--pat-oauth-token gh_theRestOfThePAT \
--endpoint github.com
```
The double parenthesis means that this is an array. You can specify the ```[[github]]``` section multiple times, with different tokens from different users, or with different access levels. You will then be able to list the available credentials using the API, and reference these credentials when adding repositories or organizations.
To add a new GitHub App credential, you can use the following command:
The API will only ever return the name and description to the API consumer.
```bash
garm-cli github credentials add \
--name gabriel_app \
--description "Github App with access to repos" \
--endpoint github.com \
--auth-type app \
--app-id 1 \
--app-installation-id 99 \
--private-key-path $HOME/yourAppName.2024-03-01.private-key.pem
```
All sensitive data is encrypted at rest. The API will not return any sensitive info.
## Listing GitHub credentials
To list the available GitHub credentials, you can use the following command:
```bash
garm-cli github credentials list
```
## Deleting GitHub credentials
To delete a GitHub credential, you can use the following command:
```bash
garm-cli github credentials delete <CREDENTIAL_ID>
```

View file

@ -25,6 +25,8 @@ For a `classic` PAT, GARM needs the following permissions to function properly (
* ```repo``` - for access to a private repository
* ```admin:org``` - if you plan on using this with an organization to which you have access
* ```manage_runners:enterprise``` - if you plan to use garm at the enterprise level
* ```admin:repo_hook``` - if you want to allow GARM to install webhooks on repositories
* ```admin:org_hook``` - if you want to allow GARM to install webhooks on organizations
This doc will be updated at a future date with the exact permissions needed in case you want to use a fine grained PAT.
@ -48,6 +50,20 @@ Open `/etc/garm/config.toml` in your favorite editor and paste the following:
[default]
callback_url = "https://garm.example.com/api/v1/callbacks"
metadata_url = "https://garm.example.com/api/v1/metadata"
# This is important for webhook management.
webhook_url = "https://garm.example.com/webhooks"
enable_webhook_management = true
[logging]
# If using nginx, you'll need to configure connection upgrade headers
# for the /api/v1/ws location. See the sample config in the testdata
# folder.
enable_log_streamer = true
# Set this to "json" if you want to consume these logs in something like
# Loki or ELK.
log_format = "text"
log_level = "info"
log_source = false
[metrics]
enable = true
@ -71,11 +87,12 @@ time_to_live = "8760h"
db_file = "/etc/garm/garm.db"
```
This is a minimal config, with no providers or credentials defined. In this example we have the [default](./config_default.md), [metrics](./config_metrics.md), [jwt_auth](./config_jwt_auth.md), [apiserver](./config_api_server.md) and [database](./database.md) sections. Each are documented separately. Feel free to read through the available docs if, for example you need to enable TLS without using an nginx reverse proxy or if you want to enable the debug server, the log streamer or a log file.
This is a minimal config, with no providers defined. In this example we have the [default](./config_default.md), [logging](./config_logging.md), [metrics](./config_metrics.md), [jwt_auth](./config_jwt_auth.md), [apiserver](./config_api_server.md) and [database](./database.md) sections. Each are documented separately. Feel free to read through the available docs if, for example you need to enable TLS without using an nginx reverse proxy or if you want to enable the debug server, the log streamer or a log file.
In this sample config we:
* define the callback and the metadata URLs
* define the callback, webhooks and the metadata URLs
* set up logging prefrences
* enable metrics with authentication
* set a JWT secret which is used to sign JWT tokens
* set a time to live for the JWT tokens
@ -88,9 +105,10 @@ We need to tell garm by which addresses it can be reached. There are many ways b
The information in these two options is used by the instances we spin up to phone home their status and to fetch the needed metadata to finish setting themselves up. For now, the metadata URL is only used to fetch the runner registration token.
The webhook URL is used by GARM itself to know how to set up the webhooks in GitHub. Each controller will have a unique ID and GARM will use the value in `webhook_url` as a base. It will appen
We won't go too much into detail about each of the options here. Have a look at the different config sections and their respective docs for more information.
At this point, we have a valid config file, but we still need to add `provider` and `credentials` sections.
At this point, we have a valid config file, but we still need to add the `provider` section.
## The provider section
@ -110,9 +128,7 @@ All currently available providers are `external`.
The easiest provider to set up is probably the LXD or Incus provider. Incus is a fork of LXD so the functionality is identical (for now). For the purpose of this document, we'll continue with LXD. You don't need an account on an external cloud. You can just use your machine.
You will need to have LXD installed and configured. There is an excellent [getting started guide](https://documentation.ubuntu.com/lxd/en/latest/getting_started/) for LXD. Follow the instructions there to install and configure LXD, then come back here.
Once you have LXD installed and configured, you can add the provider section to your config file. If you're connecting to the `local` LXD installation, the [config snippet for the LXD provider](https://github.com/cloudbase/garm-provider-lxd/blob/main/testdata/garm-provider-lxd.toml) will work out of the box. We'll be connecting using the unix socket so no further configuration will be needed.
You will need to have LXD installed and configured. There is an excellent [getting started guide](https://documentation.ubuntu.com/lxd/en/latest/getting_started/) for LXD. Follow the unix socket so no further configuration will be needed.
Go ahead and create a new config somwhere where GARM can access it and paste that entire snippet. For the purposes of this doc, we'll assume you created a new file called `/etc/garm/garm-provider-lxd.toml`. Now we need to define the external provider config in `/etc/garm/config.toml`:
@ -126,32 +142,6 @@ Go ahead and create a new config somwhere where GARM can access it and paste tha
config_file = "/etc/garm/garm-provider-lxd.toml"
```
## The credentials section
The credentials section is where we define out GitHub credentials. GARM is capable of using either GitHub proper or [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@3.6/get-started/onboarding/getting-started-with-github-enterprise-server). The credentials section allows you to override the default GitHub API endpoint and point it to your own deployment of GHES.
The credentials section is [documented in a separate doc](./github_credentials.md), but we will include a small snippet here for clarity.
```toml
# 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 for user gabriel"
# 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"
```
The `oauth2_token` option will hold the PAT we created earlier. You can add multiple credentials to the config file. Each will be referenced by name when we define the repo/org/enterprise.
Alright, we're almost there. We have a config file with a provider and a credentials section. We now have to start the service and create a webhook in GitHub pointing at our `webhook` endpoint.
## Starting the service
You can start GARM using docker or directly on your system. I'll show you both ways.
@ -194,7 +184,7 @@ useradd --shell /usr/bin/false \
--no-create-home garm
```
Adding the `garm` user to the LXD group will allow it to connect to the LXD unix socket. We'll need that considering the config we crafted above.
Adding the `garm` user to the LXD group will allow it to connect to the LXD unix socket. We'll need that considering the config we crafted above. The recommendation is to use TCP connections to connect to a remote LXD installation. The local setup of an LXD provider is just for demonstration purposes/testing.
Next, download the latest release from the [releases page](https://github.com/cloudbase/garm/releases).
@ -217,7 +207,9 @@ sudo mkdir -p /opt/garm/providers.d
Download the LXD provider binary:
```bash
wget -q -O - https://github.com/cloudbase/garm-provider-lxd/releases/download/v0.1.0/garm-linux-amd64.tgz | sudo tar xzf - -C /opt/garm/providers.d/
git clone https://github.com/cloudbase/garm-provider-lxd
cd garm-provider-lxd
go build -o /opt/garm/providers.d/garm-provider-lxd
```
Change the permissions on the config dir:
@ -230,7 +222,7 @@ Copy the sample `systemd` service file:
```bash
wget -O /etc/systemd/system/garm.service \
https://raw.githubusercontent.com/cloudbase/garm/v0.1.3/contrib/garm.service
https://raw.githubusercontent.com/cloudbase/garm/v0.1.4/contrib/garm.service
```
Reload the `systemd` daemon and start the service:
@ -263,15 +255,11 @@ Excellent! We have a working GARM installation. Now we need to set up the webhoo
## Setting up the webhook
Before we create a pool, we need to set up the webhook in GitHub. This is a fairly simple process.
There are two options when it comes to setting up the webhook in GitHub. You can manually set up the webhook in the GitHub UI, and then use the resulting secret when creating the entity (repo, org, enterprise), or you can let GARM do it automatically if the app or PAT you're using has the [required privileges](./github_credentials.md).
Head over to the [webhooks doc](./webhooks.md) and follow the instructions there. Come back here when you're done.
If you want to manually set up the webhooks, have a look at the [webhooks doc](./webhooks.md) for more information.
After you've finished setting up the webhook, there are just a few more things to do:
* Initialize GARM
* Add a repo/org/enterprise
* Create a pool
In this guide, I'll show you how to do it automatically when adding a new repo, assuming you have the required privileges. Note, you'll still have to manually set up webhooks if you want to use GARM at the enterprise level. Automatic webhook management is only available for repos and orgs.
## Initializing GARM
@ -280,7 +268,7 @@ Before we can start using GARM, we need initialize it. This will create the `adm
To initialize GARM, we'll use the `garm-cli` tool. You can download the latest release from the [releases page](https://github.com/cloudbase/garm/releases):
```bash
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.3/garm-cli-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.4/garm-cli-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
```
Now we can initialize GARM:
@ -314,7 +302,9 @@ ubuntu@garm:~$ garm-cli profile list
Every time you init a new GARM instance, a new profile will be created in your local `garm-cli` config. You can also log into an already initialized instance using:
```bash
garm-cli profile add --name="another_garm" --url https://garm2.example.com
garm-cli profile add \
--name="another_garm" \
--url https://garm2.example.com
```
Then you can switch between profiles using:
@ -323,6 +313,69 @@ Then you can switch between profiles using:
garm-cli profile switch another_garm
```
## Creating a gitHub endpoint (Optional)
This section is only of interest if you're using a GitHub Enterprise Server (GHES) deployment. If you're using [github.com](https://github.com), you can skip this section.
Let's list existing endpoints:
```bash
gabriel@rossak:~$ garm-cli github endpoint list
+------------+--------------------+-------------------------+
| NAME | BASE URL | DESCRIPTION |
+------------+--------------------+-------------------------+
| github.com | https://github.com | The github.com endpoint |
+------------+--------------------+-------------------------+
```
By default, GARM creates a default `github.com` endpoint. This endpoint cannot be updated or deleted. If you want to add a new endpoint, you can do so using the `github endpoint create` command:
```bash
garm-cli github endpoint create \
--name example \
--description "Just an example ghes endpoint" \
--base-url https://ghes.example.com \
--upload-url https://upload.ghes.example.com \
--api-base-url https://api.ghes.example.com \
--ca-cert-path $HOME/ca-cert.pem
```
In this exampe, we add a new github endpoint called `example`. The `ca-cert-path` is optional and is used to verify the server's certificate. If you don't provide a path, GARM will use the system's default CA certificates.
## Adding credentials
Before we can add a new entity, we need github credentials to interact with that entity (manipulate runners, create webhooks, etc). Credentials are tied to a specific github endpoint. In this section we'll be adding credentials that are valid for either [github.com](https://github.com) or your own GHES server (if you added one in the previous section).
When creating a new entity (repo, org, enterprise) using the credentials you define here, GARM will automatically associate that entity with the gitHub endpoint the credentials use.
If you want to swap the credentials for an entity, the new credentials will need to be associated with the same endpoint as the old credentials.
Let's add some credentials:
```bash
garm-cli github credentials add \
--name gabriel \
--description "GitHub PAT for user gabriel" \
--auth-type pat \
--pat-oauth-token gh_theRestOfThePAT \
--endpoint github.com
```
You can also add a GitHub App as credentials. The process is similar, but you'll need to provide the `app_id`, `private_key_path` and `installation_id`:
```bash
garm-cli github credentials add \
--name gabriel_app \
--description "Github App with access to repos" \
--endpoint github.com \
--auth-type app \
--app-id 1 \
--app-installation-id 99 \
--private-key-path $HOME/yourAppName.2024-03-01.private-key.pem
```
All sensitive info is encrypted at rest. Also, the API will not return sensitive data.
## Define a repo
We now have a working GARM installation, with github credentials and a provider added. It's time to add a repo.
@ -330,45 +383,51 @@ We now have a working GARM installation, with github credentials and a provider
Before we add a repo, let's list credentials. We'll need their names when we'll add a new repo.
```bash
gabriel@rossak:~$ garm-cli credentials list
+---------+-------------------------------+--------------------+-------------------------+-----------------------------+
| NAME | DESCRIPTION | BASE URL | API URL | UPLOAD URL |
+---------+-------------------------------+--------------------+-------------------------+-----------------------------+
| gabriel | github token for user gabriel | https://github.com | https://api.github.com/ | https://uploads.github.com/ |
+---------+-------------------------------+--------------------+-------------------------+-----------------------------+
ubuntu@garm:~$ garm-cli github credentials list
+----+-------------+------------------------------------+--------------------+-------------------------+-----------------------------+------+
| ID | NAME | DESCRIPTION | BASE URL | API URL | UPLOAD URL | TYPE |
+----+-------------+------------------------------------+--------------------+-------------------------+-----------------------------+------+
| 1 | gabriel | GitHub PAT for user gabriel | https://github.com | https://api.github.com/ | https://uploads.github.com/ | pat |
+----+-------------+------------------------------------+--------------------+-------------------------+-----------------------------+------+
| 2 | gabriel_app | Github App with access to repos | https://github.com | https://api.github.com/ | https://uploads.github.com/ | app |
+----+-------------+------------------------------------+--------------------+-------------------------+-----------------------------+------+
```
Even though you didn't explicitly set the URLs, GARM will default to the GitHub ones. You can override them if you want to use a GHES deployment.
Now we can add a repo:
```bash
garm-cli repo add \
--credentials gabriel \
--owner gsamfira \
--name scripts \
--webhook-secret $SECRET
--credentials gabriel \
--random-webhook-secret \
--install-webhook \
--pool-balancer-type roundrobin
```
In this case, `$SECRET` holds the webhook secret you set previously when you defined the webhook in GitHub. This secret is mandatory as GARM will always validate the webhook payloads it receives.
This will add a new repo called `scripts` under the `gsamfira` org. We also tell GARM to generate a random secret and install a webhook using that random secret. If you want to use a specific secret, you can use the `--webhook-secret` option, but in that case, you'll have to manually set up the webhook in GitHub.
The `--pool-balancer-type` option is used to set the pool balancer type. That dictates how GARM will choose in which pool it should create a new runner when consuming recorded queued jobs. If `roundrobin` (default) is used, GARM will cycle through all pools and create a runner in the first pool that has available resources. If `pack` is used, GARM will try to fill up a pool before moving to the next one. The order of the pools is determined by the pool priority. We'll see more about pools in the next section.
You should see something like this:
```bash
gabriel@rossak:~$ garm-cli repo add \
> --credentials gabriel \
> --owner gsamfira \
> --name scripts \
> --webhook-secret $SECRET
--name scripts \
--credentials gabriel_org \
--install-webhook \
--random-webhook-secret \
--owner gsamfira \
--pool-balancer-type roundrobin
+----------------------+--------------------------------------+
| FIELD | VALUE |
+----------------------+--------------------------------------+
| ID | f4900c7c-2ec0-41bd-9eab-d70fe9bd850d |
| ID | 0c91d9fd-2417-45d4-883c-05daeeaa8272 |
| Owner | gsamfira |
| Name | scripts |
| Credentials | gabriel |
| Pool manager running | false |
| Failure reason | |
| Pool balancer type | roundrobin |
| Credentials | gabriel_app |
| Pool manager running | true |
+----------------------+--------------------------------------+
```
@ -376,11 +435,11 @@ We can now list the repos:
```bash
gabriel@rock:~$ garm-cli repo ls
+--------------------------------------+----------+---------+------------------+------------------+
| ID | OWNER | NAME | CREDENTIALS NAME | POOL MGR RUNNING |
+--------------------------------------+----------+---------+------------------+------------------+
| f4900c7c-2ec0-41bd-9eab-d70fe9bd850d | gsamfira | scripts | gabriel | true |
+--------------------------------------+----------+---------+------------------+------------------+
+--------------------------------------+----------+--------------+------------------+--------------------+------------------+
| ID | OWNER | NAME | CREDENTIALS NAME | POOL BALANCER TYPE | POOL MGR RUNNING |
+--------------------------------------+----------+--------------+------------------+--------------------+------------------+
| 0c91d9fd-2417-45d4-883c-05daeeaa8272 | gsamfira | scripts | gabriel | pack | true |
+--------------------------------------+----------+--------------+------------------+--------------------+------------------+
```
Excellent! Make a note of the ID. We'll need it later when we create a pool.
@ -393,18 +452,18 @@ To create a pool we'll need the repo ID from the previous step (which we have) a
```bash
gabriel@rossak:~$ garm-cli provider list
+-----------+------------------------+------+
| NAME | DESCRIPTION | TYPE |
+-----------+------------------------+------+
| lxd_local | Local LXD installation | lxd |
+-----------+------------------------+------+
+-----------+------------------------+-----------+
| NAME | DESCRIPTION | TYPE |
+-----------+------------------------+-----------+
| lxd_local | Local LXD installation | external |
+-----------+------------------------+-----------+
```
Now we can create a pool:
```bash
garm-cli pool add \
--repo f4900c7c-2ec0-41bd-9eab-d70fe9bd850d \
--repo 0c91d9fd-2417-45d4-883c-05daeeaa8272 \
--enabled true \
--provider-name lxd_local \
--flavor default \
@ -420,7 +479,7 @@ You should see something like this:
```bash
gabriel@rossak:~$ garm-cli pool add \
> --repo f4900c7c-2ec0-41bd-9eab-d70fe9bd850d \
> --repo 0c91d9fd-2417-45d4-883c-05daeeaa8272 \
> --enabled true \
> --provider-name lxd_local \
> --flavor default \
@ -435,6 +494,7 @@ gabriel@rossak:~$ garm-cli pool add \
+--------------------------+--------------------------------------------+
| ID | 344e4a72-2035-4a18-a3d5-87bd3874b56c |
| Provider Name | lxd_local |
| Priority | 0 |
| Image | ubuntu:22.04 |
| Flavor | default |
| OS Type | linux |
@ -456,11 +516,11 @@ If we list the pool we should see it:
```bash
gabriel@rock:~$ garm-cli pool ls -a
+--------------------------------------+--------------+---------+----------------------------------------+------------------+-------+---------+---------------+
| ID | IMAGE | FLAVOR | TAGS | BELONGS TO | LEVEL | ENABLED | RUNNER PREFIX |
+--------------------------------------+--------------+---------+----------------------------------------+------------------+-------+---------+---------------+
| 344e4a72-2035-4a18-a3d5-87bd3874b56c | ubuntu:22.04 | default | self-hosted amd64 Linux ubuntu generic | gsamfira/scripts | repo | true | garm |
+--------------------------------------+--------------+---------+----------------------------------------+------------------+-------+---------+---------------+
+--------------------------------------+---------------------------+--------------+-----------------------------------------+------------------+-------+---------+---------------+----------+
| ID | IMAGE | FLAVOR | TAGS | BELONGS TO | LEVEL | ENABLED | RUNNER PREFIX | PRIORITY |
+--------------------------------------+---------------------------+--------------+-----------------------------------------+------------------+-------+---------+---------------+----------+
| 344e4a72-2035-4a18-a3d5-87bd3874b56c | ubuntu:22.04 | default | self-hosted amd64 Linux ubuntu generic | gsamfira/scripts | repo | true | garm | 0 |
+--------------------------------------+---------------------------+--------------+-----------------------------------------+------------------+-------+---------+---------------+----------+
```
This pool is enabled, but the `min-idle-runners` option is set to 0. This means that it will not create any lingering runners. It will only create runners when a job is started. If your provider is slow to boot up new instances, you may want to set this to a value higher than 0.
@ -486,6 +546,7 @@ gabriel@rossak:~$ garm-cli pool update 344e4a72-2035-4a18-a3d5-87bd3874b56c --mi
+--------------------------+--------------------------------------------+
| ID | 344e4a72-2035-4a18-a3d5-87bd3874b56c |
| Provider Name | lxd_local |
| Priority | 0 |
| Image | ubuntu:22.04 |
| Flavor | default |
| OS Type | linux |

View file

@ -18,19 +18,115 @@
package testing
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util/appdefaults"
)
//nolint:golangci-lint,gosec
var encryptionPassphrase = "bocyasicgatEtenOubwonIbsudNutDom"
func ImpersonateAdminContext(ctx context.Context, db common.Store, s *testing.T) context.Context {
adminUser, err := db.GetAdminUser(ctx)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.Fatalf("failed to get admin user: %v", err)
}
newUserParams := params.NewUserParams{
Email: "admin@localhost",
Username: "admin",
Password: "superSecretAdminPassword@123",
IsAdmin: true,
Enabled: true,
}
adminUser, err = db.CreateUser(ctx, newUserParams)
if err != nil {
s.Fatalf("failed to create admin user: %v", err)
}
}
ctx = auth.PopulateContext(ctx, adminUser)
return ctx
}
func CreateGARMTestUser(ctx context.Context, username string, db common.Store, s *testing.T) params.User {
newUserParams := params.NewUserParams{
Email: fmt.Sprintf("%s@localhost", username),
Username: username,
Password: "superSecretPassword@123",
IsAdmin: false,
Enabled: true,
}
user, err := db.CreateUser(ctx, newUserParams)
if err != nil {
if errors.Is(err, runnerErrors.ErrDuplicateEntity) {
user, err = db.GetUser(ctx, newUserParams.Username)
if err != nil {
s.Fatalf("failed to get user by email: %v", err)
}
return user
}
s.Fatalf("failed to create user: %v", err)
}
return user
}
func CreateDefaultGithubEndpoint(ctx context.Context, db common.Store, s *testing.T) params.GithubEndpoint {
endpointParams := params.CreateGithubEndpointParams{
Name: "github.com",
Description: "github endpoint",
APIBaseURL: appdefaults.GithubDefaultBaseURL,
UploadBaseURL: appdefaults.GithubDefaultUploadBaseURL,
BaseURL: appdefaults.DefaultGithubURL,
}
ep, err := db.GetGithubEndpoint(ctx, endpointParams.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.Fatalf("failed to get database object (github.com): %v", err)
}
ep, err = db.CreateGithubEndpoint(ctx, endpointParams)
if err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
s.Fatalf("failed to create database object (github.com): %v", err)
}
}
}
return ep
}
func CreateTestGithubCredentials(ctx context.Context, credsName string, db common.Store, s *testing.T, endpoint params.GithubEndpoint) params.GithubCredentials {
newCredsParams := params.CreateGithubCredentialsParams{
Name: credsName,
Description: "Test creds",
AuthType: params.GithubAuthTypePAT,
Endpoint: endpoint.Name,
PAT: params.GithubPAT{
OAuth2Token: "test-token",
},
}
newCreds, err := db.CreateGithubCredentials(ctx, newCredsParams)
if err != nil {
s.Fatalf("failed to create database object (new-creds): %v", err)
}
return newCreds
}
func GetTestSqliteDBConfig(t *testing.T) config.Database {
dir, err := os.MkdirTemp("", "garm-config-test")
if err != nil {

View file

@ -16,6 +16,8 @@ package params
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
@ -23,8 +25,10 @@ import (
"net/http"
"time"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v57/github"
"github.com/google/uuid"
"golang.org/x/oauth2"
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/util/appdefaults"
@ -393,13 +397,19 @@ type Internal struct {
}
type Repository struct {
ID string `json:"id"`
Owner string `json:"owner"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
ID string `json:"id"`
Owner string `json:"owner"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
// CredentialName is the name of the credentials associated with the enterprise.
// This field is now deprecated. Use CredentialsID instead. This field will be
// removed in v0.2.0.
CredentialsName string `json:"credentials_name,omitempty"`
CredentialsID uint `json:"credentials_id"`
Credentials GithubCredentials `json:"credentials"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
PoolBalancerType PoolBalancerType `json:"pool_balancing_type"`
Endpoint GithubEndpoint `json:"endpoint"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -431,16 +441,26 @@ func (r Repository) GetBalancerType() PoolBalancerType {
return r.PoolBalancerType
}
func (r Repository) String() string {
return fmt.Sprintf("%s/%s", r.Owner, r.Name)
}
// used by swagger client generated code
type Repositories []Repository
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
// CredentialName is the name of the credentials associated with the enterprise.
// This field is now deprecated. Use CredentialsID instead. This field will be
// removed in v0.2.0.
CredentialsName string `json:"credentials_name,omitempty"`
Credentials GithubCredentials `json:"credentials"`
CredentialsID uint `json:"credentials_id"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
PoolBalancerType PoolBalancerType `json:"pool_balancing_type"`
Endpoint GithubEndpoint `json:"endpoint"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -476,12 +496,18 @@ func (o Organization) GetBalancerType() PoolBalancerType {
type Organizations []Organization
type Enterprise struct {
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
// CredentialName is the name of the credentials associated with the enterprise.
// This field is now deprecated. Use CredentialsID instead. This field will be
// removed in v0.2.0.
CredentialsName string `json:"credentials_name,omitempty"`
Credentials GithubCredentials `json:"credentials"`
CredentialsID uint `json:"credentials_id"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
PoolBalancerType PoolBalancerType `json:"pool_balancing_type"`
Endpoint GithubEndpoint `json:"endpoint"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -545,6 +571,7 @@ type ControllerInfo struct {
}
type GithubCredentials struct {
ID uint `json:"id"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
APIBaseURL string `json:"api_base_url"`
@ -552,7 +579,68 @@ type GithubCredentials struct {
BaseURL string `json:"base_url"`
CABundle []byte `json:"ca_bundle,omitempty"`
AuthType GithubAuthType `toml:"auth_type" json:"auth-type"`
HTTPClient *http.Client `json:"-"`
Repositories []Repository `json:"repositories,omitempty"`
Organizations []Organization `json:"organizations,omitempty"`
Enterprises []Enterprise `json:"enterprises,omitempty"`
Endpoint GithubEndpoint `json:"endpoint"`
// Do not serialize sensitive info.
CredentialsPayload []byte `json:"-"`
}
func (g GithubCredentials) GetHTTPClient(ctx context.Context) (*http.Client, error) {
var roots *x509.CertPool
if g.CABundle != nil {
roots = x509.NewCertPool()
ok := roots.AppendCertsFromPEM(g.CABundle)
if !ok {
return nil, fmt.Errorf("failed to parse CA cert")
}
}
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: roots,
MinVersion: tls.VersionTLS12,
},
}
var tc *http.Client
switch g.AuthType {
case GithubAuthTypeApp:
var app GithubApp
if err := json.Unmarshal(g.CredentialsPayload, &app); err != nil {
return nil, fmt.Errorf("failed to unmarshal github app credentials: %w", err)
}
if app.AppID == 0 || app.InstallationID == 0 || len(app.PrivateKeyBytes) == 0 {
return nil, fmt.Errorf("github app credentials are missing required fields")
}
itr, err := ghinstallation.New(httpTransport, app.AppID, app.InstallationID, app.PrivateKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to create github app installation transport: %w", err)
}
tc = &http.Client{Transport: itr}
default:
var pat GithubPAT
if err := json.Unmarshal(g.CredentialsPayload, &pat); err != nil {
return nil, fmt.Errorf("failed to unmarshal github app credentials: %w", err)
}
httpClient := &http.Client{Transport: httpTransport}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
if pat.OAuth2Token == "" {
return nil, fmt.Errorf("github credentials are missing the OAuth2 token")
}
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: pat.OAuth2Token},
)
tc = oauth2.NewClient(ctx, ts)
}
return tc, nil
}
func (g GithubCredentials) RootCertificateBundle() (CertificateBundle, error) {
@ -700,10 +788,11 @@ type UpdateSystemInfoParams struct {
}
type GithubEntity struct {
Owner string `json:"owner"`
Name string `json:"name"`
ID string `json:"id"`
EntityType GithubEntityType `json:"entity_type"`
Owner string `json:"owner"`
Name string `json:"name"`
ID string `json:"id"`
EntityType GithubEntityType `json:"entity_type"`
Credentials GithubCredentials `json:"credentials"`
WebhookSecret string `json:"-"`
}
@ -729,3 +818,17 @@ func (g GithubEntity) String() string {
}
return ""
}
// used by swagger client generated code
type GithubEndpoints []GithubEndpoint
type GithubEndpoint struct {
Name string `json:"name"`
Description string `json:"description"`
APIBaseURL string `json:"api_base_url"`
UploadBaseURL string `json:"upload_base_url"`
BaseURL string `json:"base_url"`
CACertBundle []byte `json:"ca_cert_bundle,omitempty"`
Credentials []GithubCredentials `json:"credentials,omitempty"`
}

View file

@ -15,14 +15,23 @@
package params
import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"net/url"
"github.com/cloudbase/garm-provider-common/errors"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
)
const DefaultRunnerPrefix = "garm"
const (
DefaultRunnerPrefix string = "garm"
httpsScheme string = "https"
httpScheme string = "http"
)
type InstanceRequest struct {
Name string `json:"name"`
@ -40,24 +49,24 @@ type CreateRepoParams struct {
func (c *CreateRepoParams) Validate() error {
if c.Owner == "" {
return errors.NewBadRequestError("missing owner")
return runnerErrors.NewBadRequestError("missing owner")
}
if c.Name == "" {
return errors.NewBadRequestError("missing repo name")
return runnerErrors.NewBadRequestError("missing repo name")
}
if c.CredentialsName == "" {
return errors.NewBadRequestError("missing credentials name")
return runnerErrors.NewBadRequestError("missing credentials name")
}
if c.WebhookSecret == "" {
return errors.NewMissingSecretError("missing secret")
return runnerErrors.NewMissingSecretError("missing secret")
}
switch c.PoolBalancerType {
case PoolBalancerTypeRoundRobin, PoolBalancerTypePack, PoolBalancerTypeNone:
default:
return errors.NewBadRequestError("invalid pool balancer type")
return runnerErrors.NewBadRequestError("invalid pool balancer type")
}
return nil
@ -72,20 +81,20 @@ type CreateOrgParams struct {
func (c *CreateOrgParams) Validate() error {
if c.Name == "" {
return errors.NewBadRequestError("missing org name")
return runnerErrors.NewBadRequestError("missing org name")
}
if c.CredentialsName == "" {
return errors.NewBadRequestError("missing credentials name")
return runnerErrors.NewBadRequestError("missing credentials name")
}
if c.WebhookSecret == "" {
return errors.NewMissingSecretError("missing secret")
return runnerErrors.NewMissingSecretError("missing secret")
}
switch c.PoolBalancerType {
case PoolBalancerTypeRoundRobin, PoolBalancerTypePack, PoolBalancerTypeNone:
default:
return errors.NewBadRequestError("invalid pool balancer type")
return runnerErrors.NewBadRequestError("invalid pool balancer type")
}
return nil
}
@ -99,19 +108,19 @@ type CreateEnterpriseParams struct {
func (c *CreateEnterpriseParams) Validate() error {
if c.Name == "" {
return errors.NewBadRequestError("missing enterprise name")
return runnerErrors.NewBadRequestError("missing enterprise name")
}
if c.CredentialsName == "" {
return errors.NewBadRequestError("missing credentials name")
return runnerErrors.NewBadRequestError("missing credentials name")
}
if c.WebhookSecret == "" {
return errors.NewMissingSecretError("missing secret")
return runnerErrors.NewMissingSecretError("missing secret")
}
switch c.PoolBalancerType {
case PoolBalancerTypeRoundRobin, PoolBalancerTypePack, PoolBalancerTypeNone:
default:
return errors.NewBadRequestError("invalid pool balancer type")
return runnerErrors.NewBadRequestError("invalid pool balancer type")
}
return nil
}
@ -249,7 +258,7 @@ type PasswordLoginParams struct {
// Validate checks if the username and password are set
func (p PasswordLoginParams) Validate() error {
if p.Username == "" || p.Password == "" {
return errors.ErrUnauthorized
return runnerErrors.ErrUnauthorized
}
return nil
}
@ -265,3 +274,230 @@ type InstanceUpdateMessage struct {
Message string `json:"message"`
AgentID *int64 `json:"agent_id,omitempty"`
}
type CreateGithubEndpointParams struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
APIBaseURL string `json:"api_base_url,omitempty"`
UploadBaseURL string `json:"upload_base_url,omitempty"`
BaseURL string `json:"base_url,omitempty"`
CACertBundle []byte `json:"ca_cert_bundle,omitempty"`
}
func (c CreateGithubEndpointParams) Validate() error {
if c.APIBaseURL == "" {
return runnerErrors.NewBadRequestError("missing api_base_url")
}
url, err := url.Parse(c.APIBaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
if c.UploadBaseURL == "" {
return runnerErrors.NewBadRequestError("missing upload_base_url")
}
url, err = url.Parse(c.UploadBaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid upload_base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
if c.BaseURL == "" {
return runnerErrors.NewBadRequestError("missing base_url")
}
url, err = url.Parse(c.BaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
if c.CACertBundle != nil {
block, _ := pem.Decode(c.CACertBundle)
if block == nil {
return runnerErrors.NewBadRequestError("invalid ca_cert_bundle")
}
if _, err := x509.ParseCertificates(block.Bytes); err != nil {
return runnerErrors.NewBadRequestError("invalid ca_cert_bundle")
}
}
return nil
}
type UpdateGithubEndpointParams struct {
Description *string `json:"description,omitempty"`
APIBaseURL *string `json:"api_base_url,omitempty"`
UploadBaseURL *string `json:"upload_base_url,omitempty"`
BaseURL *string `json:"base_url,omitempty"`
CACertBundle []byte `json:"ca_cert_bundle,omitempty"`
}
func (u UpdateGithubEndpointParams) Validate() error {
if u.APIBaseURL != nil {
url, err := url.Parse(*u.APIBaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
}
if u.UploadBaseURL != nil {
url, err := url.Parse(*u.UploadBaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid upload_base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
}
if u.BaseURL != nil {
url, err := url.Parse(*u.BaseURL)
if err != nil || url.Scheme == "" || url.Host == "" {
return runnerErrors.NewBadRequestError("invalid base_url")
}
switch url.Scheme {
case httpsScheme, httpScheme:
default:
return runnerErrors.NewBadRequestError("invalid api_base_url")
}
}
if u.CACertBundle != nil {
block, _ := pem.Decode(u.CACertBundle)
if block == nil {
return runnerErrors.NewBadRequestError("invalid ca_cert_bundle")
}
if _, err := x509.ParseCertificates(block.Bytes); err != nil {
return runnerErrors.NewBadRequestError("invalid ca_cert_bundle")
}
}
return nil
}
type GithubPAT struct {
OAuth2Token string `json:"oauth2_token"`
}
type GithubApp struct {
AppID int64 `json:"app_id"`
InstallationID int64 `json:"installation_id"`
PrivateKeyBytes []byte `json:"private_key_bytes"`
}
func (g GithubApp) Validate() error {
if g.AppID == 0 {
return runnerErrors.NewBadRequestError("missing app_id")
}
if g.InstallationID == 0 {
return runnerErrors.NewBadRequestError("missing installation_id")
}
if len(g.PrivateKeyBytes) == 0 {
return runnerErrors.NewBadRequestError("missing private_key_bytes")
}
block, _ := pem.Decode(g.PrivateKeyBytes)
if block == nil {
return runnerErrors.NewBadRequestError("invalid private_key_bytes")
}
// Parse the private key as PCKS1
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf("parsing private_key_path: %w", err)
}
return nil
}
type CreateGithubCredentialsParams struct {
Name string `json:"name"`
Description string `json:"description"`
Endpoint string `json:"endpoint"`
AuthType GithubAuthType `json:"auth_type"`
PAT GithubPAT `json:"pat,omitempty"`
App GithubApp `json:"app,omitempty"`
}
func (c CreateGithubCredentialsParams) Validate() error {
if c.Name == "" {
return runnerErrors.NewBadRequestError("missing name")
}
if c.Endpoint == "" {
return runnerErrors.NewBadRequestError("missing endpoint")
}
switch c.AuthType {
case GithubAuthTypePAT, GithubAuthTypeApp:
default:
return runnerErrors.NewBadRequestError("invalid auth_type")
}
if c.AuthType == GithubAuthTypePAT {
if c.PAT.OAuth2Token == "" {
return runnerErrors.NewBadRequestError("missing oauth2_token")
}
}
if c.AuthType == GithubAuthTypeApp {
if err := c.App.Validate(); err != nil {
return errors.Wrap(err, "invalid app")
}
}
return nil
}
type UpdateGithubCredentialsParams struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
PAT *GithubPAT `json:"pat,omitempty"`
App *GithubApp `json:"app,omitempty"`
}
func (u UpdateGithubCredentialsParams) Validate() error {
if u.PAT != nil && u.App != nil {
return runnerErrors.NewBadRequestError("cannot update both PAT and App")
}
if u.PAT != nil {
if u.PAT.OAuth2Token == "" {
return runnerErrors.NewBadRequestError("missing oauth2_token")
}
}
if u.App != nil {
if err := u.App.Validate(); err != nil {
return errors.Wrap(err, "invalid app")
}
}
return nil
}

View file

@ -25,8 +25,8 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
return params.Enterprise{}, errors.Wrap(err, "validating params")
}
creds, ok := r.credentials[param.CredentialsName]
if !ok {
creds, err := r.store.GetGithubCredentialsByName(ctx, param.CredentialsName, true)
if err != nil {
return params.Enterprise{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
}
@ -54,6 +54,8 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
}
}()
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
var poolMgr common.PoolManager
poolMgr, err = r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store)
if err != nil {
@ -161,29 +163,19 @@ func (r *Runner) UpdateEnterprise(ctx context.Context, enterpriseID string, para
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
if param.CredentialsName != "" {
// Check that credentials are set before saving to db
if _, ok := r.credentials[param.CredentialsName]; !ok {
return params.Enterprise{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for enterprise %s", param.CredentialsName, enterprise.Name)
}
}
switch param.PoolBalancerType {
case params.PoolBalancerTypeRoundRobin, params.PoolBalancerTypePack, params.PoolBalancerTypeNone:
default:
return params.Enterprise{}, runnerErrors.NewBadRequestError("invalid pool balancer type: %s", param.PoolBalancerType)
}
enterprise, err = r.store.UpdateEnterprise(ctx, enterpriseID, param)
enterprise, err := r.store.UpdateEnterprise(ctx, enterpriseID, param)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
poolMgr, err := r.poolManagerCtrl.UpdateEnterprisePoolManager(r.ctx, enterprise)
if err != nil {
return params.Enterprise{}, fmt.Errorf("failed to update enterprise pool manager: %w", err)

View file

@ -19,12 +19,11 @@ import (
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing" //nolint:typecheck
@ -40,7 +39,7 @@ type EnterpriseTestFixtures struct {
Store dbCommon.Store
StoreEnterprises map[string]params.Enterprise
Providers map[string]common.Provider
Credentials map[string]config.Github
Credentials map[string]params.GithubCredentials
CreateEnterpriseParams params.CreateEnterpriseParams
CreatePoolParams params.CreatePoolParams
CreateInstanceParams params.CreateInstanceParams
@ -57,18 +56,25 @@ type EnterpriseTestSuite struct {
suite.Suite
Fixtures *EnterpriseTestFixtures
Runner *Runner
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *EnterpriseTestSuite) SetupTest() {
adminCtx := auth.GetAdminContext(context.Background())
// create testing sqlite database
dbCfg := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := database.NewDatabase(adminCtx, dbCfg)
db, err := database.NewDatabase(context.Background(), dbCfg)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some organization objects in the database, for testing purposes
enterprises := map[string]params.Enterprise{}
for i := 1; i <= 3; i++ {
@ -76,12 +82,12 @@ func (s *EnterpriseTestSuite) SetupTest() {
enterprise, err := db.CreateEnterprise(
adminCtx,
name,
fmt.Sprintf("test-creds-%v", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%v", i),
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (test-enterprise-%v)", i))
s.FailNow(fmt.Sprintf("failed to create database object (test-enterprise-%v): %+v", i, err))
}
enterprises[name] = enterprise
}
@ -98,16 +104,13 @@ func (s *EnterpriseTestSuite) SetupTest() {
Providers: map[string]common.Provider{
"test-provider": providerMock,
},
Credentials: map[string]config.Github{
"test-creds": {
Name: "test-creds-name",
Description: "test-creds-description",
OAuth2Token: "test-creds-oauth2-token",
},
Credentials: map[string]params.GithubCredentials{
s.testCreds.Name: s.testCreds,
s.secondaryTestCreds.Name: s.secondaryTestCreds,
},
CreateEnterpriseParams: params.CreateEnterpriseParams{
Name: "test-enterprise-create",
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-create-enterprise-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -126,7 +129,7 @@ func (s *EnterpriseTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-update-repo-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -148,7 +151,6 @@ func (s *EnterpriseTestSuite) SetupTest() {
// setup test runner
runner := &Runner{
providers: fixtures.Providers,
credentials: fixtures.Credentials,
ctx: fixtures.AdminContext,
store: fixtures.Store,
poolManagerCtrl: fixtures.PoolMgrCtrlMock,
@ -164,13 +166,13 @@ func (s *EnterpriseTestSuite) TestCreateEnterprise() {
// call tested function
enterprise, err := s.Runner.CreateEnterprise(s.Fixtures.AdminContext, s.Fixtures.CreateEnterpriseParams)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.CreateEnterpriseParams.Name, enterprise.Name)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateEnterpriseParams.CredentialsName].Name, enterprise.Credentials.Name)
s.Require().Equal(params.PoolBalancerTypeRoundRobin, enterprise.PoolBalancerType)
// assertions
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.CreateEnterpriseParams.Name, enterprise.Name)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateEnterpriseParams.CredentialsName].Name, enterprise.CredentialsName)
s.Require().Equal(params.PoolBalancerTypeRoundRobin, enterprise.PoolBalancerType)
}
func (s *EnterpriseTestSuite) TestCreateEnterpriseErrUnauthorized() {
@ -306,7 +308,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprise() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, org.WebhookSecret)
s.Require().Equal(params.PoolBalancerTypePack, org.PoolBalancerType)
}
@ -322,7 +324,9 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseInvalidCreds() {
_, err := s.Runner.UpdateEnterprise(s.Fixtures.AdminContext, s.Fixtures.StoreEnterprises["test-enterprise-1"].ID, s.Fixtures.UpdateRepoParams)
s.Require().Equal(runnerErrors.NewBadRequestError("invalid credentials (%s) for enterprise %s", s.Fixtures.UpdateRepoParams.CredentialsName, s.Fixtures.StoreEnterprises["test-enterprise-1"].Name), err)
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.FailNow(fmt.Sprintf("expected error: %v", runnerErrors.ErrNotFound))
}
}
func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolMgrFailed() {

View file

@ -0,0 +1,83 @@
package runner
import (
"context"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
)
func (r *Runner) ListCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
creds, err := r.store.ListGithubCredentials(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
}
return creds, nil
}
func (r *Runner) CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (params.GithubCredentials, error) {
if !auth.IsAdmin(ctx) {
return params.GithubCredentials{}, runnerErrors.ErrUnauthorized
}
if err := param.Validate(); err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "failed to validate github credentials params")
}
creds, err := r.store.CreateGithubCredentials(ctx, param)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "failed to create github credentials")
}
return creds, nil
}
func (r *Runner) GetGithubCredentials(ctx context.Context, id uint) (params.GithubCredentials, error) {
if !auth.IsAdmin(ctx) {
return params.GithubCredentials{}, runnerErrors.ErrUnauthorized
}
creds, err := r.store.GetGithubCredentials(ctx, id, true)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "failed to get github credentials")
}
return creds, nil
}
func (r *Runner) DeleteGithubCredentials(ctx context.Context, id uint) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
if err := r.store.DeleteGithubCredentials(ctx, id); err != nil {
return errors.Wrap(err, "failed to delete github credentials")
}
return nil
}
func (r *Runner) UpdateGithubCredentials(ctx context.Context, id uint, param params.UpdateGithubCredentialsParams) (params.GithubCredentials, error) {
if !auth.IsAdmin(ctx) {
return params.GithubCredentials{}, runnerErrors.ErrUnauthorized
}
if err := param.Validate(); err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "failed to validate github credentials params")
}
newCreds, err := r.store.UpdateGithubCredentials(ctx, id, param)
if err != nil {
return params.GithubCredentials{}, errors.Wrap(err, "failed to update github credentials")
}
return newCreds, nil
}

View file

@ -0,0 +1,82 @@
package runner
import (
"context"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
)
func (r *Runner) CreateGithubEndpoint(ctx context.Context, param params.CreateGithubEndpointParams) (params.GithubEndpoint, error) {
if !auth.IsAdmin(ctx) {
return params.GithubEndpoint{}, runnerErrors.ErrUnauthorized
}
if err := param.Validate(); err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "failed to validate github endpoint params")
}
ep, err := r.store.CreateGithubEndpoint(ctx, param)
if err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "failed to create github endpoint")
}
return ep, nil
}
func (r *Runner) GetGithubEndpoint(ctx context.Context, name string) (params.GithubEndpoint, error) {
if !auth.IsAdmin(ctx) {
return params.GithubEndpoint{}, runnerErrors.ErrUnauthorized
}
endpoint, err := r.store.GetGithubEndpoint(ctx, name)
if err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "failed to get github endpoint")
}
return endpoint, nil
}
func (r *Runner) DeleteGithubEndpoint(ctx context.Context, name string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
err := r.store.DeleteGithubEndpoint(ctx, name)
if err != nil {
return errors.Wrap(err, "failed to delete github endpoint")
}
return nil
}
func (r *Runner) UpdateGithubEndpoint(ctx context.Context, name string, param params.UpdateGithubEndpointParams) (params.GithubEndpoint, error) {
if !auth.IsAdmin(ctx) {
return params.GithubEndpoint{}, runnerErrors.ErrUnauthorized
}
if err := param.Validate(); err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "failed to validate github endpoint params")
}
newEp, err := r.store.UpdateGithubEndpoint(ctx, name, param)
if err != nil {
return params.GithubEndpoint{}, errors.Wrap(err, "failed to update github endpoint")
}
return newEp, nil
}
func (r *Runner) ListGithubEndpoints(ctx context.Context) ([]params.GithubEndpoint, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
endpoints, err := r.store.ListGithubEndpoints(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to list github endpoints")
}
return endpoints, nil
}

View file

@ -38,8 +38,8 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
return params.Organization{}, errors.Wrap(err, "validating params")
}
creds, ok := r.credentials[param.CredentialsName]
if !ok {
creds, err := r.store.GetGithubCredentialsByName(ctx, param.CredentialsName, true)
if err != nil {
return params.Organization{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
}
@ -67,6 +67,8 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
}
}()
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
poolMgr, err := r.poolManagerCtrl.CreateOrgPoolManager(r.ctx, org, r.providers, r.store)
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org pool manager")
@ -190,29 +192,19 @@ func (r *Runner) UpdateOrganization(ctx context.Context, orgID string, param par
r.mux.Lock()
defer r.mux.Unlock()
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}
if param.CredentialsName != "" {
// Check that credentials are set before saving to db
if _, ok := r.credentials[param.CredentialsName]; !ok {
return params.Organization{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for org %s", param.CredentialsName, org.Name)
}
}
switch param.PoolBalancerType {
case params.PoolBalancerTypeRoundRobin, params.PoolBalancerTypePack, params.PoolBalancerTypeNone:
default:
return params.Organization{}, runnerErrors.NewBadRequestError("invalid pool balancer type: %s", param.PoolBalancerType)
}
org, err = r.store.UpdateOrganization(ctx, orgID, param)
org, err := r.store.UpdateOrganization(ctx, orgID, param)
if err != nil {
return params.Organization{}, errors.Wrap(err, "updating org")
}
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
poolMgr, err := r.poolManagerCtrl.UpdateOrgPoolManager(r.ctx, org)
if err != nil {
return params.Organization{}, fmt.Errorf("updating org pool manager: %w", err)

View file

@ -19,12 +19,11 @@ import (
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
@ -40,7 +39,7 @@ type OrgTestFixtures struct {
Store dbCommon.Store
StoreOrgs map[string]params.Organization
Providers map[string]common.Provider
Credentials map[string]config.Github
Credentials map[string]params.GithubCredentials
CreateOrgParams params.CreateOrgParams
CreatePoolParams params.CreatePoolParams
CreateInstanceParams params.CreateInstanceParams
@ -57,18 +56,26 @@ type OrgTestSuite struct {
suite.Suite
Fixtures *OrgTestFixtures
Runner *Runner
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *OrgTestSuite) SetupTest() {
adminCtx := auth.GetAdminContext(context.Background())
// create testing sqlite database
dbCfg := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := database.NewDatabase(adminCtx, dbCfg)
db, err := database.NewDatabase(context.Background(), dbCfg)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some organization objects in the database, for testing purposes
orgs := map[string]params.Organization{}
for i := 1; i <= 3; i++ {
@ -76,7 +83,7 @@ func (s *OrgTestSuite) SetupTest() {
org, err := db.CreateOrganization(
adminCtx,
name,
fmt.Sprintf("test-creds-%v", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%v", i),
params.PoolBalancerTypeRoundRobin,
)
@ -98,16 +105,13 @@ func (s *OrgTestSuite) SetupTest() {
Providers: map[string]common.Provider{
"test-provider": providerMock,
},
Credentials: map[string]config.Github{
"test-creds": {
Name: "test-creds-name",
Description: "test-creds-description",
OAuth2Token: "test-creds-oauth2-token",
},
Credentials: map[string]params.GithubCredentials{
s.testCreds.Name: s.testCreds,
s.secondaryTestCreds.Name: s.secondaryTestCreds,
},
CreateOrgParams: params.CreateOrgParams{
Name: "test-org-create",
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-create-org-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -126,7 +130,7 @@ func (s *OrgTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-update-repo-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -148,7 +152,6 @@ func (s *OrgTestSuite) SetupTest() {
// setup test runner
runner := &Runner{
providers: fixtures.Providers,
credentials: fixtures.Credentials,
ctx: fixtures.AdminContext,
store: fixtures.Store,
poolManagerCtrl: fixtures.PoolMgrCtrlMock,
@ -169,7 +172,7 @@ func (s *OrgTestSuite) TestCreateOrganization() {
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.CreateOrgParams.Name, org.Name)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateOrgParams.CredentialsName].Name, org.CredentialsName)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateOrgParams.CredentialsName].Name, org.Credentials.Name)
s.Require().Equal(params.PoolBalancerTypeRoundRobin, org.PoolBalancerType)
}
@ -317,7 +320,7 @@ func (s *OrgTestSuite) TestUpdateOrganization() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, org.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, org.WebhookSecret)
}
@ -346,8 +349,9 @@ func (s *OrgTestSuite) TestUpdateOrganizationInvalidCreds() {
s.Fixtures.UpdateRepoParams.CredentialsName = invalidCredentialsName
_, err := s.Runner.UpdateOrganization(s.Fixtures.AdminContext, s.Fixtures.StoreOrgs["test-org-1"].ID, s.Fixtures.UpdateRepoParams)
s.Require().Equal(runnerErrors.NewBadRequestError("invalid credentials (%s) for org %s", s.Fixtures.UpdateRepoParams.CredentialsName, s.Fixtures.StoreOrgs["test-org-1"].Name), err)
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.FailNow(fmt.Sprintf("expected error: %v", runnerErrors.ErrNotFound))
}
}
func (s *OrgTestSuite) TestUpdateOrganizationPoolMgrFailed() {

View file

@ -45,6 +45,11 @@ type PoolTestSuite struct {
suite.Suite
Fixtures *PoolTestFixtures
Runner *Runner
adminCtx context.Context
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *PoolTestSuite) SetupTest() {
@ -57,8 +62,14 @@ func (s *PoolTestSuite) SetupTest() {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.adminCtx = garmTesting.ImpersonateAdminContext(adminCtx, db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(s.adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(s.adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(s.adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create an organization for testing purposes
org, err := db.CreateOrganization(context.Background(), "test-org", "test-creds", "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
org, err := db.CreateOrganization(s.adminCtx, "test-org", s.testCreds.Name, "test-webhookSecret", params.PoolBalancerTypeRoundRobin)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create org: %s", err))
}
@ -71,7 +82,7 @@ func (s *PoolTestSuite) SetupTest() {
orgPools := []params.Pool{}
for i := 1; i <= 3; i++ {
pool, err := db.CreateEntityPool(
context.Background(),
adminCtx,
entity,
params.CreatePoolParams{
ProviderName: "test-provider",
@ -112,10 +123,9 @@ func (s *PoolTestSuite) SetupTest() {
// setup test runner
runner := &Runner{
providers: fixtures.Providers,
credentials: fixtures.Credentials,
store: fixtures.Store,
ctx: fixtures.AdminContext,
providers: fixtures.Providers,
store: fixtures.Store,
ctx: fixtures.AdminContext,
}
s.Runner = runner
}

View file

@ -38,8 +38,8 @@ func (r *Runner) CreateRepository(ctx context.Context, param params.CreateRepoPa
return params.Repository{}, errors.Wrap(err, "validating params")
}
creds, ok := r.credentials[param.CredentialsName]
if !ok {
creds, err := r.store.GetGithubCredentialsByName(ctx, param.CredentialsName, true)
if err != nil {
return params.Repository{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
}
@ -67,6 +67,8 @@ func (r *Runner) CreateRepository(ctx context.Context, param params.CreateRepoPa
}
}()
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
poolMgr, err := r.poolManagerCtrl.CreateRepoPoolManager(r.ctx, repo, r.providers, r.store)
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repo pool manager")
@ -189,29 +191,19 @@ func (r *Runner) UpdateRepository(ctx context.Context, repoID string, param para
r.mux.Lock()
defer r.mux.Unlock()
repo, err := r.store.GetRepositoryByID(ctx, repoID)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}
if param.CredentialsName != "" {
// Check that credentials are set before saving to db
if _, ok := r.credentials[param.CredentialsName]; !ok {
return params.Repository{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for repo %s/%s", param.CredentialsName, repo.Owner, repo.Name)
}
}
switch param.PoolBalancerType {
case params.PoolBalancerTypeRoundRobin, params.PoolBalancerTypePack, params.PoolBalancerTypeNone:
default:
return params.Repository{}, runnerErrors.NewBadRequestError("invalid pool balancer type: %s", param.PoolBalancerType)
}
repo, err = r.store.UpdateRepository(ctx, repoID, param)
repo, err := r.store.UpdateRepository(ctx, repoID, param)
if err != nil {
return params.Repository{}, errors.Wrap(err, "updating repo")
}
// Use the admin context in the pool manager. Any access control is already done above when
// updating the store.
poolMgr, err := r.poolManagerCtrl.UpdateRepoPoolManager(r.ctx, repo)
if err != nil {
return params.Repository{}, fmt.Errorf("failed to update pool manager: %w", err)

View file

@ -19,12 +19,11 @@ import (
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/database"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
@ -39,7 +38,7 @@ type RepoTestFixtures struct {
Store dbCommon.Store
StoreRepos map[string]params.Repository
Providers map[string]common.Provider
Credentials map[string]config.Github
Credentials map[string]params.GithubCredentials
CreateRepoParams params.CreateRepoParams
CreatePoolParams params.CreatePoolParams
CreateInstanceParams params.CreateInstanceParams
@ -56,18 +55,25 @@ type RepoTestSuite struct {
suite.Suite
Fixtures *RepoTestFixtures
Runner *Runner
testCreds params.GithubCredentials
secondaryTestCreds params.GithubCredentials
githubEndpoint params.GithubEndpoint
}
func (s *RepoTestSuite) SetupTest() {
adminCtx := auth.GetAdminContext(context.Background())
// create testing sqlite database
dbCfg := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := database.NewDatabase(adminCtx, dbCfg)
db, err := database.NewDatabase(context.Background(), dbCfg)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some repository objects in the database, for testing purposes
repos := map[string]params.Repository{}
for i := 1; i <= 3; i++ {
@ -76,12 +82,12 @@ func (s *RepoTestSuite) SetupTest() {
adminCtx,
fmt.Sprintf("test-owner-%v", i),
name,
fmt.Sprintf("test-creds-%v", i),
s.testCreds.Name,
fmt.Sprintf("test-webhook-secret-%v", i),
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (test-repo-%v)", i))
s.FailNow(fmt.Sprintf("failed to create database object (test-repo-%v): %q", i, err))
}
repos[name] = repo
}
@ -91,23 +97,20 @@ func (s *RepoTestSuite) SetupTest() {
var minIdleRunners uint = 20
providerMock := runnerCommonMocks.NewProvider(s.T())
fixtures := &RepoTestFixtures{
AdminContext: auth.GetAdminContext(context.Background()),
AdminContext: adminCtx,
Store: db,
StoreRepos: repos,
Providers: map[string]common.Provider{
"test-provider": providerMock,
},
Credentials: map[string]config.Github{
"test-creds": {
Name: "test-creds-name",
Description: "test-creds-description",
OAuth2Token: "test-creds-oauth2-token",
},
Credentials: map[string]params.GithubCredentials{
s.testCreds.Name: s.testCreds,
s.secondaryTestCreds.Name: s.secondaryTestCreds,
},
CreateRepoParams: params.CreateRepoParams{
Owner: "test-owner-create",
Name: "test-repo-create",
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-create-repo-webhook-secret",
},
CreatePoolParams: params.CreatePoolParams{
@ -126,7 +129,7 @@ func (s *RepoTestSuite) SetupTest() {
OSType: "linux",
},
UpdateRepoParams: params.UpdateEntityParams{
CredentialsName: "test-creds",
CredentialsName: s.testCreds.Name,
WebhookSecret: "test-update-repo-webhook-secret",
},
UpdatePoolParams: params.UpdatePoolParams{
@ -148,7 +151,6 @@ func (s *RepoTestSuite) SetupTest() {
// setup test runner
runner := &Runner{
providers: fixtures.Providers,
credentials: fixtures.Credentials,
ctx: fixtures.AdminContext,
store: fixtures.Store,
poolManagerCtrl: fixtures.PoolMgrCtrlMock,
@ -167,10 +169,11 @@ func (s *RepoTestSuite) TestCreateRepository() {
// assertions
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.CreateRepoParams.Owner, repo.Owner)
s.Require().Equal(s.Fixtures.CreateRepoParams.Name, repo.Name)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateRepoParams.CredentialsName].Name, repo.CredentialsName)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateRepoParams.CredentialsName].Name, repo.Credentials.Name)
s.Require().Equal(params.PoolBalancerTypeRoundRobin, repo.PoolBalancerType)
}
@ -190,7 +193,7 @@ func (s *RepoTestSuite) TestCreareRepositoryPoolBalancerTypePack() {
s.Require().Nil(err)
s.Require().Equal(param.Owner, repo.Owner)
s.Require().Equal(param.Name, repo.Name)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateRepoParams.CredentialsName].Name, repo.CredentialsName)
s.Require().Equal(s.Fixtures.Credentials[s.Fixtures.CreateRepoParams.CredentialsName].Name, repo.Credentials.Name)
s.Require().Equal(params.PoolBalancerTypePack, repo.PoolBalancerType)
}
@ -327,7 +330,7 @@ func (s *RepoTestSuite) TestUpdateRepository() {
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, repo.CredentialsName)
s.Require().Equal(s.Fixtures.UpdateRepoParams.CredentialsName, repo.Credentials.Name)
s.Require().Equal(s.Fixtures.UpdateRepoParams.WebhookSecret, repo.WebhookSecret)
s.Require().Equal(params.PoolBalancerTypeRoundRobin, repo.PoolBalancerType)
}
@ -343,7 +346,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryBalancingType() {
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Require().Nil(err)
s.Require().Equal(updateRepoParams.CredentialsName, repo.CredentialsName)
s.Require().Equal(updateRepoParams.CredentialsName, repo.Credentials.Name)
s.Require().Equal(updateRepoParams.WebhookSecret, repo.WebhookSecret)
s.Require().Equal(params.PoolBalancerTypePack, repo.PoolBalancerType)
}
@ -358,7 +361,9 @@ func (s *RepoTestSuite) TestUpdateRepositoryInvalidCreds() {
_, err := s.Runner.UpdateRepository(s.Fixtures.AdminContext, s.Fixtures.StoreRepos["test-repo-1"].ID, s.Fixtures.UpdateRepoParams)
s.Require().Equal(runnerErrors.NewBadRequestError("invalid credentials (%s) for repo %s/%s", s.Fixtures.UpdateRepoParams.CredentialsName, s.Fixtures.StoreRepos["test-repo-1"].Owner, s.Fixtures.StoreRepos["test-repo-1"].Name), err)
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.FailNow(fmt.Sprintf("expected error: %v", runnerErrors.ErrNotFound))
}
}
func (s *RepoTestSuite) TestUpdateRepositoryPoolMgrFailed() {

View file

@ -67,7 +67,7 @@ func NewRunner(ctx context.Context, cfg config.Config, db dbCommon.Store) (*Runn
poolManagerCtrl := &poolManagerCtrl{
controllerID: ctrlID.ControllerID.String(),
config: cfg,
credentials: creds,
store: db,
repositories: map[string]common.PoolManager{},
organizations: map[string]common.PoolManager{},
enterprises: map[string]common.PoolManager{},
@ -78,7 +78,6 @@ func NewRunner(ctx context.Context, cfg config.Config, db dbCommon.Store) (*Runn
store: db,
poolManagerCtrl: poolManagerCtrl,
providers: providers,
credentials: creds,
controllerID: ctrlID.ControllerID,
}
@ -94,7 +93,7 @@ type poolManagerCtrl struct {
controllerID string
config config.Config
credentials map[string]config.Github
store dbCommon.Store
repositories map[string]common.PoolManager
organizations map[string]common.PoolManager
@ -105,7 +104,12 @@ func (p *poolManagerCtrl) CreateRepoPoolManager(ctx context.Context, repo params
p.mux.Lock()
defer p.mux.Unlock()
cfgInternal, err := p.getInternalConfig(ctx, repo.CredentialsName, repo.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, repo.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
cfgInternal, err := p.getInternalConfig(ctx, creds, repo.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -133,7 +137,12 @@ func (p *poolManagerCtrl) UpdateRepoPoolManager(ctx context.Context, repo params
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "repository %s/%s pool manager not loaded", repo.Owner, repo.Name)
}
internalCfg, err := p.getInternalConfig(ctx, repo.CredentialsName, repo.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, repo.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
internalCfg, err := p.getInternalConfig(ctx, creds, repo.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -178,7 +187,11 @@ func (p *poolManagerCtrl) CreateOrgPoolManager(ctx context.Context, org params.O
p.mux.Lock()
defer p.mux.Unlock()
cfgInternal, err := p.getInternalConfig(ctx, org.CredentialsName, org.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, org.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
cfgInternal, err := p.getInternalConfig(ctx, creds, org.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -205,7 +218,11 @@ func (p *poolManagerCtrl) UpdateOrgPoolManager(ctx context.Context, org params.O
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "org %s pool manager not loaded", org.Name)
}
internalCfg, err := p.getInternalConfig(ctx, org.CredentialsName, org.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, org.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
internalCfg, err := p.getInternalConfig(ctx, creds, org.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -250,7 +267,11 @@ func (p *poolManagerCtrl) CreateEnterprisePoolManager(ctx context.Context, enter
p.mux.Lock()
defer p.mux.Unlock()
cfgInternal, err := p.getInternalConfig(ctx, enterprise.CredentialsName, enterprise.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, enterprise.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
cfgInternal, err := p.getInternalConfig(ctx, creds, enterprise.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -278,7 +299,11 @@ func (p *poolManagerCtrl) UpdateEnterprisePoolManager(ctx context.Context, enter
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "enterprise %s pool manager not loaded", enterprise.Name)
}
internalCfg, err := p.getInternalConfig(ctx, enterprise.CredentialsName, enterprise.GetBalancerType())
creds, err := p.store.GetGithubCredentials(ctx, enterprise.CredentialsID, true)
if err != nil {
return nil, errors.Wrap(err, "fetching credentials")
}
internalCfg, err := p.getInternalConfig(ctx, creds, enterprise.GetBalancerType())
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
@ -319,42 +344,21 @@ func (p *poolManagerCtrl) GetEnterprisePoolManagers() (map[string]common.PoolMan
return p.enterprises, nil
}
func (p *poolManagerCtrl) getInternalConfig(ctx context.Context, credsName string, poolBalancerType params.PoolBalancerType) (params.Internal, error) {
creds, ok := p.credentials[credsName]
if !ok {
return params.Internal{}, runnerErrors.NewBadRequestError("invalid credential name (%s)", credsName)
}
caBundle, err := creds.CACertBundle()
if err != nil {
return params.Internal{}, fmt.Errorf("fetching CA bundle for creds: %w", err)
}
func (p *poolManagerCtrl) getInternalConfig(_ context.Context, creds params.GithubCredentials, poolBalancerType params.PoolBalancerType) (params.Internal, error) {
var controllerWebhookURL string
if p.config.Default.WebhookURL != "" {
controllerWebhookURL = fmt.Sprintf("%s/%s", p.config.Default.WebhookURL, p.controllerID)
}
httpClient, err := creds.HTTPClient(ctx)
if err != nil {
return params.Internal{}, fmt.Errorf("fetching http client for creds: %w", err)
}
return params.Internal{
ControllerID: p.controllerID,
InstanceCallbackURL: p.config.Default.CallbackURL,
InstanceMetadataURL: p.config.Default.MetadataURL,
BaseWebhookURL: p.config.Default.WebhookURL,
ControllerWebhookURL: controllerWebhookURL,
JWTSecret: p.config.JWTAuth.Secret,
PoolBalancerType: poolBalancerType,
GithubCredentialsDetails: params.GithubCredentials{
Name: creds.Name,
Description: creds.Description,
BaseURL: creds.BaseEndpoint(),
APIBaseURL: creds.APIEndpoint(),
UploadBaseURL: creds.UploadEndpoint(),
CABundle: caBundle,
HTTPClient: httpClient,
},
ControllerID: p.controllerID,
InstanceCallbackURL: p.config.Default.CallbackURL,
InstanceMetadataURL: p.config.Default.MetadataURL,
BaseWebhookURL: p.config.Default.WebhookURL,
ControllerWebhookURL: controllerWebhookURL,
JWTSecret: p.config.JWTAuth.Secret,
PoolBalancerType: poolBalancerType,
GithubCredentialsDetails: creds,
}, nil
}
@ -367,8 +371,7 @@ type Runner struct {
poolManagerCtrl PoolManagerController
providers map[string]common.Provider
credentials map[string]config.Github
providers map[string]common.Provider
controllerInfo params.ControllerInfo
controllerID uuid.UUID
@ -420,25 +423,6 @@ func (r *Runner) GetControllerInfo(ctx context.Context) (params.ControllerInfo,
}, nil
}
func (r *Runner) ListCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
ret := []params.GithubCredentials{}
for _, val := range r.config.Github {
ret = append(ret, params.GithubCredentials{
Name: val.Name,
Description: val.Description,
BaseURL: val.BaseEndpoint(),
APIBaseURL: val.APIEndpoint(),
UploadBaseURL: val.UploadEndpoint(),
AuthType: params.GithubAuthType(val.AuthType),
})
}
return ret, nil
}
func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized

View file

@ -6,6 +6,7 @@ import (
"github.com/cloudbase/garm/client"
clientControllerInfo "github.com/cloudbase/garm/client/controller_info"
clientCredentials "github.com/cloudbase/garm/client/credentials"
clientEndpoints "github.com/cloudbase/garm/client/endpoints"
clientFirstRun "github.com/cloudbase/garm/client/first_run"
clientInstances "github.com/cloudbase/garm/client/instances"
clientJobs "github.com/cloudbase/garm/client/jobs"
@ -18,9 +19,7 @@ import (
"github.com/cloudbase/garm/params"
)
// ///////////
// Garm Init /
// ///////////
// firstRun will initialize a new garm installation.
func firstRun(apiCli *client.GarmAPI, newUser params.NewUserParams) (params.User, error) {
firstRunResponse, err := apiCli.FirstRun.FirstRun(
clientFirstRun.NewFirstRunParams().WithBody(newUser),
@ -41,9 +40,7 @@ func login(apiCli *client.GarmAPI, params params.PasswordLoginParams) (string, e
return loginResponse.Payload.Token, nil
}
// ////////////////////////////
// Credentials and Providers //
// ////////////////////////////
// listCredentials lists all the credentials configured in GARM.
func listCredentials(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (params.Credentials, error) {
listCredentialsResponse, err := apiCli.Credentials.ListCredentials(
clientCredentials.NewListCredentialsParams(),
@ -54,6 +51,89 @@ func listCredentials(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfo
return listCredentialsResponse.Payload, nil
}
func createGithubCredentials(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, credentialsParams params.CreateGithubCredentialsParams) (*params.GithubCredentials, error) {
createCredentialsResponse, err := apiCli.Credentials.CreateCredentials(
clientCredentials.NewCreateCredentialsParams().WithBody(credentialsParams),
apiAuthToken)
if err != nil {
return nil, err
}
return &createCredentialsResponse.Payload, nil
}
func deleteGithubCredentials(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, credentialsID int64) error {
return apiCli.Credentials.DeleteCredentials(
clientCredentials.NewDeleteCredentialsParams().WithID(credentialsID),
apiAuthToken)
}
func getGithubCredential(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, credentialsID int64) (*params.GithubCredentials, error) {
getCredentialsResponse, err := apiCli.Credentials.GetCredentials(
clientCredentials.NewGetCredentialsParams().WithID(credentialsID),
apiAuthToken)
if err != nil {
return nil, err
}
return &getCredentialsResponse.Payload, nil
}
func updateGithubCredentials(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, credentialsID int64, credentialsParams params.UpdateGithubCredentialsParams) (*params.GithubCredentials, error) {
updateCredentialsResponse, err := apiCli.Credentials.UpdateCredentials(
clientCredentials.NewUpdateCredentialsParams().WithID(credentialsID).WithBody(credentialsParams),
apiAuthToken)
if err != nil {
return nil, err
}
return &updateCredentialsResponse.Payload, nil
}
func createGithubEndpoint(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, endpointParams params.CreateGithubEndpointParams) (*params.GithubEndpoint, error) {
createEndpointResponse, err := apiCli.Endpoints.CreateGithubEndpoint(
clientEndpoints.NewCreateGithubEndpointParams().WithBody(endpointParams),
apiAuthToken)
if err != nil {
return nil, err
}
return &createEndpointResponse.Payload, nil
}
func listGithubEndpoints(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (params.GithubEndpoints, error) {
listEndpointsResponse, err := apiCli.Endpoints.ListGithubEndpoints(
clientEndpoints.NewListGithubEndpointsParams(),
apiAuthToken)
if err != nil {
return nil, err
}
return listEndpointsResponse.Payload, nil
}
func getGithubEndpoint(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, endpointName string) (*params.GithubEndpoint, error) {
getEndpointResponse, err := apiCli.Endpoints.GetGithubEndpoint(
clientEndpoints.NewGetGithubEndpointParams().WithName(endpointName),
apiAuthToken)
if err != nil {
return nil, err
}
return &getEndpointResponse.Payload, nil
}
func deleteGithubEndpoint(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, endpointName string) error {
return apiCli.Endpoints.DeleteGithubEndpoint(
clientEndpoints.NewDeleteGithubEndpointParams().WithName(endpointName),
apiAuthToken)
}
func updateGithubEndpoint(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter, endpointName string, endpointParams params.UpdateGithubEndpointParams) (*params.GithubEndpoint, error) {
updateEndpointResponse, err := apiCli.Endpoints.UpdateGithubEndpoint(
clientEndpoints.NewUpdateGithubEndpointParams().WithName(endpointName).WithBody(endpointParams),
apiAuthToken)
if err != nil {
return nil, err
}
return &updateEndpointResponse.Payload, nil
}
// listProviders lists all the providers configured in GARM.
func listProviders(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (params.Providers, error) {
listProvidersResponse, err := apiCli.Providers.ListProviders(
clientProviders.NewListProvidersParams(),
@ -64,9 +144,7 @@ func listProviders(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWr
return listProvidersResponse.Payload, nil
}
// ////////////////////////
// // Controller info ////
// ////////////////////////
// getControllerInfo returns information about the GARM controller.
func getControllerInfo(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (params.ControllerInfo, error) {
controllerInfoResponse, err := apiCli.ControllerInfo.ControllerInfo(
clientControllerInfo.NewControllerInfoParams(),
@ -77,9 +155,7 @@ func getControllerInfo(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthIn
return controllerInfoResponse.Payload, nil
}
// ////////
// Jobs //
// ////////
// listJobs lists all the jobs configured in GARM.
func listJobs(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (params.Jobs, error) {
listJobsResponse, err := apiCli.Jobs.ListJobs(
clientJobs.NewListJobsParams(),
@ -90,9 +166,7 @@ func listJobs(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter)
return listJobsResponse.Payload, nil
}
// //////////////////
// / Metrics Token //
// //////////////////
// getMetricsToken returns the metrics token.
func getMetricsToken(apiCli *client.GarmAPI, apiAuthToken runtime.ClientAuthInfoWriter) (string, error) {
getMetricsTokenResponse, err := apiCli.MetricsToken.GetMetricsToken(
clientMetricsToken.NewGetMetricsTokenParams(),

View file

@ -0,0 +1,206 @@
package e2e
import (
"fmt"
"log/slog"
"github.com/cloudbase/garm/params"
)
func EnsureTestCredentials(name string, oauthToken string, endpointName string) {
slog.Info("Ensuring test credentials exist")
createCredsParams := params.CreateGithubCredentialsParams{
Name: name,
Endpoint: endpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: oauthToken,
},
}
CreateGithubCredentials(createCredsParams)
createCredsParams.Name = fmt.Sprintf("%s-clone", name)
CreateGithubCredentials(createCredsParams)
}
func createDummyCredentials(name, endpointName string) *params.GithubCredentials {
createCredsParams := params.CreateGithubCredentialsParams{
Name: name,
Endpoint: endpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "dummy",
},
}
return CreateGithubCredentials(createCredsParams)
}
func TestGithubCredentialsErrorOnDuplicateCredentialsName() {
slog.Info("Testing error on duplicate credentials name")
creds := createDummyCredentials(dummyCredentialsName, defaultEndpointName)
defer DeleteGithubCredential(int64(creds.ID))
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "dummy",
},
}
if _, err := createGithubCredentials(cli, authToken, createCredsParams); err == nil {
panic("expected error when creating credentials with duplicate name")
}
}
func TestGithubCredentialsFailsToDeleteWhenInUse() {
slog.Info("Testing error when deleting credentials in use")
creds := createDummyCredentials(dummyCredentialsName, defaultEndpointName)
repo := CreateRepo("dummy-owner", "dummy-repo", creds.Name, "superSecret@123BlaBla")
defer func() {
deleteRepo(cli, authToken, repo.ID)
deleteGithubCredentials(cli, authToken, int64(creds.ID))
}()
if err := deleteGithubCredentials(cli, authToken, int64(creds.ID)); err == nil {
panic("expected error when deleting credentials in use")
}
}
func TestGithubCredentialsFailsOnInvalidAuthType() {
slog.Info("Testing error on invalid auth type")
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthType("invalid"),
PAT: params.GithubPAT{
OAuth2Token: "dummy",
},
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with invalid auth type")
}
expectAPIStatusCode(err, 400)
}
func TestGithubCredentialsFailsWhenAuthTypeParamsAreIncorrect() {
slog.Info("Testing error when auth type params are incorrect")
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
App: params.GithubApp{
AppID: 123,
InstallationID: 456,
PrivateKeyBytes: getTestFileContents("certs/srv-key.pem"),
},
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with invalid auth type params")
}
expectAPIStatusCode(err, 400)
}
func TestGithubCredentialsFailsWhenAuthTypeParamsAreMissing() {
slog.Info("Testing error when auth type params are missing")
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypeApp,
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with missing auth type params")
}
expectAPIStatusCode(err, 400)
}
func TestGithubCredentialsUpdateFailsWhenBothPATAndAppAreSupplied() {
slog.Info("Testing error when both PAT and App are supplied")
creds := createDummyCredentials(dummyCredentialsName, defaultEndpointName)
defer DeleteGithubCredential(int64(creds.ID))
updateCredsParams := params.UpdateGithubCredentialsParams{
PAT: &params.GithubPAT{
OAuth2Token: "dummy",
},
App: &params.GithubApp{
AppID: 123,
InstallationID: 456,
PrivateKeyBytes: getTestFileContents("certs/srv-key.pem"),
},
}
_, err := updateGithubCredentials(cli, authToken, int64(creds.ID), updateCredsParams)
if err == nil {
panic("expected error when updating credentials with both PAT and App")
}
expectAPIStatusCode(err, 400)
}
func TestGithubCredentialsFailWhenAppKeyIsInvalid() {
slog.Info("Testing error when app key is invalid")
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypeApp,
App: params.GithubApp{
AppID: 123,
InstallationID: 456,
PrivateKeyBytes: []byte("invalid"),
},
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with invalid app key")
}
expectAPIStatusCode(err, 400)
}
func TestGithubCredentialsFailWhenEndpointDoesntExist() {
slog.Info("Testing error when endpoint doesn't exist")
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: "iDontExist.example.com",
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "dummy",
},
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with invalid endpoint")
}
expectAPIStatusCode(err, 404)
}
func TestGithubCredentialsFailsOnDuplicateName() {
slog.Info("Testing error on duplicate credentials name")
creds := createDummyCredentials(dummyCredentialsName, defaultEndpointName)
defer DeleteGithubCredential(int64(creds.ID))
createCredsParams := params.CreateGithubCredentialsParams{
Name: dummyCredentialsName,
Endpoint: defaultEndpointName,
Description: "GARM test credentials",
AuthType: params.GithubAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "dummy",
},
}
_, err := createGithubCredentials(cli, authToken, createCredsParams)
if err == nil {
panic("expected error when creating credentials with duplicate name")
}
expectAPIStatusCode(err, 409)
}

View file

@ -18,6 +18,74 @@ func ListCredentials() params.Credentials {
return credentials
}
func CreateGithubCredentials(credentialsParams params.CreateGithubCredentialsParams) *params.GithubCredentials {
slog.Info("Create GitHub credentials")
credentials, err := createGithubCredentials(cli, authToken, credentialsParams)
if err != nil {
panic(err)
}
return credentials
}
func GetGithubCredential(id int64) *params.GithubCredentials {
slog.Info("Get GitHub credential")
credentials, err := getGithubCredential(cli, authToken, id)
if err != nil {
panic(err)
}
return credentials
}
func DeleteGithubCredential(id int64) {
slog.Info("Delete GitHub credential")
if err := deleteGithubCredentials(cli, authToken, id); err != nil {
panic(err)
}
}
func CreateGithubEndpoint(endpointParams params.CreateGithubEndpointParams) *params.GithubEndpoint {
slog.Info("Create GitHub endpoint")
endpoint, err := createGithubEndpoint(cli, authToken, endpointParams)
if err != nil {
panic(err)
}
return endpoint
}
func ListGithubEndpoints() params.GithubEndpoints {
slog.Info("List GitHub endpoints")
endpoints, err := listGithubEndpoints(cli, authToken)
if err != nil {
panic(err)
}
return endpoints
}
func GetGithubEndpoint(name string) *params.GithubEndpoint {
slog.Info("Get GitHub endpoint")
endpoint, err := getGithubEndpoint(cli, authToken, name)
if err != nil {
panic(err)
}
return endpoint
}
func DeleteGithubEndpoint(name string) {
slog.Info("Delete GitHub endpoint")
if err := deleteGithubEndpoint(cli, authToken, name); err != nil {
panic(err)
}
}
func UpdateGithubEndpoint(name string, updateParams params.UpdateGithubEndpointParams) *params.GithubEndpoint {
slog.Info("Update GitHub endpoint")
updated, err := updateGithubEndpoint(cli, authToken, name, updateParams)
if err != nil {
panic(err)
}
return updated
}
func ListProviders() params.Providers {
slog.Info("List providers")
providers, err := listProviders(cli, authToken)

View file

@ -0,0 +1,247 @@
package e2e
import (
"log/slog"
"os"
"path/filepath"
"github.com/cloudbase/garm/params"
)
const (
defaultEndpointName string = "github.com"
dummyCredentialsName string = "dummy"
)
func MustDefaultGithubEndpoint() {
ep := GetGithubEndpoint("github.com")
if ep == nil {
panic("Default GitHub endpoint not found")
}
if ep.Name != "github.com" {
panic("Default GitHub endpoint name mismatch")
}
}
func checkEndpointParamsAreEqual(a, b params.GithubEndpoint) {
if a.Name != b.Name {
panic("Endpoint name mismatch")
}
if a.Description != b.Description {
panic("Endpoint description mismatch")
}
if a.BaseURL != b.BaseURL {
panic("Endpoint base URL mismatch")
}
if a.APIBaseURL != b.APIBaseURL {
panic("Endpoint API base URL mismatch")
}
if a.UploadBaseURL != b.UploadBaseURL {
panic("Endpoint upload base URL mismatch")
}
if string(a.CACertBundle) != string(b.CACertBundle) {
panic("Endpoint CA cert bundle mismatch")
}
}
func getTestFileContents(relPath string) []byte {
baseDir := os.Getenv("GARM_CHECKOUT_DIR")
if baseDir == "" {
panic("GARM_CHECKOUT_DIR not set")
}
contents, err := os.ReadFile(filepath.Join(baseDir, "testdata", relPath))
if err != nil {
panic(err)
}
return contents
}
func TestGithubEndpointOperations() {
slog.Info("Testing endpoint operations")
MustDefaultGithubEndpoint()
caBundle := getTestFileContents("certs/srv-pub.pem")
endpointParams := params.CreateGithubEndpointParams{
Name: "test-endpoint",
Description: "Test endpoint",
BaseURL: "https://ghes.example.com",
APIBaseURL: "https://api.ghes.example.com/",
UploadBaseURL: "https://uploads.ghes.example.com/",
CACertBundle: caBundle,
}
endpoint := CreateGithubEndpoint(endpointParams)
if endpoint.Name != endpointParams.Name {
panic("Endpoint name mismatch")
}
if endpoint.Description != endpointParams.Description {
panic("Endpoint description mismatch")
}
if endpoint.BaseURL != endpointParams.BaseURL {
panic("Endpoint base URL mismatch")
}
if endpoint.APIBaseURL != endpointParams.APIBaseURL {
panic("Endpoint API base URL mismatch")
}
if endpoint.UploadBaseURL != endpointParams.UploadBaseURL {
panic("Endpoint upload base URL mismatch")
}
if string(endpoint.CACertBundle) != string(caBundle) {
panic("Endpoint CA cert bundle mismatch")
}
endpoint2 := GetGithubEndpoint(endpointParams.Name)
if endpoint == nil || endpoint2 == nil {
panic("endpoint is nil")
}
checkEndpointParamsAreEqual(*endpoint, *endpoint2)
endpoints := ListGithubEndpoints()
var found bool
for _, ep := range endpoints {
if ep.Name == endpointParams.Name {
checkEndpointParamsAreEqual(*endpoint, ep)
found = true
break
}
}
if !found {
panic("Endpoint not found in list")
}
DeleteGithubEndpoint(endpoint.Name)
}
func TestGithubEndpointMustFailToDeleteDefaultGithubEndpoint() {
slog.Info("Testing error when deleting default github.com endpoint")
if err := deleteGithubEndpoint(cli, authToken, "github.com"); err == nil {
panic("expected error when attempting to delete the default github.com endpoint")
}
}
func TestGithubEndpointFailsOnInvalidCABundle() {
slog.Info("Testing endpoint creation with invalid CA cert bundle")
badCABundle := getTestFileContents("certs/srv-key.pem")
endpointParams := params.CreateGithubEndpointParams{
Name: "dummy",
Description: "Dummy endpoint",
BaseURL: "https://ghes.example.com",
APIBaseURL: "https://api.ghes.example.com/",
UploadBaseURL: "https://uploads.ghes.example.com/",
CACertBundle: badCABundle,
}
if _, err := createGithubEndpoint(cli, authToken, endpointParams); err == nil {
panic("expected error when creating endpoint with invalid CA cert bundle")
}
}
func TestGithubEndpointDeletionFailsWhenCredentialsExist() {
slog.Info("Testing endpoint deletion when credentials exist")
endpointParams := params.CreateGithubEndpointParams{
Name: "dummy",
Description: "Dummy endpoint",
BaseURL: "https://ghes.example.com",
APIBaseURL: "https://api.ghes.example.com/",
UploadBaseURL: "https://uploads.ghes.example.com/",
}
endpoint := CreateGithubEndpoint(endpointParams)
creds := createDummyCredentials("test-creds", endpoint.Name)
if err := deleteGithubEndpoint(cli, authToken, endpoint.Name); err == nil {
panic("expected error when deleting endpoint with credentials")
}
DeleteGithubCredential(int64(creds.ID))
DeleteGithubEndpoint(endpoint.Name)
}
func TestGithubEndpointFailsOnDuplicateName() {
slog.Info("Testing endpoint creation with duplicate name")
endpointParams := params.CreateGithubEndpointParams{
Name: "github.com",
Description: "Dummy endpoint",
BaseURL: "https://ghes.example.com",
APIBaseURL: "https://api.ghes.example.com/",
UploadBaseURL: "https://uploads.ghes.example.com/",
}
if _, err := createGithubEndpoint(cli, authToken, endpointParams); err == nil {
panic("expected error when creating endpoint with duplicate name")
}
}
func TestGithubEndpointUpdateEndpoint() {
slog.Info("Testing endpoint update")
endpoint := createDummyEndpoint("dummy")
defer DeleteGithubEndpoint(endpoint.Name)
newDescription := "Updated description"
newBaseURL := "https://ghes2.example.com"
newAPIBaseURL := "https://api.ghes2.example.com/"
newUploadBaseURL := "https://uploads.ghes2.example.com/"
newCABundle := getTestFileContents("certs/srv-pub.pem")
updateParams := params.UpdateGithubEndpointParams{
Description: &newDescription,
BaseURL: &newBaseURL,
APIBaseURL: &newAPIBaseURL,
UploadBaseURL: &newUploadBaseURL,
CACertBundle: newCABundle,
}
updated, err := updateGithubEndpoint(cli, authToken, endpoint.Name, updateParams)
if err != nil {
panic(err)
}
if updated.Name != endpoint.Name {
panic("Endpoint name mismatch")
}
if updated.Description != newDescription {
panic("Endpoint description mismatch")
}
if updated.BaseURL != newBaseURL {
panic("Endpoint base URL mismatch")
}
if updated.APIBaseURL != newAPIBaseURL {
panic("Endpoint API base URL mismatch")
}
if updated.UploadBaseURL != newUploadBaseURL {
panic("Endpoint upload base URL mismatch")
}
if string(updated.CACertBundle) != string(newCABundle) {
panic("Endpoint CA cert bundle mismatch")
}
}
func createDummyEndpoint(name string) *params.GithubEndpoint {
endpointParams := params.CreateGithubEndpointParams{
Name: name,
Description: "Dummy endpoint",
BaseURL: "https://ghes.example.com",
APIBaseURL: "https://api.ghes.example.com/",
UploadBaseURL: "https://uploads.ghes.example.com/",
}
return CreateGithubEndpoint(endpointParams)
}

View file

@ -12,10 +12,11 @@ import (
func waitInstanceStatus(name string, status commonParams.InstanceStatus, runnerStatus params.RunnerStatus, timeout time.Duration) (*params.Instance, error) {
var timeWaited time.Duration // default is 0
var instance *params.Instance
var err error
slog.Info("Waiting for instance to reach desired status", "instance", name, "desired_status", status, "desired_runner_status", runnerStatus)
for timeWaited < timeout {
instance, err := getInstance(cli, authToken, name)
instance, err = getInstance(cli, authToken, name)
if err != nil {
return nil, err
}

View file

@ -2,6 +2,7 @@ package e2e
import (
"encoding/json"
"log"
"log/slog"
)
@ -13,3 +14,20 @@ func printJSONResponse(resp interface{}) error {
slog.Info(string(b))
return nil
}
type apiCodeGetter interface {
IsCode(code int) bool
}
func expectAPIStatusCode(err error, expectedCode int) {
if err == nil {
panic("expected error")
}
apiErr, ok := err.(apiCodeGetter)
if !ok {
log.Fatalf("expected API error, got %v (%T)", err, err)
}
if !apiErr.IsCode(expectedCode) {
log.Fatalf("expected status code %d: %v", expectedCode, err)
}
}

View file

@ -76,6 +76,25 @@ func main() {
e2e.FirstRun(adminUsername, adminPassword, adminFullName, adminEmail)
e2e.Login(adminUsername, adminPassword)
// Test endpoint operations
e2e.TestGithubEndpointOperations()
e2e.TestGithubEndpointFailsOnInvalidCABundle()
e2e.TestGithubEndpointDeletionFailsWhenCredentialsExist()
e2e.TestGithubEndpointFailsOnDuplicateName()
e2e.TestGithubEndpointMustFailToDeleteDefaultGithubEndpoint()
// Create test credentials
e2e.EnsureTestCredentials(credentialsName, ghToken, "github.com")
e2e.TestGithubCredentialsErrorOnDuplicateCredentialsName()
e2e.TestGithubCredentialsFailsToDeleteWhenInUse()
e2e.TestGithubCredentialsFailsOnInvalidAuthType()
e2e.TestGithubCredentialsFailsWhenAuthTypeParamsAreIncorrect()
e2e.TestGithubCredentialsFailsWhenAuthTypeParamsAreMissing()
e2e.TestGithubCredentialsUpdateFailsWhenBothPATAndAppAreSupplied()
e2e.TestGithubCredentialsFailWhenAppKeyIsInvalid()
e2e.TestGithubCredentialsFailWhenEndpointDoesntExist()
e2e.TestGithubCredentialsFailsOnDuplicateName()
// //////////////////
// controller info //
// //////////////////

BIN
testdata/db/v0.1.4/garm.db vendored Normal file

Binary file not shown.

View file

@ -59,7 +59,7 @@ func (g *githubClient) ListEntityHooks(ctx context.Context, opts *github.ListOpt
case params.GithubEntityTypeOrganization:
ret, response, err = g.org.ListHooks(ctx, g.entity.Owner, opts)
default:
return nil, nil, errors.New("invalid entity type")
return nil, nil, fmt.Errorf("invalid entity type: %s", g.entity.EntityType)
}
return ret, response, err
}
@ -435,11 +435,13 @@ func (g *githubClient) GetEntityJITConfig(ctx context.Context, instance string,
return jitConfig, ret.Runner, nil
}
func GithubClient(_ context.Context, entity params.GithubEntity, credsDetails params.GithubCredentials) (common.GithubClient, error) {
if credsDetails.HTTPClient == nil {
return nil, errors.New("http client is nil")
func GithubClient(ctx context.Context, entity params.GithubEntity, credsDetails params.GithubCredentials) (common.GithubClient, error) {
httpClient, err := credsDetails.GetHTTPClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching http client")
}
ghClient, err := github.NewClient(credsDetails.HTTPClient).WithEnterpriseURLs(credsDetails.APIBaseURL, credsDetails.UploadBaseURL)
ghClient, err := github.NewClient(httpClient).WithEnterpriseURLs(credsDetails.APIBaseURL, credsDetails.UploadBaseURL)
if err != nil {
return nil, errors.Wrap(err, "fetching github client")
}