package admin_api import ( "fmt" "net/http" "git.ohea.xyz/cursorius/server/database" "github.com/google/uuid" "github.com/graphql-go/graphql" "github.com/graphql-go/handler" "github.com/op/go-logging" ) var log = logging.MustGetLogger("cursorius-server") func createSchema(db database.Database) (graphql.Schema, error) { credentialType := graphql.NewObject(graphql.ObjectConfig{ Name: "Credential", Description: "A credential for authenticating with the pipeline source host.", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the credential.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if credential, ok := p.Source.(database.Credential); ok { return credential.Id, nil } return nil, nil }, }, "name": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The name of the credential.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if credential, ok := p.Source.(database.Credential); ok { return credential.Name, nil } return nil, nil }, }, "type": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The credential type.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if credential, ok := p.Source.(database.Credential); ok { return credential.Type, nil } return nil, nil }, }, "username": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The username to user with the credential.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if credential, ok := p.Source.(database.Credential); ok { return credential.Username, nil } return nil, nil }, }, "secret": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The secret for the credential.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if credential, ok := p.Source.(database.Credential); ok { return credential.Secret, nil } return nil, nil }, }, }, }) webhookType := graphql.NewObject(graphql.ObjectConfig{ Name: "Webhook", Description: "A webhook for triggering pipelines", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.Id, nil } return nil, nil }, }, "serverType": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The format of the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.ServerType, nil } return nil, nil }, }, "secret": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The secret used to validate the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.Secret, nil } return nil, nil }, }, }, }) runType := graphql.NewObject(graphql.ObjectConfig{ Name: "Run", Description: "A pipeline run", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Id, nil } return nil, nil }, }, "progress": &graphql.Field{ Type: graphql.Boolean, Description: "The progress status of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.InProgress, nil } return nil, nil }, }, "result": &graphql.Field{ // TODO: handle bigint properly here Type: graphql.Float, Description: "The result of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Result, nil } return nil, nil }, }, "stdout": &graphql.Field{ Type: graphql.String, Description: "The stdout used to validate the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Stdout, nil } return nil, nil }, }, "stderr": &graphql.Field{ Type: graphql.String, Description: "The stderr used to validate the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Stderr, nil } return nil, nil }, }, }, }) pipelineType := graphql.NewObject(graphql.ObjectConfig{ Name: "Pipeline", Description: "A pipeline for running ci jobs", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Id, nil } return nil, nil }, }, "name": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The name of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Name, nil } return nil, nil }, }, "url": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The url of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Url, nil } return nil, nil }, }, "pollInterval": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Description: "The polling interval for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.PollInterval, nil } return nil, nil }, }, "credentialId": &graphql.Field{ Type: graphql.String, Description: "The configured credential for cloning the pipeline source.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Credential, nil } return nil, nil }, }, "webhooks": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(webhookType))), Description: "The list of webhooks for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return db.GetWebhooksForPipeline(pipeline.Id) } return []database.Webhook{}, nil }, }, "runs": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(runType))), Description: "The list of runs for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return db.GetRunsForPipeline(pipeline.Id) } return []database.Webhook{}, nil }, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "Pipeline": &graphql.Field{ Type: pipelineType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the requested pipeline.", }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, err := uuid.Parse(p.Args["id"].(string)) if err != nil { return nil, err } return db.GetPipelineById(id) }, }, "Pipelines": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(pipelineType)), Args: graphql.FieldConfigArgument{}, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return db.GetPipelines() }, }, "Credential": &graphql.Field{ Type: credentialType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the requested credential.", }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, err := uuid.Parse(p.Args["id"].(string)) if err != nil { return nil, err } return db.GetCredentialById(id) }, }, "Credentials": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(credentialType)), Args: graphql.FieldConfigArgument{}, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return db.GetCredentials() }, }, }, }) mutationType := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "createPipeline": &graphql.Field{ Type: pipelineType, Description: "Create a new pipeline", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "url": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "pollInterval": &graphql.ArgumentConfig{ Type: graphql.Int, }, "credentialId": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var interval int if intervalVal, ok := params.Args["pollInterval"]; ok { interval = intervalVal.(int) } else { interval = 0 } var credential *uuid.UUID if credentialVal, ok := params.Args["credentialId"]; ok { id, err := uuid.Parse(credentialVal.(string)) if err != nil { return nil, err } credential = &id } else { credential = nil } pipeline, err := db.CreatePipeline( params.Args["name"].(string), params.Args["url"].(string), interval, credential, ) if err != nil { return nil, err } return pipeline, nil }, }, "createWebhook": &graphql.Field{ Type: webhookType, Description: "Create a new webhook", Args: graphql.FieldConfigArgument{ "type": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "pipelineId": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, err := uuid.Parse(params.Args["id"].(string)) if err != nil { return nil, err } webhook, err := db.CreateWebhook( database.WebhookSender(params.Args["type"].(string)), id, ) if err != nil { return nil, err } return webhook, nil }, }, "createCredential": &graphql.Field{ Type: credentialType, Description: "Create a new credential", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "type": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "username": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "secret": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { credential, err := db.CreateCredential( params.Args["name"].(string), database.CredentialType(params.Args["type"].(string)), params.Args["username"].(string), params.Args["secret"].(string), ) if err != nil { return nil, err } return credential, nil }, }, "setPipelineCredential": &graphql.Field{ Type: pipelineType, Description: "Add an credential to a pipeline", Args: graphql.FieldConfigArgument{ "credentialId": &graphql.ArgumentConfig{ Type: graphql.String, }, "pipelineId": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { pipelineId, err := uuid.Parse(params.Args["pipelineId"].(string)) if err != nil { return nil, err } if credentialIdVal, ok := params.Args["credentialId"]; ok { credentialId, err := uuid.Parse(credentialIdVal.(string)) if err != nil { return nil, err } pipeline, err := db.SetPipelineCredential(pipelineId, &credentialId) if err != nil { return nil, err } return pipeline, nil } else { pipeline, err := db.SetPipelineCredential(pipelineId, nil) if err != nil { return nil, err } return pipeline, nil } }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }) if err != nil { return schema, fmt.Errorf("Could not create schema: %w", err) } return schema, nil } func CreateHandler(db database.Database, mux *http.ServeMux) error { schema, err := createSchema(db) if err != nil { return err } h := handler.New(&handler.Config{ Schema: &schema, Pretty: true, GraphiQL: true, }) mux.Handle("/graphql", h) return nil }