diff --git a/go.mod b/go.mod index df0c5085e..fcad70d1c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/terraform-providers/terraform-provider-tfe require ( - github.com/hashicorp/go-tfe v0.4.0 + github.com/hashicorp/go-tfe v0.5.0 github.com/hashicorp/go-version v1.2.0 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce github.com/hashicorp/terraform-plugin-sdk v1.0.0 diff --git a/go.sum b/go.sum index cd0dd99e4..0db8204e5 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc= github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= -github.com/hashicorp/go-tfe v0.4.0 h1:p5eh7MhrJ5ysAQaq4sbWg/eVJ94qtrqFJWqlYZZhgLE= -github.com/hashicorp/go-tfe v0.4.0/go.mod h1:DVPSW2ogH+M9W1/i50ASgMht8cHP7NxxK0nrY9aFikQ= +github.com/hashicorp/go-tfe v0.5.0 h1:8fKVNGdCziwZy0VtZkKmurYL7RJRPvGL9R72glwk6F8= +github.com/hashicorp/go-tfe v0.5.0/go.mod h1:DVPSW2ogH+M9W1/i50ASgMht8cHP7NxxK0nrY9aFikQ= 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 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= diff --git a/tfe/provider.go b/tfe/provider.go index cc80ac20a..2fdcf6fa8 100644 --- a/tfe/provider.go +++ b/tfe/provider.go @@ -76,6 +76,7 @@ func Provider() terraform.ResourceProvider { "tfe_organization_token": resourceTFEOrganizationToken(), "tfe_policy_set": resourceTFEPolicySet(), "tfe_policy_set_parameter": resourceTFEPolicySetParameter(), + "tfe_run_trigger": resourceTFERunTrigger(), "tfe_sentinel_policy": resourceTFESentinelPolicy(), "tfe_ssh_key": resourceTFESSHKey(), "tfe_team": resourceTFETeam(), diff --git a/tfe/resource_tfe_run_trigger.go b/tfe/resource_tfe_run_trigger.go new file mode 100644 index 000000000..bbbaa2249 --- /dev/null +++ b/tfe/resource_tfe_run_trigger.go @@ -0,0 +1,96 @@ +package tfe + +import ( + "fmt" + "log" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceTFERunTrigger() *schema.Resource { + return &schema.Resource{ + Create: resourceTFERunTriggerCreate, + Read: resourceTFERunTriggerRead, + Delete: resourceTFERunTriggerDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "workspace_external_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "sourceable_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceTFERunTriggerCreate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get workspace + workspaceID := d.Get("workspace_external_id").(string) + + // Get attributes + sourceableID := d.Get("sourceable_id").(string) + + // Create a new options struct + options := tfe.RunTriggerCreateOptions{ + Sourceable: &tfe.Workspace{ + ID: sourceableID, + }, + } + + log.Printf("[DEBUG] Create run trigger on workspace %s with sourceable %s", workspaceID, sourceableID) + runTrigger, err := tfeClient.RunTriggers.Create(ctx, workspaceID, options) + if err != nil { + return fmt.Errorf("Error creating run trigger on workspace %s with sourceable %s: %v", workspaceID, sourceableID, err) + } + + d.SetId(runTrigger.ID) + + return resourceTFERunTriggerRead(d, meta) +} + +func resourceTFERunTriggerRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Read run trigger: %s", d.Id()) + runTrigger, err := tfeClient.RunTriggers.Read(ctx, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + log.Printf("[DEBUG] run trigger %s no longer exists", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading run trigger %s: %v", d.Id(), err) + } + + // Update config + d.Set("workspace_external_id", runTrigger.Workspace.ID) + d.Set("sourceable_id", runTrigger.Sourceable.ID) + + return nil +} + +func resourceTFERunTriggerDelete(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Delete run trigger: %s", d.Id()) + err := tfeClient.RunTriggers.Delete(ctx, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + return nil + } + return fmt.Errorf("Error deleting run trigger %s: %v", d.Id(), err) + } + + return nil +} diff --git a/tfe/resource_tfe_run_trigger_test.go b/tfe/resource_tfe_run_trigger_test.go new file mode 100644 index 000000000..f5bd19a4c --- /dev/null +++ b/tfe/resource_tfe_run_trigger_test.go @@ -0,0 +1,135 @@ +package tfe + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccTFERunTrigger_basic(t *testing.T) { + runTrigger := &tfe.RunTrigger{} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFERunTriggerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFERunTrigger_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckTFERunTriggerExists( + "tfe_run_trigger.foobar", runTrigger), + testAccCheckTFERunTriggerAttributes(runTrigger), + ), + }, + }, + }) +} + +func TestAccTFERunTriggerImport(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFERunTriggerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFERunTrigger_basic, + }, + + { + ResourceName: "tfe_run_trigger.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckTFERunTriggerExists(n string, runTrigger *tfe.RunTrigger) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + rt, err := tfeClient.RunTriggers.Read(ctx, rs.Primary.ID) + if err != nil { + return err + } + + *runTrigger = *rt + + return nil + } +} + +func testAccCheckTFERunTriggerAttributes(runTrigger *tfe.RunTrigger) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + workspaceID := runTrigger.Workspace.ID + workspace, _ := tfeClient.Workspaces.Read(ctx, "tst-terraform", "workspace-test") + if workspace.ID != workspaceID { + return fmt.Errorf("Wrong workspace: %v", workspace.ID) + } + + sourceableID := runTrigger.Sourceable.ID + sourceable, _ := tfeClient.Workspaces.Read(ctx, "tst-terraform", "sourceable-test") + if sourceable.ID != sourceableID { + return fmt.Errorf("Wrong sourceable: %v", sourceable.ID) + } + + return nil + } +} + +func testAccCheckTFERunTriggerDestroy(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "tfe_run_trigger" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + _, err := tfeClient.RunTriggers.Read(ctx, rs.Primary.ID) + if err == nil { + return fmt.Errorf("Notification configuration %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccTFERunTrigger_basic = ` +resource "tfe_organization" "foobar" { + name = "tst-terraform" + email = "admin@company.com" +} + +resource "tfe_workspace" "workspace" { + name = "workspace-test" + organization = "${tfe_organization.foobar.id}" +} + +resource "tfe_workspace" "sourceable" { + name = "sourceable-test" + organization = "${tfe_organization.foobar.id}" +} + +resource "tfe_run_trigger" "foobar" { + workspace_external_id = "${tfe_workspace.workspace.external_id}" + sourceable_id = "${tfe_workspace.sourceable.external_id}" +}` diff --git a/vendor/github.com/hashicorp/go-tfe/README.md b/vendor/github.com/hashicorp/go-tfe/README.md index 4b49a0e79..f7dd6eaf9 100644 --- a/vendor/github.com/hashicorp/go-tfe/README.md +++ b/vendor/github.com/hashicorp/go-tfe/README.md @@ -34,6 +34,7 @@ Currently the following endpoints are supported: - [x] [Policy Checks](https://www.terraform.io/docs/enterprise/api/policy-checks.html) - [ ] [Registry Modules](https://www.terraform.io/docs/enterprise/api/modules.html) - [x] [Runs](https://www.terraform.io/docs/enterprise/api/run.html) +- [x] [Run Triggers](https://www.terraform.io/docs/cloud/api/run-triggers.html) - [x] [SSH Keys](https://www.terraform.io/docs/enterprise/api/ssh-keys.html) - [x] [State Versions](https://www.terraform.io/docs/enterprise/api/state-versions.html) - [x] [Team Access](https://www.terraform.io/docs/enterprise/api/team-access.html) diff --git a/vendor/github.com/hashicorp/go-tfe/run_trigger.go b/vendor/github.com/hashicorp/go-tfe/run_trigger.go new file mode 100644 index 000000000..a4c6f4f65 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/run_trigger.go @@ -0,0 +1,177 @@ +package tfe + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" +) + +// Compile-time proof of interface implementation. +var _ RunTriggers = (*runTriggers)(nil) + +// RunTriggers describes all the Run Trigger +// related methods that the Terraform Cloud API supports. +// +// TFE API docs: +// https://www.terraform.io/docs/cloud/api/run-triggers.html +type RunTriggers interface { + // List all the run triggers within a workspace. + List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) + + // Create a new run trigger with the given options. + Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) + + // Read a run trigger by its ID. + Read(ctx context.Context, RunTriggerID string) (*RunTrigger, error) + + // Delete a run trigger by its ID. + Delete(ctx context.Context, RunTriggerID string) error +} + +// runTriggers implements RunTriggers. +type runTriggers struct { + client *Client +} + +// RunTriggerList represents a list of Run Triggers +type RunTriggerList struct { + *Pagination + Items []*RunTrigger +} + +// RunTrigger represents a run trigger. +type RunTrigger struct { + ID string `jsonapi:"primary,run-triggers"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + SourceableName string `jsonapi:"attr,sourceable-name"` + WorkspaceName string `jsonapi:"attr,workspace-name"` + + // Relations + // TODO: this will eventually need to be polymorphic + Sourceable *Workspace `jsonapi:"relation,sourceable"` + Workspace *Workspace `jsonapi:"relation,workspace"` +} + +// RunTriggerListOptions represents the options for listing +// run triggers. +type RunTriggerListOptions struct { + ListOptions + RunTriggerType *string `url:"filter[run-trigger][type]"` +} + +func (o RunTriggerListOptions) valid() error { + if !validString(o.RunTriggerType) { + return errors.New("run-trigger type is required") + } + if *o.RunTriggerType != "inbound" && *o.RunTriggerType != "outbound" { + return errors.New("invalid value for run-trigger type") + } + return nil +} + +// List all the run triggers associated with a workspace. +func (s *runTriggers) List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) { + if !validStringID(&workspaceID) { + return nil, errors.New("invalid value for workspace ID") + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + rtl := &RunTriggerList{} + err = s.client.do(ctx, req, rtl) + if err != nil { + return nil, err + } + + return rtl, nil +} + +// RunTriggerCreateOptions represents the options for +// creating a new run trigger. +type RunTriggerCreateOptions struct { + // For internal use only! + ID string `jsonapi:"primary,run-triggers"` + + // The source workspace + Sourceable *Workspace `jsonapi:"relation,sourceable"` +} + +func (o RunTriggerCreateOptions) valid() error { + if o.Sourceable == nil { + return errors.New("sourceable is required") + } + return nil +} + +// Creates a run trigger with the given options. +func (s *runTriggers) Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) { + if !validStringID(&workspaceID) { + return nil, errors.New("invalid value for workspace ID") + } + if err := options.valid(); err != nil { + return nil, err + } + + // Make sure we don't send a user provided ID. + options.ID = "" + + u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + rt := &RunTrigger{} + err = s.client.do(ctx, req, rt) + if err != nil { + return nil, err + } + + return rt, nil +} + +// Read a run trigger by its ID. +func (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigger, error) { + if !validStringID(&runTriggerID) { + return nil, errors.New("invalid value for run trigger ID") + } + + u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + rt := &RunTrigger{} + err = s.client.do(ctx, req, rt) + if err != nil { + return nil, err + } + + return rt, nil +} + +// Delete a run trigger by its ID. +func (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error { + if !validStringID(&runTriggerID) { + return errors.New("invalid value for run trigger ID") + } + + u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} diff --git a/vendor/github.com/hashicorp/go-tfe/tfe.go b/vendor/github.com/hashicorp/go-tfe/tfe.go index 94d706b41..ac90007cb 100644 --- a/vendor/github.com/hashicorp/go-tfe/tfe.go +++ b/vendor/github.com/hashicorp/go-tfe/tfe.go @@ -122,6 +122,7 @@ type Client struct { PolicySetParameters PolicySetParameters PolicySets PolicySets Runs Runs + RunTriggers RunTriggers SSHKeys SSHKeys StateVersions StateVersions Teams Teams @@ -215,6 +216,7 @@ func NewClient(cfg *Config) (*Client, error) { client.PolicySetParameters = &policySetParameters{client: client} client.PolicySets = &policySets{client: client} client.Runs = &runs{client: client} + client.RunTriggers = &runTriggers{client: client} client.SSHKeys = &sshKeys{client: client} client.StateVersions = &stateVersions{client: client} client.Teams = &teams{client: client} diff --git a/vendor/modules.txt b/vendor/modules.txt index ddc5ea30a..bde1e829b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -97,7 +97,7 @@ github.com/hashicorp/go-retryablehttp github.com/hashicorp/go-safetemp # github.com/hashicorp/go-slug v0.4.1 github.com/hashicorp/go-slug -# github.com/hashicorp/go-tfe v0.4.0 +# github.com/hashicorp/go-tfe v0.5.0 github.com/hashicorp/go-tfe # github.com/hashicorp/go-uuid v1.0.1 github.com/hashicorp/go-uuid diff --git a/website/docs/r/run_trigger.html.markdown b/website/docs/r/run_trigger.html.markdown new file mode 100644 index 000000000..971b20478 --- /dev/null +++ b/website/docs/r/run_trigger.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "tfe" +page_title: "Terraform Enterprise: tfe_notification_configuration" +sidebar_current: "docs-resource-tfe-run-trigger" +description: |- + Manages run triggers +--- + +# tfe_run_trigger + +~> **Important:** This feature is currently in beta and not suggested for production use. + +Terraform Cloud provides a way to connect your workspace to one or more workspaces within your organization, known as "source workspaces". +These connections, called run triggers, allow runs to queue automatically in your workspace on successful apply of runs in any of the source workspaces. +You can connect your workspace to up to 20 source workspaces. + +## Example Usage + +Basic usage: + +```hcl +resource "tfe_organization" "test-organization" { + name = "my-org-name" + email = "admin@company.com" +} + +resource "tfe_workspace" "test-workspace" { + name = "my-workspace-name" + organization = "${tfe_organization.test-organization.id}" +} + +resource "tfe_workspace" "test-sourceable" { + name = "my-sourceable-workspace-name" + organization = "${tfe_organization.test-organization.id}" +} + +resource "tfe_run_trigger" "test" { + workspace_external_id = "${tfe_workspace.test-workspace.external_id}" + sourceable_id = "${tfe_workspace.test-sourceable.external_id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `workspace_external_id` - (Required) The external id of the workspace that owns the run trigger. This is the workspace where runs will be triggered. +* `sourceable_id` - (Required) The external id of the sourceable. The sourceable must be a workspace. + +## Attributes Reference + +* `id` - The ID of the run trigger. + +## Import + +Run triggers can be imported; use `` as the import ID. For example: + +```shell +terraform import tfe_run_trigger.test rt-qV9JnKRkmtMa4zcA +``` diff --git a/website/tfe.erb b/website/tfe.erb index 347177bd4..5b57b9e42 100644 --- a/website/tfe.erb +++ b/website/tfe.erb @@ -61,6 +61,10 @@ tfe_policy_set_parameter + > + tfe_run_trigger + + > tfe_sentinel_policy