Skip to content

Commit

Permalink
Add Buildkite pipeline resource (#5)
Browse files Browse the repository at this point in the history
This is just a first pass - steps are not configurable
  • Loading branch information
Jarryd Tilbrook authored Jul 19, 2019
1 parent 011ee2a commit 1b092eb
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ provider "buildkite" {
resource "buildkite_agent_token" "token" {
description = "default agent token"
}
# create a pipeline with default upload step
resource "buildkite_pipeline" "repo1" {
name = "repo1"
description = "a repository pipeline"
repository = "[email protected]:org/repo1"
}
```

### Importing existing resources

The following resources provided by this provider are:

- `buildkite_agent_token` using its GraphQL ID (not UUID)
- `buildkite_pipeline` using its GraphQL ID (not UUID)

You can import them using the standard terraform method.

Expand Down
3 changes: 3 additions & 0 deletions buildkite/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package buildkite

import (
"context"
"net/http"

"github.com/shurcooL/graphql"
"golang.org/x/oauth2"
Expand All @@ -11,6 +12,7 @@ const graphqlEndpoint = "https://graphql.buildkite.com/v1"

type Client struct {
graphql *graphql.Client
http *http.Client
organization string
}

Expand All @@ -20,6 +22,7 @@ func NewClient(org, apiToken string) *Client {

return &Client{
graphql: graphql.NewClient(graphqlEndpoint, httpClient),
http: httpClient,
organization: org,
}
}
1 change: 1 addition & 0 deletions buildkite/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ func Provider() terraform.ResourceProvider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"buildkite_agent_token": resourceAgentToken(),
"buildkite_pipeline": resourcePipeline(),
},
Schema: map[string]*schema.Schema{
"organization": &schema.Schema{
Expand Down
2 changes: 0 additions & 2 deletions buildkite/resource_agent_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/shurcooL/graphql"
)

const idSeparator = "|"

type AgentTokenNode struct {
Description graphql.String
Id graphql.String
Expand Down
179 changes: 179 additions & 0 deletions buildkite/resource_pipeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package buildkite

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/shurcooL/graphql"
)

type PipelineNode struct {
DefaultBranch graphql.String
Description graphql.String
Id graphql.String
Name graphql.String
Repository struct {
Url graphql.String
}
Slug graphql.String
Steps struct {
Yaml graphql.String
}
Uuid graphql.String
WebhookURL graphql.String `graphql:"webhookURL"`
}

func resourcePipeline() *schema.Resource {
return &schema.Resource{
Create: CreatePipeline,
Read: ReadPipeline,
Update: UpdatePipeline,
Delete: DeletePipeline,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Required: true,
Type: schema.TypeString,
},
"description": &schema.Schema{
Optional: true,
Type: schema.TypeString,
},
"repository": &schema.Schema{
Required: true,
Type: schema.TypeString,
},
"webhook_url": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
"slug": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
"steps": &schema.Schema{
// TODO: make this an input
Computed: true,
Type: schema.TypeString,
},
},
}
}

func CreatePipeline(d *schema.ResourceData, m interface{}) error {
client := m.(*Client)
id, err := GetOrganizationID(client.organization, client.graphql)
if err != nil {
return err
}

var mutation struct {
PipelineCreate struct {
Pipeline PipelineNode
} `graphql:"pipelineCreate(input: {organizationId: $org, name: $name, description: $desc, repository: {url: $repository_url}, steps: {yaml: $steps}})"`
}

vars := map[string]interface{}{
"desc": graphql.String(d.Get("description").(string)),
"name": graphql.String(d.Get("name").(string)),
"org": id,
"repository_url": graphql.String(d.Get("repository").(string)),
"steps": graphql.String("steps:\n - command: \"buildkite-agent pipeline upload\"\n label: \":pipeline:\""),
}

err = client.graphql.Mutate(context.Background(), &mutation, vars)
if err != nil {
return err
}

updatePipeline(d, &mutation.PipelineCreate.Pipeline)

return nil
}

func ReadPipeline(d *schema.ResourceData, m interface{}) error {
client := m.(*Client)
var query struct {
Node struct {
Pipeline PipelineNode `graphql:"... on Pipeline"`
} `graphql:"node(id: $id)"`
}

vars := map[string]interface{}{
"id": graphql.ID(d.Id()),
}

err := client.graphql.Query(context.Background(), &query, vars)
if err != nil {
return err
}

updatePipeline(d, &query.Node.Pipeline)

return nil
}

func UpdatePipeline(d *schema.ResourceData, m interface{}) error {
client := m.(*Client)
id, err := GetOrganizationID(client.organization, client.graphql)
if err != nil {
return err
}

var mutation struct {
PipelineUpdate struct {
Pipeline PipelineNode
} `graphql:"pipelineUpdate(input: {id: $id, name: $name, description: $desc, repository: {url: $repository_url}, steps: {yaml: $steps}})"`
}

vars := map[string]interface{}{
"desc": graphql.String(d.Get("description").(string)),
"name": graphql.String(d.Get("name").(string)),
"org": graphql.String(id),
"repository_url": graphql.String(d.Get("repository").(string)),
"steps": graphql.String("steps:\n - command: \"buildkite-agent pipeline upload\"\n label: \":pipeline:\""),
}

err = client.graphql.Mutate(context.Background(), &mutation, vars)
if err != nil {
return err
}

updatePipeline(d, &mutation.PipelineUpdate.Pipeline)

return nil
}

func DeletePipeline(d *schema.ResourceData, m interface{}) error {
client := m.(*Client)

// there is no delete mutation in graphql yet so we must use rest api
req, err := http.NewRequest("DELETE", fmt.Sprintf("https://api.buildkite.com/v2/organizations/%s/pipelines/%s",
client.organization, d.Get("slug").(string)), strings.NewReader(""))
if err != nil {
return err
}
resp, err := client.http.Do(req)
if err != nil && resp.StatusCode != 204 {
return err
}

return nil
}

func updatePipeline(d *schema.ResourceData, t *PipelineNode) {
d.SetId(string(t.Id))
d.Set("description", string(t.Description))
d.Set("name", string(t.Name))
d.Set("repository", string(t.Repository.Url))
d.Set("slug", string(t.Slug))
d.Set("steps", string(t.Steps.Yaml))
d.Set("uuid", string(t.Uuid))
d.Set("webhook_url", string(t.WebhookURL))
}

0 comments on commit 1b092eb

Please sign in to comment.