diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..106be32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +## 1.0.0 (Unreleased) + +NOTES: +First official release! + +BREAKING CHANGES: + +N/A + +FEATURES: + +* **New Resource:** `buildkite_pipeline` +* **New Resource:** `buildkite_pipeline_schedule` +* **New Resource:** `buildkite_team` +* **New Resource:** `buildkite_team_pipeline` +* **New Resource:** `buildkite_team_member` +* **New Data Source:** `buildkite_user` + +IMPROVEMENTS: + +N/A + +BUG FIXES: + +N/A diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a87222 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2020 samsara-dev + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8d69d87 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +BINARY_NAME=terraform-provider-buildkite +VERSION=1.0.0 +BIN_PATH ?= ./bin + +.PHONY: build +build: + @GOOS=darwin GOARCH=amd64 go build -o ${BIN_PATH}/${BINARY_NAME}_v${VERSION}_darwin_amd64 + @GOOS=linux GOARCH=amd64 go build -o ${BIN_PATH}/${BINARY_NAME}_v${VERSION}_linux_amd64 + +.PHONY: test +test: + @go test -v ./... + +.PHONY: testacc +testacc: + @TF_ACC=1 go test -v ./... + +.PHONY: clean +clean: + @rm -rf ${BIN_PATH} diff --git a/README.md b/README.md new file mode 100644 index 0000000..4172e4f --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +[![Build status](https://badge.buildkite.com/ba2febb05f89921c3824ce22bd94d47310dcd481186151de21.svg?branch=main)](https://buildkite.com/samsara/hq-terraform-provider-buildkite) +# Terraform Provider for [Buildkite](https://buildkite.com) + +Note: This provider is built for Terraform 0.11 and all docs/examples reflect this. +## Documentation +Documentation for this provider is located in `/docs` with the templates generated according to [Terraform Guidelines](https://www.terraform.io/docs/registry/providers/docs.html#generating-documentation). + +## Development +To build binaries run +``` +make build +``` +which will output the binaries to `./bin` in the format `terraform-provider-buildkite_v0.1.0_${OS}_${ARCH}`. + +### Testing +The tests for this provider create and delete *real* resources in a given Buildkite account specified by `BUILDKITE_ORGANIZATION_SLUG` and `BUILDKITE_TOKEN`. A real user already registered in the organization is also required and must be specified via `BUILDKITE_USER_EMAIL`. + + +Integration tests for just the Buildkite client can be run via: +``` +make test +``` +while full [Terraform Acceptance Tests](https://www.terraform.io/docs/extend/testing/acceptance-tests/index.html) are run via: +``` +make testacc +``` diff --git a/buildkite/client/client.go b/buildkite/client/client.go new file mode 100644 index 0000000..ec7b634 --- /dev/null +++ b/buildkite/client/client.go @@ -0,0 +1,90 @@ +package client + +import ( + "context" + "errors" + "fmt" + "net/http" + + buildkiteRest "github.com/buildkite/go-buildkite/v2/buildkite" + "github.com/shurcooL/graphql" +) + +// Base URLs for Buildkite API +const ( + RESTBaseURL = "https://api.buildkite.com/v2" + GQLBaseURL = "https://graphql.buildkite.com/v1" +) + +// Client encapsulates the REST and GQL client for a given org. +type Client struct { + // orgSlug is the slug of the org + orgSlug string + // orgID is the gql ID for the org + orgID string + + httpClient *http.Client + restClient *buildkiteRest.Client + gqlClient *graphql.Client +} + +// We need this transport to add the authorization headers to our requests. +// We can't use `buildkite.NewTokenConfig` because that transport does not work +// for GQL requests. +type tokenTransport struct { + token string +} + +func (t *tokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.token)) + return http.DefaultTransport.RoundTrip(req) +} + +// NewClient returns a new buildkite client based on the given org and token. +// It will return an error if the token is invalid. +func NewClient(org, token string) (*Client, error) { + httpClient := &http.Client{ + Transport: &tokenTransport{token: token}, + } + restCli := buildkiteRest.NewClient(httpClient) + gqlCli := graphql.NewClient(GQLBaseURL, httpClient) + c := &Client{ + orgSlug: org, + httpClient: httpClient, + restClient: restCli, + gqlClient: gqlCli, + } + if err := c.CheckAuth(); err != nil { + return nil, fmt.Errorf("checking auth: %w", err) + } + + // get org id + var query struct { + Organization struct { + ID string `graphql:"id"` + } `graphql:"organization(slug: $slug)"` + } + vars := map[string]interface{}{ + "slug": org, + } + err := c.gqlClient.Query(context.TODO(), &query, vars) + if err != nil { + return nil, fmt.Errorf("getting org id: %w", err) + } + c.orgID = query.Organization.ID + return c, nil +} + +// CheckAuth validates the client's token against the access token endpoint and +// returns whether the token appears valid based on this request. +func (c *Client) CheckAuth() error { + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/access-token", RESTBaseURL), nil) + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return errors.New("non 200 status") + } + return nil +} diff --git a/buildkite/client/client_test.go b/buildkite/client/client_test.go new file mode 100644 index 0000000..e224ef4 --- /dev/null +++ b/buildkite/client/client_test.go @@ -0,0 +1,62 @@ +package client + +import ( + "fmt" + "net/http" + "os" + "testing" +) + +const ( + orgEnvVar = "BUILDKITE_ORGANIZATION_SLUG" + tokenEnvVar = "BUILDKITE_TOKEN" + userEnvVar = "BUILDKITE_USER_EMAIL" +) + +var ( + cli *Client + userEmail string + userID string +) + +func init() { + requiredVars := []string{orgEnvVar, tokenEnvVar, userEnvVar} + for _, v := range requiredVars { + if val := os.Getenv(v); val == "" { + panic(fmt.Sprintf("Required env var %s is not set", v)) + } + } + + c, err := NewClient(os.Getenv(orgEnvVar), os.Getenv(tokenEnvVar)) + if err != nil { + panic("Couldn't create client") + } + cli = c + userEmail = os.Getenv(userEnvVar) + u, err := cli.GetUser(userEmail) + if err != nil { + panic("Couldn't get user") + } + userID = string(u.ID) +} + +func TestCheckAuth(t *testing.T) { + c := &Client{ + httpClient: &http.Client{ + Transport: &tokenTransport{token: "wontwork"}, + }, + } + if err := c.CheckAuth(); err == nil { + t.Error("Invalid token still passed auth") + } + + token := os.Getenv("BUILDKITE_TOKEN") + c = &Client{ + httpClient: &http.Client{ + Transport: &tokenTransport{token: token}, + }, + } + if err := c.CheckAuth(); err != nil { + t.Errorf("Auth should have passed but failed, a valid token must be set at BUILDKITE_TOKEN") + } +} diff --git a/buildkite/client/pipeline.go b/buildkite/client/pipeline.go new file mode 100644 index 0000000..669fd7d --- /dev/null +++ b/buildkite/client/pipeline.go @@ -0,0 +1,96 @@ +package client + +import ( + "context" + "fmt" + + buildkiteRest "github.com/buildkite/go-buildkite/v2/buildkite" + "github.com/shurcooL/graphql" +) + +// Pipeline represents a pipeline in Buildkite. +type Pipeline = buildkiteRest.Pipeline + +// GetPipelineID returns the gql ID for a given pipeline specified by its slug. +func (c *Client) GetPipelineID(slug string) (string, error) { + var query struct { + Pipeline struct { + ID graphql.String `graphql:"id"` + } `graphql:"pipeline(slug: $slug)"` + } + vars := map[string]interface{}{ + "slug": fmt.Sprintf("%s/%s", c.orgSlug, slug), + } + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return "", err + } + return string(query.Pipeline.ID), nil +} + +func (c *Client) CreatePipeline(pipeline *Pipeline) error { + safeString := func(s *string) string { + if s == nil { + return "" + } + return *s + } + safeBool := func(b *bool) bool { + if b == nil { + return false + } + return *b + } + var provider buildkiteRest.ProviderSettings + if pipeline.Provider != nil { + provider = pipeline.Provider.Settings + } + payload := &buildkiteRest.CreatePipeline{ + // Steps are managed through the configuration field as a yaml string. + Steps: nil, + // Env is also part of the yaml steps. + Env: nil, + // Teams are associated through TeamPipelines rather than specifying them solely on + // Pipeline create. + TeamUuids: nil, + + Name: safeString(pipeline.Name), + Repository: safeString(pipeline.Repository), + DefaultBranch: safeString(pipeline.DefaultBranch), + Description: safeString(pipeline.Description), + BranchConfiguration: safeString(pipeline.BranchConfiguration), + SkipQueuedBranchBuilds: safeBool(pipeline.SkipQueuedBranchBuilds), + SkipQueuedBranchBuildsFilter: safeString(pipeline.SkipQueuedBranchBuildsFilter), + CancelRunningBranchBuilds: safeBool(pipeline.CancelRunningBranchBuilds), + CancelRunningBranchBuildsFilter: safeString(pipeline.CancelRunningBranchBuildsFilter), + Configuration: pipeline.Configuration, + + ProviderSettings: provider, + } + p, _, err := c.restClient.Pipelines.Create(c.orgSlug, payload) + if err != nil { + return err + } + if p.ID == nil { + return fmt.Errorf("nil ID for pipeline: %s", payload.Name) + } + pipeline.Slug = p.Slug + return nil +} + +func (c *Client) ReadPipeline(slug string) (*Pipeline, error) { + p, _, err := c.restClient.Pipelines.Get(c.orgSlug, slug) + if err != nil { + return nil, err + } + return p, nil +} + +func (c *Client) UpdatePipeline(pipeline *Pipeline) error { + _, err := c.restClient.Pipelines.Update(c.orgSlug, pipeline) + return err +} + +func (c *Client) DeletePipeline(pipeline *Pipeline) error { + _, err := c.restClient.Pipelines.Delete(c.orgSlug, *pipeline.Slug) + return err +} diff --git a/buildkite/client/pipeline_schedule.go b/buildkite/client/pipeline_schedule.go new file mode 100644 index 0000000..d900f8d --- /dev/null +++ b/buildkite/client/pipeline_schedule.go @@ -0,0 +1,168 @@ +package client + +import ( + "context" + "strings" + + "github.com/shurcooL/graphql" +) + +// PipelineSchedule represents a schedule of builds to run on a Pipeline. +type PipelineSchedule struct { + // https://buildkite.com/docs/pipelines/scheduled-builds#schedule-intervals + Cronline graphql.String + // Env is a slice of strings of key-value pairs in the form KEY=value. + Env []string + Enabled graphql.Boolean + Message graphql.String + Branch graphql.String + Commit graphql.String + Label graphql.String + ID graphql.String + // Pipeline is the parent that the pipelineschedule belongs to. + Pipeline struct { + ID graphql.String + } +} + +// ReadPipelineSchedules looks up all schedules for a given Pipeline via the Pipeline's Graphql ID. +func (c *Client) ReadPipelineSchedules(pipelineID string) ([]PipelineSchedule, error) { + type Pipeline struct { + Schedules struct { + Edges []struct { + Node PipelineSchedule + } + } + } + var query struct { + Node struct { + Pipeline Pipeline `graphql:"... on Pipeline"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": pipelineID, + } + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + + var result []PipelineSchedule + for _, edge := range query.Node.Pipeline.Schedules.Edges { + result = append(result, edge.Node) + } + return result, nil +} + +// ReadPipelineSchedule looks up a PipelineSchedule by given Graphql ID. +func (c *Client) ReadPipelineSchedule(id string) (*PipelineSchedule, error) { + var query struct { + Node struct { + PipelineSchedule PipelineSchedule `graphql:"... on PipelineSchedule"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": id, + } + + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + return &query.Node.PipelineSchedule, nil +} + +// CreatePipelineSchedule creates the provided PipelineSchedule. +func (c *Client) CreatePipelineSchedule(ps *PipelineSchedule) error { + var mutation struct { + PipelineScheduleCreate struct { + PipelineScheduleEdge struct { + Node PipelineSchedule + } + } `graphql:"pipelineScheduleCreate(input: $input)"` + } + + type PipelineScheduleCreateInput struct { + PipelineID string `json:"pipelineID"` + Label string `json:"label"` + CronLine string `json:"cronline"` + Message string `json:"message"` + Commit string `json:"commit"` + Branch string `json:"branch"` + Enabled bool `json:"enabled"` + Env string `json:"env"` + } + vars := map[string]interface{}{ + "input": PipelineScheduleCreateInput{ + PipelineID: string(ps.Pipeline.ID), + Label: string(ps.Label), + CronLine: string(ps.Cronline), + Message: string(ps.Message), + Commit: string(ps.Commit), + Branch: string(ps.Branch), + Enabled: bool(ps.Enabled), + Env: strings.Join(ps.Env, "\n"), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + + ps.ID = mutation.PipelineScheduleCreate.PipelineScheduleEdge.Node.ID + return nil +} + +// UpdatePipelineSchedule updates the provided PipelineSchedule. +func (c *Client) UpdatePipelineSchedule(ps *PipelineSchedule) error { + var mutation struct { + PipelineScheduleUpdate struct { + PipelineSchedule PipelineSchedule + } `graphql:"pipelineScheduleUpdate(input: $input)"` + } + type PipelineScheduleUpdateInput struct { + ID string `json:"id"` + Label string `json:"label"` + CronLine string `json:"cronline"` + Message string `json:"message"` + Commit string `json:"commit"` + Branch string `json:"branch"` + Enabled bool `json:"enabled"` + Env string `json:"env"` + } + vars := map[string]interface{}{ + "input": PipelineScheduleUpdateInput{ + ID: string(ps.ID), + Label: string(ps.Label), + CronLine: string(ps.Cronline), + Message: string(ps.Message), + Commit: string(ps.Commit), + Branch: string(ps.Branch), + Enabled: bool(ps.Enabled), + Env: strings.Join(ps.Env, "\n"), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + + *ps = mutation.PipelineScheduleUpdate.PipelineSchedule + return nil +} + +// DeletePipelineSchedule deletes the provided PipelineSchedule. +func (c *Client) DeletePipelineSchedule(ps *PipelineSchedule) error { + var mutation struct { + PipelineScheduleDelete struct { + DeletedPipelineScheduleID graphql.String `graphql:"deletedPipelineScheduleID"` + } `graphql:"pipelineScheduleDelete(input: $input)"` + } + type PipelineScheduleDeleteInput struct { + ID string `json:"id"` + } + vars := map[string]interface{}{ + "input": PipelineScheduleDeleteInput{ + ID: string(ps.ID), + }, + } + return c.gqlClient.Mutate(context.TODO(), &mutation, vars) +} diff --git a/buildkite/client/pipeline_schedule_test.go b/buildkite/client/pipeline_schedule_test.go new file mode 100644 index 0000000..bf16729 --- /dev/null +++ b/buildkite/client/pipeline_schedule_test.go @@ -0,0 +1,110 @@ +package client + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + + "github.com/shurcooL/graphql" +) + +func setupPipeline() (*Pipeline, error) { + // Use a random suffix to avoid collisions. + name := fmt.Sprintf("test-pipeline-%d", rand.Int31n(10000)) + p := &Pipeline{ + Name: strPtr(name), + Repository: strPtr(repoName), + Description: strPtr("integration tests"), + Configuration: ` +env: + TEST: true +steps: +- command: echo "hello" +`, + DefaultBranch: strPtr("master"), + } + + if err := cli.CreatePipeline(p); err != nil { + return p, err + } + return cli.ReadPipeline(name) +} + +func TestPipelineScheduleCRUD(t *testing.T) { + p, err := setupPipeline() + if err != nil { + t.Errorf("Couldn't setup pipeline %s", err) + } + defer cli.DeletePipeline(p) + + testPipelineID, err := cli.GetPipelineID(*p.Slug) + if err != nil { + t.Errorf("Couldn't get pipeline ID %s", err) + } + testPipelineSlug := *p.Slug + + // Test pre-condition. + schedules, err := cli.ReadPipelineSchedules(testPipelineID) + if err != nil { + t.Errorf("Couldn't check PipelineSchedules for %s!", testPipelineSlug) + } + if len(schedules) != 0 { + t.Errorf("Test pre-condition failed - %s should contain no schedules at test start.", testPipelineSlug) + } + + ps := &PipelineSchedule{ + Branch: graphql.String("master"), + Commit: graphql.String("HEAD"), + Cronline: graphql.String("0 0 1 1 *"), + Enabled: graphql.Boolean(false), + Env: []string{"KEY1=val1", "KEY2=val2"}, + Label: graphql.String("Test label"), + Message: graphql.String("Test message"), + Pipeline: struct { + ID graphql.String + }{ + ID: graphql.String(testPipelineID), + }, + } + + // Test Create. + err = cli.CreatePipelineSchedule(ps) + if err != nil { + t.Errorf("Couldn't create PipelineSchedule: %s", err) + } + if ps.ID == "" { + t.Errorf("Expected ID to be populated.") + } + + // Test Update. + ps.Enabled = graphql.Boolean(true) + err = cli.UpdatePipelineSchedule(ps) + if err != nil { + t.Errorf("Couldn't update PipelineSchedule: %s", err) + } + + // Test Read. + updatedPipelineSchedule, err := cli.ReadPipelineSchedule(string(ps.ID)) + if err != nil { + t.Errorf("Couldn't read PipelineSchedule: %s", err) + } + if !reflect.DeepEqual(ps, updatedPipelineSchedule) { + t.Errorf("Updated PipelineSchedule did not match expected PipelineSchedule") + } + + // Test Delete. + err = cli.DeletePipelineSchedule(updatedPipelineSchedule) + if err != nil { + t.Errorf("Could not delete PipelineSchedule: %s", err) + } + + // Make sure its gone. + schedules, err = cli.ReadPipelineSchedules(testPipelineID) + if err != nil { + t.Errorf("Could not read PipelineSchedules: %s", err) + } + if len(schedules) != 0 { + t.Errorf("Expected 0 schedules for pipeline, but found %v", len(schedules)) + } +} diff --git a/buildkite/client/pipeline_test.go b/buildkite/client/pipeline_test.go new file mode 100644 index 0000000..90902e9 --- /dev/null +++ b/buildkite/client/pipeline_test.go @@ -0,0 +1,79 @@ +package client + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/likexian/gokit/assert" +) + +const ( + repoName = "git@github.com:samsara-dev/terraform-provider-buildkite.git" +) + +func strPtr(s interface{}) *string { + if s == nil { + return strPtr("") + } + str, ok := s.(string) + if !ok { + return strPtr("") + } + return &str +} + +func TestPipelineCRUD(t *testing.T) { + name := fmt.Sprintf("test-pipeline-%d", rand.Int31n(10000)) + p := &Pipeline{ + Name: strPtr(name), + Repository: strPtr(repoName), + Description: strPtr("integration tests"), + Configuration: ` +env: + TEST: true +steps: +- command: echo "hello" +`, + DefaultBranch: strPtr("master"), + } + + // Test create. + if err := cli.CreatePipeline(p); err != nil { + t.Errorf("Could not create pipeline: %s", err) + } + + p, err := cli.ReadPipeline(*p.Name) + if err != nil { + t.Errorf("Could not read pipeline: %s", err) + } + if p.ID == nil || *p.ID == "" { + t.Errorf("Pipeline ID was blank") + } + + // Test getting gql ID. + id, err := cli.GetPipelineID(*p.Slug) + if err != nil { + t.Errorf("Could not get pipeline id: %s", err) + } + if id == "" { + t.Errorf("Pipeline ID was blank") + } + + // Test update. + p.DefaultBranch = strPtr("notmaster") + if err := cli.UpdatePipeline(p); err != nil { + t.Errorf("Could not update pipeline: %s", err) + } + + updatedP, err := cli.ReadPipeline(*p.Name) + if err != nil { + t.Errorf("Could not read pipeline: %s", err) + } + assert.Equal(t, p, updatedP) + + // Test delete. + if err := cli.DeletePipeline(p); err != nil { + t.Errorf("Could not delete pipeline: %s", err) + } +} diff --git a/buildkite/client/team.go b/buildkite/client/team.go new file mode 100644 index 0000000..56073f9 --- /dev/null +++ b/buildkite/client/team.go @@ -0,0 +1,130 @@ +package client + +import ( + "context" + "fmt" + + "github.com/shurcooL/graphql" +) + +// Team represents a Buildkite team. +type Team struct { + ID graphql.String + Name graphql.String + Privacy graphql.String + IsDefaultTeam graphql.Boolean + DefaultMemberRole graphql.String +} + +// CreateTeam creates a given team and if successful, adds an ID to the given team. +func (c *Client) CreateTeam(team *Team) error { + var mutation struct { + TeamCreate struct { + TeamEdge struct { + Node Team + } + } `graphql:"teamCreate(input: $input)"` + } + type TeamCreateInput struct { + OrganizationID string `json:"organizationID"` + Name string `json:"name"` + Privacy string `json:"privacy"` + IsDefaultTeam bool `json:"isDefaultTeam"` + DefaultMemberRole string `json:"defaultMemberRole"` + } + vars := map[string]interface{}{ + "input": TeamCreateInput{ + OrganizationID: c.orgID, + Name: string(team.Name), + Privacy: string(team.Privacy), + IsDefaultTeam: bool(team.IsDefaultTeam), + DefaultMemberRole: string(team.DefaultMemberRole), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + team.ID = mutation.TeamCreate.TeamEdge.Node.ID + return nil +} + +// ReadTeamByName uses the human readable name of the team to query for the team struct. +func (c *Client) ReadTeamByName(name string) (*Team, error) { + var query struct { + Team Team `graphql:"team(slug: $slug)"` + } + vars := map[string]interface{}{ + "slug": fmt.Sprintf("%s/%s", c.orgSlug, name), + } + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + return &query.Team, nil +} + +// ReadTeam returns a team for a given gql ID. +func (c *Client) ReadTeam(id string) (*Team, error) { + var query struct { + Node struct { + Team Team `graphql:"... on Team"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": id, + } + + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + return &query.Node.Team, nil +} + +// UpdateTeam syncs the local team struct with Buildkite. +func (c *Client) UpdateTeam(team *Team) error { + var mutation struct { + TeamUpdate struct { + Team Team + } `graphql:"teamUpdate(input: $input)"` + } + type TeamUpdateInput struct { + ID string `json:"id"` + Name string `json:"name"` + Privacy string `json:"privacy"` + IsDefaultTeam bool `json:"isDefaultTeam"` + DefaultMemberRole string `json:"defaultMemberRole"` + } + vars := map[string]interface{}{ + "input": TeamUpdateInput{ + ID: string(team.ID), + Name: string(team.Name), + Privacy: string(team.Privacy), + IsDefaultTeam: bool(team.IsDefaultTeam), + DefaultMemberRole: string(team.DefaultMemberRole), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + *team = mutation.TeamUpdate.Team + return nil +} + +// DeleteTeam deletes the given team based on the ID field. +func (c *Client) DeleteTeam(team *Team) error { + var mutation struct { + TeamDelete struct { + DeletedTeamID graphql.String `graphql:"deletedTeamID"` + } `graphql:"teamDelete(input: $input)"` + } + type TeamDeleteInput struct { + ID string `json:"id"` + } + vars := map[string]interface{}{ + "input": TeamDeleteInput{ + ID: string(team.ID), + }, + } + return c.gqlClient.Mutate(context.TODO(), &mutation, vars) +} diff --git a/buildkite/client/team_member.go b/buildkite/client/team_member.go new file mode 100644 index 0000000..b552f48 --- /dev/null +++ b/buildkite/client/team_member.go @@ -0,0 +1,85 @@ +package client + +import ( + "context" + + "github.com/shurcooL/graphql" +) + +// TeamMember represents a user's membership with a team. +type TeamMember struct { + ID graphql.String `graphql:"id"` + UserID graphql.String `graphql:"userID"` + TeamID graphql.String `graphql:"teamID"` +} + +func (c *Client) CreateTeamMember(member *TeamMember) error { + var mutation struct { + TeamMemberCreate struct { + TeamMemberEdge struct { + Node struct { + ID graphql.String `graphql:"id"` + } `graphql:"node"` + } `graphql:"teamMemberEdge` + } `graphql:"teamMemberCreate(input: $input)"` + } + type TeamMemberCreateInput struct { + UserID string `json:"userID"` + TeamID string `json:"teamID"` + } + vars := map[string]interface{}{ + "input": TeamMemberCreateInput{ + UserID: string(member.UserID), + TeamID: string(member.TeamID), + }, + } + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + member.ID = mutation.TeamMemberCreate.TeamMemberEdge.Node.ID + return nil +} + +func (c *Client) ReadTeamMember(id string) (*TeamMember, error) { + var query struct { + Node struct { + TeamMember struct { + User struct { + ID graphql.String `graphql:"id"` + } `graphql:"user"` + Team struct { + ID graphql.String `graphql:"id"` + } `graphql:"team"` + } `graphql:"... on TeamMember"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": id, + } + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + member := &TeamMember{ + ID: graphql.String(id), + UserID: query.Node.TeamMember.User.ID, + TeamID: query.Node.TeamMember.Team.ID, + } + return member, nil +} + +func (c *Client) DeleteTeamMember(member *TeamMember) error { + var mutation struct { + TeamMemberDelete struct { + DeletedTeamMemberID graphql.String `graphql:"deletedTeamMemberID"` + } `graphql:"teamMemberDelete(input: $input)"` + } + type TeamMemberDeleteInput struct { + ID string `json:"id"` + } + vars := map[string]interface{}{ + "input": TeamMemberDeleteInput{ + ID: string(member.ID), + }, + } + return c.gqlClient.Mutate(context.TODO(), &mutation, vars) +} diff --git a/buildkite/client/team_member_test.go b/buildkite/client/team_member_test.go new file mode 100644 index 0000000..52927eb --- /dev/null +++ b/buildkite/client/team_member_test.go @@ -0,0 +1,55 @@ +package client + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/likexian/gokit/assert" + "github.com/shurcooL/graphql" +) + +func setupTeam() (*Team, error) { + name := fmt.Sprintf("test-pipeline-%d", rand.Int31n(10000)) + team := &Team{ + Name: graphql.String(name), + Privacy: "VISIBLE", + IsDefaultTeam: false, + DefaultMemberRole: "MAINTAINER", + } + return team, cli.CreateTeam(team) +} +func TestTeamMemberCRUD(t *testing.T) { + team, err := setupTeam() + if err != nil { + t.Errorf("Could not setup team %s", err) + } + defer cli.DeleteTeam(team) + teamID := team.ID + + member := &TeamMember{ + TeamID: graphql.String(teamID), + UserID: graphql.String(userID), + } + + if err := cli.CreateTeamMember(member); err != nil { + t.Errorf("Could not create team member: %s", err) + } + if member.ID == "" { + t.Errorf("Member ID was empty.") + } + m, err := cli.ReadTeamMember(string(member.ID)) + if err != nil { + t.Errorf("Could not read team member: %s", err) + } + assert.Equal(t, member, m) + + if err := cli.DeleteTeamMember(member); err != nil { + t.Errorf("Could not delete team member: %s", err) + } + + m, err = cli.ReadTeamMember(string(member.ID)) + if err != nil { + t.Errorf("Could not read team member: %s", err) + } +} diff --git a/buildkite/client/team_pipeline.go b/buildkite/client/team_pipeline.go new file mode 100644 index 0000000..0fab641 --- /dev/null +++ b/buildkite/client/team_pipeline.go @@ -0,0 +1,140 @@ +package client + +import ( + "context" + + "github.com/shurcooL/graphql" +) + +// TeamPipeline represents the association of a team to a pipeline. +type TeamPipeline struct { + ID graphql.String + AccessLevel graphql.String + // Team is the parent resource that a PipelineTeam belongs to. + Team struct { + ID graphql.String + } + // Pipeline is the parent resource that a PipelineTeam belongs to. + Pipeline struct { + ID graphql.String + } +} + +// ReadTeamPipelines looks up all teams for a given Pipeline via the Pipeline's Graphql ID. +func (c *Client) ReadTeamPipelines(pipelineID string) ([]TeamPipeline, error) { + var query struct { + Node struct { + Fragment struct { + Teams struct { + Edges []struct { + Node TeamPipeline + } + } `graphql:"teams(first: 20)"` + } `graphql:"... on Pipeline"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": pipelineID, + } + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + + result := []TeamPipeline{} + for _, edge := range query.Node.Fragment.Teams.Edges { + result = append(result, edge.Node) + } + return result, nil +} + +// ReadTeamPipeline returns a PipelineTeam based on its ID. +func (c *Client) ReadTeamPipeline(id string) (*TeamPipeline, error) { + var query struct { + Node struct { + TeamPipeline TeamPipeline `graphql:"... on TeamPipeline"` + } `graphql:"node(id: $id)"` + } + vars := map[string]interface{}{ + "id": id, + } + + if err := c.gqlClient.Query(context.TODO(), &query, vars); err != nil { + return nil, err + } + return &query.Node.TeamPipeline, nil +} + +// CreateTeamPipeline creates the provided PipelineTeam. +func (c *Client) CreateTeamPipeline(tp *TeamPipeline) error { + var mutation struct { + TeamPipelineCreate struct { + TeamPipelineEdge struct { + Node TeamPipeline + } + } `graphql:"teamPipelineCreate(input: $input)"` + } + type TeamPipelineCreateInput struct { + TeamID string `json:"teamID"` + PipelineID string `json:"pipelineID"` + AccessLevel string `json:"accessLevel"` + } + vars := map[string]interface{}{ + "input": TeamPipelineCreateInput{ + TeamID: string(tp.Team.ID), + PipelineID: string(tp.Pipeline.ID), + AccessLevel: string(tp.AccessLevel), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + tp.ID = mutation.TeamPipelineCreate.TeamPipelineEdge.Node.ID + return nil +} + +// UpdateTeamPipeline updates the provided PipelineTeam. +func (c *Client) UpdateTeamPipeline(tp *TeamPipeline) error { + var mutation struct { + TeamPipelineUpdate struct { + TeamPipeline TeamPipeline + } `graphql:"teamPipelineUpdate(input: $input)"` + } + type TeamPipelineUpdateInput struct { + ID string `json:"id"` + AccessLevel string `json:"accessLevel"` + } + vars := map[string]interface{}{ + "input": TeamPipelineUpdateInput{ + ID: string(tp.ID), + AccessLevel: string(tp.AccessLevel), + }, + } + + if err := c.gqlClient.Mutate(context.TODO(), &mutation, vars); err != nil { + return err + } + + *tp = mutation.TeamPipelineUpdate.TeamPipeline + return nil +} + +// DeleteTeamPipeline deletes the provided PipelineTeam. +func (c *Client) DeleteTeamPipeline(tp *TeamPipeline) error { + var mutation struct { + TeamPipelineDelete struct { + DeletedTeamPipelineID graphql.String `graphql:"deletedTeamPipelineID"` + } `graphql:"teamPipelineDelete(input: $input)"` + } + type TeamPipelineDeleteInput struct { + ID string `json:"id"` + Force bool `json:"force"` + } + vars := map[string]interface{}{ + "input": TeamPipelineDeleteInput{ + ID: string(tp.ID), + Force: false, + }, + } + return c.gqlClient.Mutate(context.TODO(), &mutation, vars) +} diff --git a/buildkite/client/team_pipeline_test.go b/buildkite/client/team_pipeline_test.go new file mode 100644 index 0000000..3ab38fe --- /dev/null +++ b/buildkite/client/team_pipeline_test.go @@ -0,0 +1,88 @@ +package client + +import ( + "reflect" + "testing" + + "github.com/shurcooL/graphql" +) + +func TestTeamPipelineCRUD(t *testing.T) { + p, err := setupPipeline() + if err != nil { + t.Errorf("Couldn't setup pipeline %s", err) + } + defer cli.DeletePipeline(p) + + testPipelineID, err := cli.GetPipelineID(*p.Slug) + if err != nil { + t.Errorf("Couldn't get pipeline ID %s", err) + } + testPipelineSlug := *p.Slug + + team, err := setupTeam() + if err != nil { + t.Errorf("Could not setup team %s", err) + } + defer cli.DeleteTeam(team) + testTeamID := team.ID + + // Test pre-condition. + teams, err := cli.ReadTeamPipelines(testPipelineID) + if err != nil { + t.Errorf("Couldn't check PipelineTeams for %s!", testPipelineSlug) + } + if len(teams) != 0 { + t.Errorf("Test pre-condition failed - %s should contain no teams at test start.", testPipelineSlug) + } + + tp := &TeamPipeline{ + AccessLevel: "MANAGE_BUILD_AND_READ", + Team: struct{ ID graphql.String }{ + ID: graphql.String(testTeamID), + }, + Pipeline: struct{ ID graphql.String }{ + ID: graphql.String(testPipelineID), + }, + } + + // Test Create. + err = cli.CreateTeamPipeline(tp) + if err != nil { + t.Errorf("Couldn't create PipelineTeam: %s", err) + } + if tp.ID == "" { + t.Errorf("Expected ID to be populated.") + } + + // Test Update. + tp.AccessLevel = graphql.String("BUILD_AND_READ") + err = cli.UpdateTeamPipeline(tp) + if err != nil { + t.Errorf("Couldn't update PipelineTeam: %s", err) + } + + // Test Read. + updatedTeamPipeline, err := cli.ReadTeamPipeline(string(tp.ID)) + if err != nil { + t.Errorf("Couldn't read PipelineTeam: %s", err) + } + if !reflect.DeepEqual(tp, updatedTeamPipeline) { + t.Errorf("Updated PipelineTeam did not match expected PipelineTeam") + } + + // Test Delete. + err = cli.DeleteTeamPipeline(updatedTeamPipeline) + if err != nil { + t.Errorf("Could not delete PipelineTeam: %s", err) + } + + // Make sure its gone. + teams, err = cli.ReadTeamPipelines(testPipelineID) + if err != nil { + t.Errorf("Could not read PipelineTeams: %s", err) + } + if len(teams) != 0 { + t.Errorf("Expected 0 teams for pipeline, but found %v", len(teams)) + } +} diff --git a/buildkite/client/team_test.go b/buildkite/client/team_test.go new file mode 100644 index 0000000..60dce13 --- /dev/null +++ b/buildkite/client/team_test.go @@ -0,0 +1,57 @@ +package client + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + + "github.com/shurcooL/graphql" +) + +func TestTeamCRUD(t *testing.T) { + name := fmt.Sprintf("test-pipeline-%d", rand.Int31n(10000)) + team := &Team{ + Name: graphql.String(name), + Privacy: "VISIBLE", + IsDefaultTeam: false, + DefaultMemberRole: "MAINTAINER", + } + if err := cli.CreateTeam(team); err != nil { + t.Errorf("Couldn't create team: %s", err) + } + if team.ID == "" { + t.Errorf("Team ID was blank") + } + + team.DefaultMemberRole = "MEMBER" + if err := cli.UpdateTeam(team); err != nil { + t.Errorf("Couldn't update team: %s", err) + } + + updatedTeam, err := cli.ReadTeam(string(team.ID)) + if err != nil { + t.Errorf("Couldn't read team: %s", err) + } + if !reflect.DeepEqual(team, updatedTeam) { + t.Errorf("Actual team not equal to updated team") + } + updatedTeam, err = cli.ReadTeamByName(string(team.Name)) + if err != nil { + t.Errorf("Couldn't read team by name: %s", err) + } + if !reflect.DeepEqual(team, updatedTeam) { + t.Errorf("Actual team not equal to read team") + } + + if err := cli.DeleteTeam(team); err != nil { + t.Errorf("Couldn't delete team: %s", err) + } + team, err = cli.ReadTeam(string(team.ID)) + if err != nil { + t.Errorf("Couldn't read team: %s", err) + } + if team.ID != "" { + t.Error("Expected empty team struct after delete") + } +} diff --git a/buildkite/client/user.go b/buildkite/client/user.go new file mode 100644 index 0000000..7b6299a --- /dev/null +++ b/buildkite/client/user.go @@ -0,0 +1,44 @@ +package client + +import ( + "context" + "errors" + + "github.com/shurcooL/graphql" +) + +// User represents a Buildkite user. +type User struct { + ID graphql.String + Name graphql.String + Email graphql.String + UUID graphql.String +} + +// GetUser returns user from the GraphQL API by email. +func (c *Client) GetUser(email string) (*User, error) { + var query struct { + Organization struct { + Members struct { + Edges []struct { + Node struct { + User User + } + } + } `graphql:"members(first: 1, email: $email)"` + } `graphql:"organization(slug: $slug)"` + } + vars := map[string]interface{}{ + "email": graphql.String(email), + "slug": c.orgSlug, + } + err := c.gqlClient.Query(context.TODO(), &query, vars) + if err != nil { + return nil, err + } + edges := query.Organization.Members.Edges + if len(edges) != 1 { + return nil, errors.New("expected exactly 1 result") + } + return &edges[0].Node.User, nil +} diff --git a/buildkite/client/user_test.go b/buildkite/client/user_test.go new file mode 100644 index 0000000..aaa8c51 --- /dev/null +++ b/buildkite/client/user_test.go @@ -0,0 +1,16 @@ +package client + +import "testing" + +func TestGetUser(t *testing.T) { + u, err := cli.GetUser(userEmail) + if err != nil { + t.Errorf("Couldn't make query: %s", err) + } + if u.Name == "" { + t.Errorf("Name was not expected, received %s", u.Name) + } + if u.ID == "" { + t.Error("User ID was blank") + } +} diff --git a/buildkite/data_source_user.go b/buildkite/data_source_user.go new file mode 100644 index 0000000..9321bfc --- /dev/null +++ b/buildkite/data_source_user.go @@ -0,0 +1,45 @@ +package buildkite + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" +) + +func dataSourceUser() *schema.Resource { + return &schema.Resource{ + Description: "A data source to reference users by email.", + Schema: map[string]*schema.Schema{ + "email": &schema.Schema{ + Type: schema.TypeString, + Computed: false, + Required: true, + }, + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "uuid": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + Read: getUser, + } +} + +func getUser(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + email := d.Get("email").(string) + u, err := bk.GetUser(email) + if err != nil { + return err + } + d.SetId(string(u.ID)) + d.Set("uuid", string(u.UUID)) + d.Set("name", string(u.Name)) + return nil +} diff --git a/buildkite/data_source_user_test.go b/buildkite/data_source_user_test.go new file mode 100644 index 0000000..895a721 --- /dev/null +++ b/buildkite/data_source_user_test.go @@ -0,0 +1,33 @@ +package buildkite + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +var testAccUserConfig = fmt.Sprintf(` +data "buildkite_user" "me" { + email = "%s" +} +`, userEmail) + +func TestAccDataSourceUser(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: testAccProviderFactory, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.buildkite_user.me", "name"), + resource.TestCheckResourceAttrSet("data.buildkite_user.me", "uuid"), + resource.TestCheckResourceAttrSet("data.buildkite_user.me", "id"), + ), + }, + }, + }) +} diff --git a/buildkite/provider.go b/buildkite/provider.go new file mode 100644 index 0000000..29eafff --- /dev/null +++ b/buildkite/provider.go @@ -0,0 +1,53 @@ +package buildkite + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" +) + +// Constants for environment variable names +const ( + OrgEnvVar = "BUILDKITE_ORGANIZATION_SLUG" + TokenEnvVar = "BUILDKITE_TOKEN" +) + +// Provider returns the sole provider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "organization_slug": { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc(OrgEnvVar, nil), + }, + "api_token": { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc(TokenEnvVar, nil), + }, + }, + ResourcesMap: map[string]*schema.Resource{ + "buildkite_pipeline": resourcePipeline(), + "buildkite_pipeline_schedule": resourcePipelineSchedule(), + "buildkite_team": resourceTeam(), + "buildkite_team_pipeline": resourceTeamPipeline(), + "buildkite_team_member": resourceTeamMember(), + }, + DataSourcesMap: map[string]*schema.Resource{ + "buildkite_user": dataSourceUser(), + }, + ConfigureFunc: createClient, + } +} + +func createClient(d *schema.ResourceData) (interface{}, error) { + org := d.Get("organization_slug").(string) + token := d.Get("api_token").(string) + + cli, err := client.NewClient(org, token) + if err != nil { + return nil, err + } + return cli, nil +} diff --git a/buildkite/provider_test.go b/buildkite/provider_test.go new file mode 100644 index 0000000..d78c574 --- /dev/null +++ b/buildkite/provider_test.go @@ -0,0 +1,47 @@ +package buildkite + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" +) + +var testAccProviderFactory = map[string]terraform.ResourceProviderFactory{ + "buildkite": func() (terraform.ResourceProvider, error) { + return Provider(), nil + }, +} + +const repoName = "git@github.com:samsara-dev/terraform-provider-buildkite.git" + +var ( + cli *client.Client + userEmail string +) + +func init() { + c, err := client.NewClient(os.Getenv(OrgEnvVar), os.Getenv(TokenEnvVar)) + if err != nil { + panic("Couldn't create client") + } + cli = c + userEmail = os.Getenv("BUILDKITE_USER_EMAIL") +} + +func TestProvider(t *testing.T) { + p := Provider().(*schema.Provider) + if err := p.InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func testAccPreCheck(t *testing.T) { + for _, env := range []string{OrgEnvVar, TokenEnvVar} { + if err := os.Getenv(env); err == "" { + t.Fatalf("%s must be set for acceptance tests", env) + } + } +} diff --git a/buildkite/resource_pipeline.go b/buildkite/resource_pipeline.go new file mode 100644 index 0000000..20bb19d --- /dev/null +++ b/buildkite/resource_pipeline.go @@ -0,0 +1,248 @@ +package buildkite + +import ( + "strconv" + + buildkiteRest "github.com/buildkite/go-buildkite/v2/buildkite" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" +) + +func resourcePipeline() *schema.Resource { + return &schema.Resource{ + Description: "A resource representing a pipeline in Buildkite.", + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "slug": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "repository": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "steps": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "branch_configuration": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "cancel_running_branch_builds": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + "cancel_running_branch_builds_filter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "default_branch": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "skip_queued_branch_builds": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + "skip_queued_branch_builds_filter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "provider_settings": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + }, + Create: createPipeline, + Read: readPipeline, + Update: updatePipeline, + Delete: deletePipeline, + Importer: &schema.ResourceImporter{ + State: importPipeline, + }, + } +} + +func strPtr(s interface{}) *string { + if s == nil { + return strPtr("") + } + str, ok := s.(string) + if !ok { + return strPtr("") + } + return &str +} + +func boolPtr(b interface{}) *bool { + if b == nil { + return boolPtr(false) + } + boo, ok := b.(bool) + if !ok { + return boolPtr(false) + } + return &boo +} + +func pipelineFromSchema(d *schema.ResourceData) *client.Pipeline { + boolStrPtr := func(b interface{}) *bool { + if b == nil { + return boolPtr(false) + } + s, ok := b.(string) + if !ok { + return boolPtr(false) + } + boo, _ := strconv.ParseBool(s) + return &boo + } + // Always use GithubSettings as they are a superset of all settings. + var provider *buildkiteRest.GitHubSettings + if _, ok := d.GetOk("provider_settings"); ok { + settings := d.Get("provider_settings").(map[string]interface{}) + provider = &buildkiteRest.GitHubSettings{ + TriggerMode: strPtr(settings["trigger_mode"]), + BuildPullRequests: boolStrPtr(settings["build_pull_requests"]), + PullRequestBranchFilterEnabled: boolStrPtr(settings["pull_request_branch_filter_enabled"]), + PullRequestBranchFilterConfiguration: strPtr(settings["pull_request_branch_filter_configuration"]), + SkipPullRequestBuildsForExistingCommits: boolStrPtr(settings["skip_pull_request_builds_for_existing_commits"]), + BuildPullRequestForks: boolStrPtr(settings["build_pull_request_forks"]), + PrefixPullRequestForkBranchNames: boolStrPtr(settings["prefix_pull_request_fork_branch_names"]), + BuildTags: boolStrPtr(settings["build_tags"]), + PublishCommitStatus: boolStrPtr(settings["publish_commit_status"]), + PublishCommitStatusPerStep: boolStrPtr(settings["publish_commit_status_per_step"]), + FilterEnabled: boolStrPtr(settings["filter_enabled"]), + FilterCondition: strPtr(settings["filter_condition"]), + SeparatePullRequestStatuses: boolStrPtr(settings["separate_pull_request_statuses"]), + PublishBlockedAsPending: boolStrPtr(settings["publish_blocked_as_pending"]), + } + } + + return &client.Pipeline{ + Name: strPtr(d.Get("name")), + Slug: strPtr(d.Get("slug")), + Repository: strPtr(d.Get("repository")), + Steps: nil, + Configuration: d.Get("steps").(string), // YAML steps specified here. + DefaultBranch: strPtr(d.Get("default_branch")), + Description: strPtr(d.Get("description")), + BranchConfiguration: strPtr(d.Get("branch_configuration")), + SkipQueuedBranchBuilds: boolPtr(d.Get("skip_queued_branch_builds")), + SkipQueuedBranchBuildsFilter: strPtr(d.Get("skip_queued_branch_builds_filter")), + CancelRunningBranchBuilds: boolPtr(d.Get("cancel_running_branch_builds")), + CancelRunningBranchBuildsFilter: strPtr(d.Get("cancel_running_branch_builds_filter")), + + Provider: &buildkiteRest.Provider{ + Settings: provider, + }, + } +} +func createPipeline(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + p := pipelineFromSchema(d) + if err := bk.CreatePipeline(p); err != nil { + return err + } + d.Set("slug", p.Slug) + return readPipeline(d, m) +} + +func readPipeline(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + slug := d.Get("slug").(string) + p, err := bk.ReadPipeline(slug) + if err != nil { + return err + } + + // Set the ID to the gql ID so it can be used by other resources. + id, err := bk.GetPipelineID(slug) + if err != nil { + return err + } + + d.SetId(id) + // Terraform handles pointers gracefully. + d.Set("repository", p.Repository) + d.Set("steps", p.Configuration) + d.Set("branch_configuration", p.BranchConfiguration) + d.Set("cancel_running_branch_builds", p.CancelRunningBranchBuilds) + d.Set("cancel_running_branch_builds_filter", p.CancelRunningBranchBuildsFilter) + d.Set("default_branch", p.DefaultBranch) + d.Set("description", p.Description) + d.Set("skip_queued_branch_builds", p.SkipQueuedBranchBuilds) + d.Set("skip_queued_branch_builds_filter", p.SkipQueuedBranchBuildsFilter) + + boolPtrToStr := func(b *bool) string { + if b == nil { + return "" + } + return strconv.FormatBool(*b) + } + safeString := func(s *string) string { + if s == nil { + return "" + } + return *s + } + settings := p.Provider.Settings.(*buildkiteRest.GitHubSettings) + provider := map[string]interface{}{ + "trigger_mode": safeString(settings.TriggerMode), + "build_pull_requests": boolPtrToStr(settings.BuildPullRequests), + "pull_request_branch_filter_enabled": boolPtrToStr(settings.PullRequestBranchFilterEnabled), + "pull_request_branch_filter_configuration": safeString(settings.PullRequestBranchFilterConfiguration), + "skip_pull_request_builds_for_existing_commits": boolPtrToStr(settings.SkipPullRequestBuildsForExistingCommits), + "build_pull_request_forks": boolPtrToStr(settings.BuildPullRequestForks), + "prefix_pull_request_fork_branch_names": boolPtrToStr(settings.PrefixPullRequestForkBranchNames), + "build_tags": boolPtrToStr(settings.BuildTags), + "publish_commit_status": boolPtrToStr(settings.PublishCommitStatus), + "publish_commit_status_per_step": boolPtrToStr(settings.PublishCommitStatusPerStep), + "filter_enabled": boolPtrToStr(settings.FilterEnabled), + "filter_condition": safeString(settings.FilterCondition), + "separate_pull_request_statuses": boolPtrToStr(settings.SeparatePullRequestStatuses), + "publish_blocked_as_pending": boolPtrToStr(settings.PublishBlockedAsPending), + } + + // Delete nil values. + for k, v := range provider { + val := v.(string) + if val == "" { + delete(provider, k) + } + } + d.Set("provider_settings", provider) + return nil +} + +func updatePipeline(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + if err := bk.UpdatePipeline(pipelineFromSchema(d)); err != nil { + return err + } + return readPipeline(d, m) +} + +func deletePipeline(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + if err := bk.DeletePipeline(pipelineFromSchema(d)); err != nil { + return err + } + return nil +} + +func importPipeline(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + d.Set("name", d.Id()) + if err := readPipeline(d, m); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/buildkite/resource_pipeline_schedule.go b/buildkite/resource_pipeline_schedule.go new file mode 100644 index 0000000..7de203e --- /dev/null +++ b/buildkite/resource_pipeline_schedule.go @@ -0,0 +1,173 @@ +package buildkite + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" + "github.com/shurcooL/graphql" +) + +func resourcePipelineSchedule() *schema.Resource { + return &schema.Resource{ + Description: "A resource representing a pipeline schedule in Buildkite.", + Schema: map[string]*schema.Schema{ + "pipeline_id": { + Type: schema.TypeString, + Required: true, + }, + "cronline": { + Type: schema.TypeString, + Required: true, + }, + "env": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + "message": { + Type: schema.TypeString, + Required: true, + }, + "branch": { + Type: schema.TypeString, + Required: true, + }, + "commit": { + Type: schema.TypeString, + Required: true, + }, + "label": { + Type: schema.TypeString, + Required: true, + }, + }, + Create: createPipelineSchedule, + Read: readPipelineSchedule, + Update: updatePipelineSchedule, + Delete: deletePipelineSchedule, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + } +} + +// flattenMap is used to convert a map of string to string, to a slice of strings. +// Each element in the slice will have the format "key=value". +func flattenMap(m map[string]interface{}) []string { + result := []string{} + for k, v := range m { + result = append(result, fmt.Sprintf("%s=%s", k, v)) + } + return result +} + +// expandSlice takes a slice of string, with each element in the form of "key=value". +// It returns a map with the key as the key, and the value as the value. +// If an element contains an illegal format, an error is returned. +func expandSlice(s []string) (map[string]interface{}, error) { + result := make(map[string]interface{}, len(s)) + for _, v := range s { + segments := strings.Split(v, "=") + if len(segments) != 2 { + return result, fmt.Errorf("Illegal env entry - must be in the form of 'key=value': %s", v) + } + result[segments[0]] = segments[1] + } + return result, nil +} + +func createPipelineSchedule(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + + var env map[string]interface{} + if v, ok := d.GetOk("env"); ok { + env = v.(map[string]interface{}) + } + + ps := &client.PipelineSchedule{ + Branch: graphql.String(d.Get("branch").(string)), + Commit: graphql.String(d.Get("commit").(string)), + Cronline: graphql.String(d.Get("cronline").(string)), + Enabled: graphql.Boolean(d.Get("enabled").(bool)), + Env: flattenMap(env), + Label: graphql.String(d.Get("label").(string)), + Message: graphql.String(d.Get("message").(string)), + Pipeline: struct { + ID graphql.String + }{ + ID: graphql.String(d.Get("pipeline_id").(string)), + }, + } + + if err := bk.CreatePipelineSchedule(ps); err != nil { + return err + } + d.SetId(string(ps.ID)) + return nil +} + +func readPipelineSchedule(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + ps, err := bk.ReadPipelineSchedule(d.Id()) + if err != nil { + return err + } + + env := make([]string, 0) + if ps.Env != nil { + env = ps.Env + } + envMap, err := expandSlice(env) + if err != nil { + return err + } + + d.Set("pipeline_id", ps.Pipeline.ID) + d.Set("cronline", ps.Cronline) + d.Set("env", envMap) + d.Set("enabled", ps.Enabled) + d.Set("message", ps.Message) + d.Set("branch", ps.Branch) + d.Set("commit", ps.Commit) + d.Set("label", ps.Label) + return nil +} + +func updatePipelineSchedule(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + + var env map[string]interface{} + if v, ok := d.GetOk("env"); ok { + env = v.(map[string]interface{}) + } + + ps := &client.PipelineSchedule{ + ID: graphql.String(d.Id()), + Branch: graphql.String(d.Get("branch").(string)), + Commit: graphql.String(d.Get("commit").(string)), + Cronline: graphql.String(d.Get("cronline").(string)), + Enabled: graphql.Boolean(d.Get("enabled").(bool)), + Env: flattenMap(env), + Label: graphql.String(d.Get("label").(string)), + Message: graphql.String(d.Get("message").(string)), + } + if err := bk.UpdatePipelineSchedule(ps); err != nil { + return err + } + return nil +} + +func deletePipelineSchedule(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + id := d.Id() + if err := bk.DeletePipelineSchedule(&client.PipelineSchedule{ID: graphql.String(id)}); err != nil { + return err + } + return nil +} diff --git a/buildkite/resource_pipeline_schedule_test.go b/buildkite/resource_pipeline_schedule_test.go new file mode 100644 index 0000000..d323c2d --- /dev/null +++ b/buildkite/resource_pipeline_schedule_test.go @@ -0,0 +1,205 @@ +package buildkite + +import ( + "fmt" + "sort" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" + "github.com/shurcooL/graphql" + "github.com/stretchr/testify/assert" +) + +func testAccPipelineScheduleConfig(name string) string { + return fmt.Sprintf(` +%s + +resource "buildkite_pipeline_schedule" "tfAccTestSchedule" { + pipeline_id = "${buildkite_pipeline.test.id}" + cronline = "0 0 1 1 *" + env = { + KEY1 = "val1" + } + enabled = true + message = "test message" + branch = "master" + commit = "HEAD" + label = "%s" +}`, testAccPipelineConfig(name), name) +} + +func testAccPipelineScheduleUpdated(name string) string { + return fmt.Sprintf(` +%s + +resource "buildkite_pipeline_schedule" "tfAccTestSchedule" { + pipeline_id = "${buildkite_pipeline.test.id}" + cronline = "0 0 1 2 *" + env = { + KEY1 = "VAL1" + KEY2 = "val2" + } + enabled = false + message = "test message" + branch = "test_branch" + commit = "HEAD" + label = "%s" +} +`, testAccPipelineConfig(name), name) +} + +func TestAccPipelineSchedule(t *testing.T) { + rLabel := acctest.RandString(5) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: testAccPipelineScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPipelineScheduleConfig(rLabel), + Check: resource.ComposeAggregateTestCheckFunc( + testAccPipelineScheduleExists("buildkite_pipeline_schedule.tfAccTestSchedule"), + resource.TestCheckResourceAttrSet("buildkite_pipeline_schedule.tfAccTestSchedule", "id"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "cronline", "0 0 1 1 *"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "env.%", "1"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "env.KEY1", "val1"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "enabled", "true"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "message", "test message"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "branch", "master"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "commit", "HEAD"), + ), + }, + { + Config: testAccPipelineScheduleUpdated(rLabel), + Check: resource.ComposeAggregateTestCheckFunc( + testAccPipelineScheduleExists("buildkite_pipeline_schedule.tfAccTestSchedule"), + resource.TestCheckResourceAttrSet("buildkite_pipeline_schedule.tfAccTestSchedule", "id"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "cronline", "0 0 1 2 *"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "env.%", "2"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "env.KEY1", "VAL1"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "env.KEY2", "val2"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "enabled", "false"), + resource.TestCheckResourceAttr("buildkite_pipeline_schedule.tfAccTestSchedule", "branch", "test_branch"), + ), + }, + }, + }) +} + +func testAccPipelineScheduleExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Resource %s not found", name) + } + id := rs.Primary.ID + if _, err := cli.ReadPipelineSchedule(id); err != nil { + return err + } + return nil + } +} + +func testAccPipelineScheduleDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "buildkite_pipeline_schedule" { + continue + } + toDelete := &client.PipelineSchedule{ID: graphql.String(rs.Primary.ID)} + if err := cli.DeletePipelineSchedule(toDelete); err != nil { + if !strings.Contains(err.Error(), "No schedule found") { + return err + } + } + } + return nil +} + +func TestFlattenMap(t *testing.T) { + testCases := []struct { + description string + input map[string]interface{} + expected []string + }{ + { + description: "standard map", + input: map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + expected: []string{"key1=val1", "key2=val2"}, + }, + { + description: "empty map", + input: map[string]interface{}{}, + expected: []string{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + actual := flattenMap(tc.input) + + sort.Strings(tc.expected) + sort.Strings(actual) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func TestExpandSlice(t *testing.T) { + testCases := []struct { + description string + input []string + shouldError bool + output map[string]interface{} + }{ + { + description: "legal input", + input: []string{"key1=val1", "key2=val2", "key3=val3"}, + shouldError: false, + output: map[string]interface{}{ + "key1": "val1", + "key2": "val2", + "key3": "val3", + }, + }, + { + description: "empty input", + input: []string{}, + shouldError: false, + output: map[string]interface{}{}, + }, + { + description: "nil input", + input: nil, + shouldError: false, + output: map[string]interface{}{}, + }, + { + description: "illegal input", + input: []string{"key1=val1", "key2=val2", "no equals sign"}, + shouldError: true, + output: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + actual, err := expandSlice(tc.input) + if tc.shouldError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tc.output, actual) + }) + } +} diff --git a/buildkite/resource_pipeline_team.go b/buildkite/resource_pipeline_team.go new file mode 100644 index 0000000..6e1c92a --- /dev/null +++ b/buildkite/resource_pipeline_team.go @@ -0,0 +1,97 @@ +package buildkite + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" + "github.com/shurcooL/graphql" +) + +func resourceTeamPipeline() *schema.Resource { + return &schema.Resource{ + Description: "A resource representing a team with permission on a pipeline in Buildkite.", + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeString, + Required: true, + }, + "pipeline_id": { + Type: schema.TypeString, + Required: true, + }, + "access_level": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"MANAGE_BUILD_AND_READ", "BUILD_AND_READ", "READ_ONLY"}, false), + }, + }, + Create: createPipelineTeam, + Read: readPipelineTeam, + Update: updatePipelineTeam, + Delete: deletePipelineTeam, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + } +} + +func createPipelineTeam(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + + tp := &client.TeamPipeline{ + AccessLevel: graphql.String(d.Get("access_level").(string)), + Team: struct{ ID graphql.String }{ + ID: graphql.String(d.Get("team_id").(string)), + }, + Pipeline: struct{ ID graphql.String }{ + ID: graphql.String(d.Get("pipeline_id").(string)), + }, + } + + if err := bk.CreateTeamPipeline(tp); err != nil { + return err + } + d.SetId(string(tp.ID)) + return nil +} + +func readPipelineTeam(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + tp, err := bk.ReadTeamPipeline(d.Id()) + if err != nil { + return err + } + + d.Set("team_id", string(tp.Team.ID)) + d.Set("pipeline_id", string(tp.Pipeline.ID)) + d.Set("access_level", string(tp.AccessLevel)) + return nil +} + +func updatePipelineTeam(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + + tp := &client.TeamPipeline{ + ID: graphql.String(d.Id()), + AccessLevel: graphql.String(d.Get("access_level").(string)), + Team: struct{ ID graphql.String }{ + ID: graphql.String(d.Get("team_id").(string)), + }, + Pipeline: struct{ ID graphql.String }{ + ID: graphql.String(d.Get("pipeline_id").(string)), + }, + } + if err := bk.UpdateTeamPipeline(tp); err != nil { + return err + } + return nil +} + +func deletePipelineTeam(d *schema.ResourceData, m interface{}) error { + bk := m.(*client.Client) + id := d.Id() + if err := bk.DeleteTeamPipeline(&client.TeamPipeline{ID: graphql.String(id)}); err != nil { + return err + } + return nil +} diff --git a/buildkite/resource_pipeline_team_test.go b/buildkite/resource_pipeline_team_test.go new file mode 100644 index 0000000..3d4cd00 --- /dev/null +++ b/buildkite/resource_pipeline_team_test.go @@ -0,0 +1,81 @@ +package buildkite + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" + "github.com/shurcooL/graphql" +) + +func testAccTeamPipelineConfig(accessLevel string) string { + return fmt.Sprintf(` +%s + +%s + +resource "buildkite_team_pipeline" "tfAccTestTeamPipeline" { + team_id = "${buildkite_team.devexp.id}" + pipeline_id = "${buildkite_pipeline.test.id}" + access_level = "%s" +}`, testAccPipelineConfig("testAccTeamPipeline"), testAccTeamConfig("testAccTeamPipeline"), accessLevel) +} + +func TestAccTeamPipeline(t *testing.T) { + rName := "buildkite_team_pipeline.tfAccTestTeamPipeline" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: testAccTeamPipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTeamPipelineConfig("READ_ONLY"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccTeamPipelineExists(rName), + resource.TestCheckResourceAttr(rName, "access_level", "READ_ONLY"), + ), + }, + { + Config: testAccTeamPipelineConfig("BUILD_AND_READ"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccTeamPipelineExists(rName), + resource.TestCheckResourceAttr(rName, "access_level", "BUILD_AND_READ"), + ), + }, + }, + }) +} + +func testAccTeamPipelineExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Resource %s not found", name) + } + id := rs.Primary.ID + if _, err := cli.ReadTeamPipeline(id); err != nil { + return err + } + return nil + } +} + +func testAccTeamPipelineDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "buildkite_team_pipeline" { + continue + } + toDelete := &client.TeamPipeline{ID: graphql.String(rs.Primary.ID)} + if err := cli.DeleteTeamPipeline(toDelete); err != nil { + if !strings.Contains(err.Error(), "No team pipeline found") { + return err + } + } + } + return nil +} diff --git a/buildkite/resource_pipeline_test.go b/buildkite/resource_pipeline_test.go new file mode 100644 index 0000000..318d96f --- /dev/null +++ b/buildkite/resource_pipeline_test.go @@ -0,0 +1,169 @@ +package buildkite + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite/client" +) + +func testAccPipelineConfig(name string) string { + return fmt.Sprintf(` +resource "buildkite_pipeline" "test" { + name = "%s" + repository = "%s" + steps = < +## Schema + +### Required + +- **email** (String) + +### Read-Only + +- **id** (String) The ID of this resource. +- **name** (String) +- **uuid** (String) + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..bd5655b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite Provider" +subcategory: "" +description: |- + +--- + +# buildkite Provider + +This is a Terraform provider for [Buildkite](https://buildkite.com). It can be used to manage a specific organization in Buildkite and accepts an API token and organization slug either via the parameters below or the environment variables `BUILDKITE_ORGANIZATION_SLUG` and `BUILDKITE_TOKEN`. The API token provided must have full GQL access as well as read/write access to the REST API, more documentation [here](https://buildkite.com/docs/apis/managing-api-tokens). + +## Example +```hcl +provider "buildkite" { + version = "0.3.1" +} +``` + +## Schema + +### Optional + +- **api_token** (String) +- **organization_slug** (String) diff --git a/docs/resources/pipeline.md b/docs/resources/pipeline.md new file mode 100644 index 0000000..5f2bca7 --- /dev/null +++ b/docs/resources/pipeline.md @@ -0,0 +1,78 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite_pipeline Resource - terraform-provider-buildkite" +subcategory: "" +description: |- + A resource representing a pipeline in Buildkite. +--- + +# buildkite_pipeline (Resource) + +A resource representing a pipeline in Buildkite. + +## Example +```hcl +resource "buildkite_pipeline" "test" { + name = "build pipeline" + repository = "git@github.com:your-org/repo.git" + steps = < +## Schema + +### Required + +- **name** (String) +- **repository** (String) +- **steps** (String) + +### Optional + +- **branch_configuration** (String) +- **cancel_running_branch_builds** (Boolean) +- **cancel_running_branch_builds_filter** (String) +- **default_branch** (String) +- **description** (String) +- **id** (String) The ID of this resource. +- **provider_settings** (Map of String) +- **skip_queued_branch_builds** (Boolean) +- **skip_queued_branch_builds_filter** (String) + +### Read-Only + +- **slug** (String) + + diff --git a/docs/resources/pipeline_schedule.md b/docs/resources/pipeline_schedule.md new file mode 100644 index 0000000..33e2826 --- /dev/null +++ b/docs/resources/pipeline_schedule.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite_pipeline_schedule Resource - terraform-provider-buildkite" +subcategory: "" +description: |- + A resource representing a pipeline schedule in Buildkite. +--- + +# buildkite_pipeline_schedule (Resource) + +A resource representing a pipeline schedule in Buildkite. + + +## Example +```hcl +resource "buildkite_pipeline" "test" { + name = "build pipeline" + ... +} + +resource "buildkite_pipeline_schedule" "tfAccTestSchedule" { + pipeline_id = "${buildkite_pipeline.test.id}" + cronline = "0 0 1 1 *" + env = { + KEY1 = "val1" + } + enabled = true + message = "test message" + branch = "master" + commit = "HEAD" + label = "build regularly" +} +``` + + + +## Schema + +### Required + +- **branch** (String) +- **commit** (String) +- **cronline** (String) +- **enabled** (Boolean) +- **label** (String) +- **message** (String) +- **pipeline_id** (String) + +### Optional + +- **env** (Map of String) + +### Read-Only +- **id** (String) The ID of this resource. + + diff --git a/docs/resources/team.md b/docs/resources/team.md new file mode 100644 index 0000000..5cebf7d --- /dev/null +++ b/docs/resources/team.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite_team Resource - terraform-provider-buildkite" +subcategory: "" +description: |- + A resource representing a team in Buildkite. +--- + +# buildkite_team (Resource) + +A resource representing a team in Buildkite. + +## Example +```hcl +resource "buildkite_team" "admins" { + name = "admins" + privacy = "VISIBLE" + is_default_team = false + default_member_role = "MAINTAINER" +} +``` + + +## Schema + +### Required + +- **default_member_role** (String) +- **is_default_team** (Boolean) +- **name** (String) +- **privacy** (String) + +### Read-Only + +- **id** (String) The ID of this resource. + + diff --git a/docs/resources/team_member.md b/docs/resources/team_member.md new file mode 100644 index 0000000..40b2eb6 --- /dev/null +++ b/docs/resources/team_member.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite_team_member Resource - terraform-provider-buildkite" +subcategory: "" +description: |- + An association between a user and a team. +--- + +# buildkite_team_member (Resource) + +An association between a user and a team. + +## Example +```hcl +data "buildkite_user" "admin" { + email = "dev@yourorg.com" +} + +resource "buildkite_team" "admins" { + name = "admins" + ... +} + +resource "buildkite_team_member" "test" { + user_id = "${data.buildkite_user.admin.id}" + team_id = "${buildkite_team.admins.id}" +} +``` + + +## Schema + +### Required + +- **team_id** (String) +- **user_id** (String) + +### Read-Only + +- **id** (String) The ID of this resource. + + diff --git a/docs/resources/team_pipeline.md b/docs/resources/team_pipeline.md new file mode 100644 index 0000000..5ab5c10 --- /dev/null +++ b/docs/resources/team_pipeline.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "buildkite_team_pipeline Resource - terraform-provider-buildkite" +subcategory: "" +description: |- + A resource representing a team with permission on a pipeline in Buildkite. +--- + +# buildkite_team_pipeline (Resource) + +A resource representing a team with permission on a pipeline in Buildkite. + +## Example +```hcl +resource "buildkite_pipeline" "test" { + name = "build pipeline" + ... +} + +resource "buildkite_team" "admins" { + name = "admins" + ... +} + +resource "buildkite_team_pipeline" "tfAccTestTeamPipeline" { + team_id = "${buildkite_team.admins.id}" + pipeline_id = "${buildkite_pipeline.test.id}" + access_level = "%s" +} +``` + + +## Schema + +### Required + +- **access_level** (String) +- **pipeline_id** (String) +- **team_id** (String) + +### Read-Only + +- **id** (String) The ID of this resource. + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7612023 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/samsara-dev/terraform-provider-buildkite + +go 1.13 + +require ( + github.com/apparentlymart/go-cidr v1.1.0 // indirect + github.com/aws/aws-sdk-go v1.31.9 // indirect + github.com/buildkite/go-buildkite/v2 v2.5.1 + github.com/hashicorp/hcl/v2 v2.6.0 // indirect + github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7 // indirect + github.com/hashicorp/terraform-plugin-sdk v1.16.0 + github.com/likexian/gokit v0.24.7 // indirect + github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f + github.com/stretchr/testify v1.5.1 + github.com/zclconf/go-cty v1.5.1 // indirect + github.com/zclconf/go-cty-yaml v1.0.2 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b780bf1 --- /dev/null +++ b/go.sum @@ -0,0 +1,598 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U= +cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.9 h1:n+b34ydVfgC30j0Qm69yaapmjejQPW2BoDBX7Uy/tLI= +github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/buildkite/go-buildkite/v2 v2.5.1 h1:xlJWovpYjayJ1v98soviHLA9fdnreR4GajUJvj82Nx8= +github.com/buildkite/go-buildkite/v2 v2.5.1/go.mod h1:kRCClqF2FuCFK42+Jk8ggYUMMAQXJC3uMjBt6W/ajJ0= +github.com/cenkalti/backoff v1.1.1-0.20171020064038-309aa717adbf h1:yxlp0s+Sge9UsKEK0Bsvjiopb9XRk+vxylmZ9eGBfm8= +github.com/cenkalti/backoff v1.1.1-0.20171020064038-309aa717adbf/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= +github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 h1:l1KB3bHVdvegcIf5upQ5mjcHjs2qsWnKh4Yr9xgIuu8= +github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8= +github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws= +github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= +github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= +github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= +github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A= +github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7 h1:Pc5TCv9mbxFN6UVX0LH6CpQrdTM5YjbVI2w15237Pjk= +github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A= +github.com/hashicorp/terraform-exec v0.10.0 h1:3nh/1e3u9gYRUQGOKWp/8wPR7ABlL2F14sZMZBrp+dM= +github.com/hashicorp/terraform-exec v0.10.0/go.mod h1:tOT8j1J8rP05bZBGWXfMyU3HkLi1LWyqL3Bzsc3CJjo= +github.com/hashicorp/terraform-json v0.5.0 h1:7TV3/F3y7QVSuN4r9BEXqnWqrAyeOtON8f0wvREtyzs= +github.com/hashicorp/terraform-json v0.5.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU= +github.com/hashicorp/terraform-plugin-sdk v1.16.0 h1:NrkXMRjHErUPPTHQkZ6JIn6bByiJzGnlJzH1rVdNEuE= +github.com/hashicorp/terraform-plugin-sdk v1.16.0/go.mod h1:5sVxrwW6/xzFhZyql+Q9zXCUEJaGWcBIxBbZFLpVXOI= +github.com/hashicorp/terraform-plugin-test/v2 v2.1.2 h1:p96IIn+XpvVjw7AtN8y9MKxn0x69S7wtbGf7JgDJoIk= +github.com/hashicorp/terraform-plugin-test/v2 v2.1.2/go.mod h1:jerO5mrd+jVNALy8aiq+VZOg/CR8T2T1QR3jd6JKGOI= +github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg= +github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +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/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/likexian/gokit v0.24.7 h1:jGb8rZTKFnk0tAY9Knd4FwQVLRVBaAEM1gY8C/y1sBQ= +github.com/likexian/gokit v0.24.7/go.mod h1:NCv1RDZK5kR0T2SfAl/vjIO6rsjszt2C/25TKxJalhs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/cli v1.1.1 h1:J64v/xD7Clql+JVKSvkYojLOXu1ibnY9ZjGLwSt/89w= +github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.4 h1:ZU1VNC02qyufSZsjjs7+khruk2fKvbQ3TwRV/IBCeFA= +github.com/mitchellh/go-testing-interface v1.0.4/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= +github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= +github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.5.1 h1:oALUZX+aJeEBUe2a1+uD2+UTaYfEjnKFDEMRydkGvWE= +github.com/zclconf/go-cty v1.5.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= +github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= +github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0= +github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY= +golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= +google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e73ed91 --- /dev/null +++ b/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/hashicorp/terraform-plugin-sdk/plugin" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/samsara-dev/terraform-provider-buildkite/buildkite" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: func() terraform.ResourceProvider { + return buildkite.Provider() + }, + }) +}