diff --git a/admin_api/admin_api.go b/admin_api/admin_api.go index 4ff221f..34ced94 100644 --- a/admin_api/admin_api.go +++ b/admin_api/admin_api.go @@ -14,6 +14,43 @@ import ( var log = logging.MustGetLogger("cursorius-server") func createSchema(db database.Database) (graphql.Schema, error) { + runnerType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Runner", + Description: "A runner available for use inside of a pipeline.", + Fields: graphql.Fields{ + "id": &graphql.Field{ + Type: graphql.NewNonNull(graphql.String), + Description: "The id of the runner.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if runner, ok := p.Source.(database.Runner); ok { + return runner.Id, nil + } + return nil, nil + }, + }, + "name": &graphql.Field{ + Type: graphql.NewNonNull(graphql.String), + Description: "The name of the runner.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if runner, ok := p.Source.(database.Runner); ok { + return runner.Name, nil + } + return nil, nil + }, + }, + "token": &graphql.Field{ + Type: graphql.NewNonNull(graphql.String), + Description: "The token.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if runner, ok := p.Source.(database.Runner); ok { + return runner.Token, nil + } + return nil, nil + }, + }, + }, + }) + secretType := graphql.NewObject(graphql.ObjectConfig{ Name: "Secret", Description: "A secret available for use inside of a pipeline.", @@ -348,6 +385,13 @@ func createSchema(db database.Database) (graphql.Schema, error) { return db.GetSecrets() }, }, + "Runners": &graphql.Field{ + Type: graphql.NewNonNull(graphql.NewList(runnerType)), + Args: graphql.FieldConfigArgument{}, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return db.GetRunners() + }, + }, }, }) @@ -485,6 +529,25 @@ func createSchema(db database.Database) (graphql.Schema, error) { return secret, nil }, }, + "createRunner": &graphql.Field{ + Type: runnerType, + Description: "Create a new runner", + Args: graphql.FieldConfigArgument{ + "name": &graphql.ArgumentConfig{ + Type: graphql.NewNonNull(graphql.String), + }, + }, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + + runner, err := db.CreateRunner( + params.Args["name"].(string), + ) + if err != nil { + return nil, err + } + return runner, nil + }, + }, "setPipelineCloneCredential": &graphql.Field{ Type: pipelineType, Description: "Set the CloneCredential used by a pipeline to clone the source repo", diff --git a/database/db.go b/database/db.go index f484f41..8a3df1a 100644 --- a/database/db.go +++ b/database/db.go @@ -175,8 +175,8 @@ CREATE TABLE command_executions ( CREATE TABLE runners ( id UUID PRIMARY KEY, - name TEXT, - secret TEXT + name TEXT NOT NULL UNIQUE, + token TEXT NOT NULL ); CREATE TABLE pipeline_refs ( diff --git a/database/func.go b/database/func.go index c96c5d7..b196b64 100644 --- a/database/func.go +++ b/database/func.go @@ -531,3 +531,83 @@ WHERE return secrets, nil } + +func (db *Database) GetRunners() ([]Runner, error) { + query := ` +SELECT id, name, token +FROM runners;` + + runners := make([]Runner, 0) + + rows, err := db.Conn.Query(context.Background(), query) + if err != nil { + return runners, fmt.Errorf("Could not query database for runners: %w", err) + } + defer rows.Close() + + for rows.Next() { + var runner Runner + var idStr string + if err := rows.Scan(&idStr, &runner.Name, &runner.Token); err != nil { + return runners, err + } + + runner.Id, err = uuid.Parse(idStr) + if err != nil { + return runners, err + } + runners = append(runners, runner) + } + + return runners, nil +} + +func (db *Database) GetRunnerById(id uuid.UUID) (Runner, error) { + query := ` +SELECT name, token +FROM runners +WHERE id=$1;` + + runner := Runner{ + Id: id, + } + + err := db.Conn.QueryRow(context.Background(), query, id).Scan(nil, &runner.Name, &runner.Token) + if err != nil { + return runner, fmt.Errorf("Could not query database for runner with id %v: %w", id.String(), err) + } + + return runner, nil +} + +func (db *Database) CreateRunner(name string) (Runner, error) { + s := Runner{} + + // validate that the runner name is only A-Z or underscores and less than 256 characters + if len(name) > 256 { + return s, fmt.Errorf("runner name must be 256 characters or less") + } + + validName := regexp.MustCompile(`[A-Z0-9_]+$`) + if !validName.MatchString(name) { + return s, fmt.Errorf("secren name must be made up of only uppercase letters, numbers, and underscores") + } + + query := ` +INSERT INTO runners (id, name, token) +VALUES (uuid_generate_v4(), $1, TODO_GENERATE_STRING) +RETURNING id, name, token;` + + var idStr string + err := db.Conn.QueryRow(context.Background(), query, name).Scan(&idStr, &s.Name, &s.Token) + if err != nil { + return s, fmt.Errorf("Could not create runner: %w", err) + } + + s.Id, err = uuid.Parse(idStr) + if err != nil { + return s, fmt.Errorf("Could not parse UUID generated by DB: %w", err) + } + + return s, nil +} diff --git a/database/types.go b/database/types.go index 1efe900..f4c003b 100644 --- a/database/types.go +++ b/database/types.go @@ -74,7 +74,7 @@ type CommandExecution struct { } type Runner struct { - Id uuid.UUID - Name string - Secret string + Id uuid.UUID + Name string + Token string }