Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77a5514578 | |||
| 77a8d0840a | |||
| 62b4e8f17e | |||
| 620c20f717 | |||
| 0979a2379e |
@@ -14,6 +14,43 @@ import (
|
|||||||
var log = logging.MustGetLogger("cursorius-server")
|
var log = logging.MustGetLogger("cursorius-server")
|
||||||
|
|
||||||
func createSchema(db database.Database) (graphql.Schema, error) {
|
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{
|
secretType := graphql.NewObject(graphql.ObjectConfig{
|
||||||
Name: "Secret",
|
Name: "Secret",
|
||||||
Description: "A secret available for use inside of a pipeline.",
|
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()
|
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
|
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{
|
"setPipelineCloneCredential": &graphql.Field{
|
||||||
Type: pipelineType,
|
Type: pipelineType,
|
||||||
Description: "Set the CloneCredential used by a pipeline to clone the source repo",
|
Description: "Set the CloneCredential used by a pipeline to clone the source repo",
|
||||||
|
|||||||
+2
-2
@@ -175,8 +175,8 @@ CREATE TABLE command_executions (
|
|||||||
|
|
||||||
CREATE TABLE runners (
|
CREATE TABLE runners (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
name TEXT,
|
name TEXT NOT NULL UNIQUE,
|
||||||
secret TEXT
|
token TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE pipeline_refs (
|
CREATE TABLE pipeline_refs (
|
||||||
|
|||||||
+93
-17
@@ -6,7 +6,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *Database) GetPipelines() ([]Pipeline, error) {
|
func (db *Database) GetPipelines() ([]Pipeline, error) {
|
||||||
@@ -301,11 +300,11 @@ func (db *Database) UpdateRunResult(r Run) error {
|
|||||||
query := `
|
query := `
|
||||||
UPDATE runs
|
UPDATE runs
|
||||||
SET in_progress=$1, result=$2, stdout=$3, stderr=$4
|
SET in_progress=$1, result=$2, stdout=$3, stderr=$4
|
||||||
WHERE id=$3;`
|
WHERE id=$5;`
|
||||||
|
|
||||||
// TODO: does r.Result need a pointer derefrence?
|
// TODO: does r.Result need a pointer derefrence?
|
||||||
_, err := db.Conn.Exec(context.Background(),
|
_, err := db.Conn.Exec(context.Background(),
|
||||||
query, r.InProgress, r.Result, r.Stdout, r.Stderr)
|
query, r.InProgress, r.Result, r.Stdout, r.Stderr, r.Id)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -379,21 +378,18 @@ WHERE pipeline_id=$1;`
|
|||||||
|
|
||||||
func (db *Database) UpdatePipelineRefs(pipelineId uuid.UUID, refsMap map[string]string) error {
|
func (db *Database) UpdatePipelineRefs(pipelineId uuid.UUID, refsMap map[string]string) error {
|
||||||
|
|
||||||
refsSlice := make([][]interface{}, 0)
|
query := `
|
||||||
for name, ref := range refsMap {
|
INSERT INTO pipeline_refs(name, pipeline_id, hash)
|
||||||
refsSlice = append(refsSlice, []interface{}{name, pipelineId, ref})
|
VALUES($1, $2, $3)
|
||||||
}
|
ON CONFLICT (name)
|
||||||
|
DO
|
||||||
|
UPDATE SET hash=$3;`
|
||||||
|
|
||||||
copyCount, err := db.Conn.CopyFrom(
|
for name, hash := range refsMap {
|
||||||
context.Background(),
|
_, err := db.Conn.Exec(context.Background(), query, name, pipelineId, hash)
|
||||||
pgx.Identifier{"pipeline_refs"},
|
|
||||||
[]string{"name", "pipeline_id", "hash"},
|
return err
|
||||||
pgx.CopyFromRows(refsSlice),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not insert updated pipeline refs: %w", err)
|
|
||||||
}
|
}
|
||||||
log.Debugf("copyCount: %v", copyCount)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +449,7 @@ func (db *Database) CreateSecret(name string, secret string) (Secret, error) {
|
|||||||
return s, fmt.Errorf("secret name must be 256 characters or less")
|
return s, fmt.Errorf("secret name must be 256 characters or less")
|
||||||
}
|
}
|
||||||
|
|
||||||
validName := regexp.MustCompile(`^[A-Z0-9_]+$`)
|
validName := regexp.MustCompile(`[A-Z0-9_]+$`)
|
||||||
if !validName.MatchString(name) {
|
if !validName.MatchString(name) {
|
||||||
return s, fmt.Errorf("secren name must be made up of only uppercase letters, numbers, and underscores")
|
return s, fmt.Errorf("secren name must be made up of only uppercase letters, numbers, and underscores")
|
||||||
}
|
}
|
||||||
@@ -531,3 +527,83 @@ WHERE
|
|||||||
|
|
||||||
return secrets, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -74,7 +74,7 @@ type CommandExecution struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
Id uuid.UUID
|
Id uuid.UUID
|
||||||
Name string
|
Name string
|
||||||
Secret string
|
Token string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ require (
|
|||||||
github.com/graphql-go/graphql v0.8.0
|
github.com/graphql-go/graphql v0.8.0
|
||||||
github.com/graphql-go/handler v0.2.3
|
github.com/graphql-go/handler v0.2.3
|
||||||
github.com/jackc/pgx/v5 v5.2.0
|
github.com/jackc/pgx/v5 v5.2.0
|
||||||
|
github.com/jhoonb/archivex v0.0.0-20201016144719-6a343cdae81d
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
golang.org/x/net v0.2.0
|
golang.org/x/net v0.2.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
|
|||||||
@@ -858,6 +858,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
|||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
|
github.com/jhoonb/archivex v0.0.0-20201016144719-6a343cdae81d h1:q7n+5taxmM+9T2Q7Ydo7YN90FkoDuR5bbzByZwkQqPo=
|
||||||
|
github.com/jhoonb/archivex v0.0.0-20201016144719-6a343cdae81d/go.mod h1:GN1Mg/uXQ6qwXA0HypnUO3xlcQJS9/y68EsHNeuuRa4=
|
||||||
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
|
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
|
||||||
github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jhoonb/archivex"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
@@ -34,6 +37,7 @@ type PipelineExecution struct {
|
|||||||
|
|
||||||
func ExecutePipeline(pe PipelineExecution, db database.Database, pipelineConf config.PipelineConf) {
|
func ExecutePipeline(pe PipelineExecution, db database.Database, pipelineConf config.PipelineConf) {
|
||||||
jobFolder := filepath.Join(pipelineConf.WorkingDir, pe.Pipeline.Id.String(), pe.Run.Id.String())
|
jobFolder := filepath.Join(pipelineConf.WorkingDir, pe.Pipeline.Id.String(), pe.Run.Id.String())
|
||||||
|
cloneFolder := filepath.Join(jobFolder, "repo")
|
||||||
|
|
||||||
log.Debugf("Job %v configured with URL \"%v\"", pe.Pipeline.Name, pe.Pipeline.Url)
|
log.Debugf("Job %v configured with URL \"%v\"", pe.Pipeline.Name, pe.Pipeline.Url)
|
||||||
|
|
||||||
@@ -45,7 +49,7 @@ func ExecutePipeline(pe PipelineExecution, db database.Database, pipelineConf co
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(jobFolder, 0755)
|
err = os.MkdirAll(cloneFolder, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not create working directory for job %v: %w", pe.Pipeline.Name, err)
|
log.Errorf("could not create working directory for job %v: %w", pe.Pipeline.Name, err)
|
||||||
return
|
return
|
||||||
@@ -84,7 +88,7 @@ func ExecutePipeline(pe PipelineExecution, db database.Database, pipelineConf co
|
|||||||
auth = nil
|
auth = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = git.PlainClone(jobFolder, false, &git.CloneOptions{
|
_, err = git.PlainClone(cloneFolder, false, &git.CloneOptions{
|
||||||
URL: pe.Pipeline.Url,
|
URL: pe.Pipeline.Url,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
})
|
})
|
||||||
@@ -102,6 +106,49 @@ func ExecutePipeline(pe PipelineExecution, db database.Database, pipelineConf co
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
log.Info("Building container")
|
||||||
|
|
||||||
|
tarFile := filepath.Join(jobFolder, "archive.tar")
|
||||||
|
tar := new(archivex.TarFile)
|
||||||
|
err = tar.Create(tarFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could not create tarfile: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tar.AddAll(cloneFolder, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could not add repo to tarfile: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tar.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could not close tarfile: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerBuildContext, err := os.Open(tarFile)
|
||||||
|
defer dockerBuildContext.Close()
|
||||||
|
|
||||||
|
buildResponse, err := cli.ImageBuild(context.Background(), dockerBuildContext, types.ImageBuildOptions{
|
||||||
|
Dockerfile: ".cursorius/Dockerfile",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could not build container: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("********* %s **********", buildResponse.OSType)
|
||||||
|
response, err := ioutil.ReadAll(buildResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could no read build response: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("build log: %v", string(response))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
imageName := "git.ohea.xyz/cursorius/pipeline-api/cursorius-pipeline:v2"
|
imageName := "git.ohea.xyz/cursorius/pipeline-api/cursorius-pipeline:v2"
|
||||||
|
|
||||||
log.Infof("Pulling image %v", imageName)
|
log.Infof("Pulling image %v", imageName)
|
||||||
|
|||||||
+3
-3
@@ -28,15 +28,15 @@ type tag struct {
|
|||||||
|
|
||||||
func pollJob(pipeline database.Pipeline, pipelineConf config.PipelineConf, db database.Database) {
|
func pollJob(pipeline database.Pipeline, pipelineConf config.PipelineConf, db database.Database) {
|
||||||
for {
|
for {
|
||||||
|
time.Sleep(time.Duration(pipeline.PollInterval) * time.Second)
|
||||||
|
log.Infof("Polling repo %v", pipeline.Name)
|
||||||
|
|
||||||
prevRefs, err := db.GetPipelineRefs(pipeline.Id)
|
prevRefs, err := db.GetPipelineRefs(pipeline.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Could not get pipeline refs from db: %v", err)
|
log.Errorf("Could not get pipeline refs from db: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Duration(pipeline.PollInterval) * time.Second)
|
|
||||||
log.Infof("Polling repo %v", pipeline.Name)
|
|
||||||
|
|
||||||
repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
||||||
URL: pipeline.Url,
|
URL: pipeline.Url,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user