diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af083c..e52db38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 0.10.0 (April 24, 2021) + +ENHANCEMENTS: + +* Added attributes `auth_token`, `default` and `verify_ssl` of `runscope_bucket`. +* Added attributes `stop_on_failure`, `parent_environment_id` and `client_certificate` of `runscope_environment`. +* Only changing of `bucket_id` and `test_id` forces new `runscope_environment`. +* Added attributes `skipped` and `form_parameter` of `runscope_step`. +* Require arguments `method` and `url` of `runscope_step`, and don't force new step when they are changed. +* Added attributes `created_at`, `created_by` and `trigger_url` of `runscope_test`. +* Changing of test arguments no more forces new test. +* Added attribute `exported_at` of `runscope_schedule`. +* Require argument `interval` of `runscope_schedule` is required and no more forces new schedule. +* Changing of `interval` and `note` no more forces new schedule. + +BUG FIXES: + +* Fixed issue with step updating (appeared in 0.9.1). + ## 0.9.1 (April 10, 2021) NOTES: diff --git a/contrib/runscope.http b/contrib/runscope.http index 0b27d97..d4a3b95 100644 --- a/contrib/runscope.http +++ b/contrib/runscope.http @@ -1,9 +1,13 @@ ### Create bucket -POST https://api.runscope.com/buckets?name=terraform-provider-staging&team_uuid={{ team_uuid }} +POST https://api.runscope.com/buckets?name=goland-bucket&team_uuid={{ team_uuid }} Authorization: Bearer {{api_token}} > {% client.global.set("bucket_id", response.body.data.key); %} +### Get bucket +GET https://api.runscope.com/buckets/{{bucket_id}} +Authorization: Bearer {{api_token}} + ### List buckets GET https://api.runscope.com/buckets Authorization: Bearer {{api_token}} @@ -13,10 +17,140 @@ POST https://api.runscope.com/buckets/{{bucket_id}}/tests Authorization: Bearer {{api_token}} Content-Type: application/json -{"name": "Sample Name","description": "My test description"} +{ + "name": "goland-test" +} > {% client.global.set("test_id", response.body.data.id); %} +### Get test +GET https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}} +Authorization: Bearer {{api_token}} + +### Create test step +POST https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/steps +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "step_type": "request", + "method": "GET", + "url": "https://example.org" +} + +> {% client.global.set("step_id", (response.body.data.length > 0) ? response.body.data[response.body.data.length-1].id : ""); %} + +### Get test step +GET https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/steps/{{step_id}} +Authorization: Bearer {{api_token}} + +### Update test step +PUT https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/steps/{{step_id}} +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "step_type": "request", + "method": "POST", + "url": "https://yourapihere.com/example/path", + "body": "a", + "form": { + "b": ["1"] + } +} + +### Delete test step +DELETE https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/steps/{{step_id}} +Authorization: Bearer {{api_token}} + +### Delete test +DELETE https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}} +Authorization: Bearer {{api_token}} + +### Get test environment +GET https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/environments/{{environment_id}} +Authorization: Bearer {{api_token}} + +### List shared environments +GET https://api.runscope.com/buckets/{{bucket_id}}/environments +Authorization: Bearer {{api_token}} + +### Create shared environment +POST https://api.runscope.com/buckets/{{bucket_id}}/environments +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "name": "goland-shared-environment" +} + +> {% client.global.set("environment_id", response.body.data.id); %} + +### Create test environment +POST https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/environments +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "name": "goland-test-environment" +} + +> {% client.global.set("environment_id", response.body.data.id); %} + +### Update shared environment +PUT https://api.runscope.com/buckets/{{bucket_id}}/environments/{{environment_id}} +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "initial_variables": { + "a": "b" + } +} + +### Update test environment +PUT https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/environments/{{environment_id}} +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "test_id": {{test1_id}} +} + +### Get shared environment +GET https://api.runscope.com/buckets/{{bucket_id}}/environments/{{environment_id}} +Authorization: Bearer {{api_token}} + +### Create test schedule +POST https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/schedules +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "environment_id": "{{environment_id}}", + "interval": "1d" +} + +> {% client.global.set("schedule_id", response.body.data.id); %} + +### Update test schedule +PUT https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/schedules/{{schedule_id}} +Authorization: Bearer {{api_token}} +Content-Type: application/json + +{ + "environment_id": "{{environment_id}}", + "interval": "1d" +} + +### Delete schedule +DELETE https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}}/schedules/{{schedule_id}} +Authorization: Bearer {{api_token}} + +### Delete shared environment +DELETE https://api.runscope.com/buckets/{{bucket_id}}/environments/{{environment_id}} +Authorization: Bearer {{api_token}} + ### Delete test DELETE https://api.runscope.com/buckets/{{bucket_id}}/tests/{{test_id}} Authorization: Bearer {{api_token}} diff --git a/docs/data-sources/bucket.md b/docs/data-sources/bucket.md index bf3f736..b9ebef7 100644 --- a/docs/data-sources/bucket.md +++ b/docs/data-sources/bucket.md @@ -29,3 +29,7 @@ The following attributes are exported: * `id` - The unique key of the found bucket. * `team_uuid` - The team unique identifier that owns the bucket. * `name` - Type name of the bucket. +* `auth_token` - Bucket auth token if set. +* `default` - `true` if this bucket is the 'default' for a team. +* `verify_ssl` - `true` if this bucket is configured to verify ssl for requests made to it. +* `trigger_url` - URL to trigger a test run for all tests within a bucket. diff --git a/docs/resources/bucket.md b/docs/resources/bucket.md index 19dc24c..572e5f6 100644 --- a/docs/resources/bucket.md +++ b/docs/resources/bucket.md @@ -27,6 +27,10 @@ The following attributes are exported: * `name` - The name of this bucket. * `id` - The ID of this bucket. * `team_uuid` - Unique identifier for the team this bucket belongs to. +* `auth_token` - Bucket auth token if set. +* `default` - `true` if this bucket is the 'default' for a team. +* `verify_ssl` - `true` if this bucket is configured to verify ssl for requests made to it. +* `trigger_url` - URL to trigger a test run for all tests within a bucket. ## Import diff --git a/docs/resources/environment.md b/docs/resources/environment.md index 04a41c4..4adcd69 100644 --- a/docs/resources/environment.md +++ b/docs/resources/environment.md @@ -2,7 +2,7 @@ An [environment](https://www.runscope.com/docs/api/environments) resource. An [environment](https://www.runscope.com/docs/api-testing/environments) -is is a group of configuration settings (initial variables, locations, +is a group of configuration settings (initial variables, locations, notifications, integrations, etc.) used when running a test. Every test has at least one environment, but you can create additional environments as needed. For common settings (base URLs, API keys) @@ -90,8 +90,13 @@ The following arguments are supported: * `regions` - (Optional) A list of [Runscope regions](https://www.runscope.com/docs/regions) to execute test runs in when using this environment. * `remote_agent` - (Optional) Block describing the properties of [Remote Agent](https://www.runscope.com/docs/api/agents) to execute test runs in when using this environment. May be declared multiple times. Remote Agent documented below. +* `retry_on_failure` - (Optional) If this is set to true, an additional test run will be triggered immediately after a failed scheduled test run. +* `stop_on_failure` - (Optional) If this is set to true, test runs will stop executing after the first step that fails. All subsequent steps will be skipped. +* `verify_ssl` - (Optional) If this is set to false, tests using this environment won't verify SSL certificates. * `webhooks` - (Optional) A list of URL's to send results to when test runs using this environment finish. -* `email` - (Optional) Block describing settings for sending email notifications upon completion of a test run using this environment. May be declared multiple times. Emails block is documented below +* `email` - (Optional) Block describing settings for sending email notifications upon completion of a test run using this environment. May be declared multiple times. Emails block is documented below. +* `parent_environment_id` - (Optional) The parent environment to inherit from, applies only to test-specific environments. +* `client_certificate` - (Optional) Client certificate text available to be used in request authentication. PEM-encoded. Remote Agent (`remote_agent`) supports the following: @@ -100,16 +105,14 @@ Remote Agent (`remote_agent`) supports the following: Email (`email`) supports the following: -* `notify_all` - (Required) Send an email to all team members according to the `notify_on` rules. -* `notify_on` - (Required) Upon completion of a test run Runscope will send email notifications, allowed values: `all`, `failures`, `threshold` or `switch` -* `notify_threshold` (Required) An integer between 1 and 10 for use with the `notify_on settings`: only used when `threshold` and `switch` values are given -* `recipient` (Required) Block describing recipient, documented below. May be declared multiple times. +* `notify_all` - (Optional) Send an email to all team members according to the `notify_on` rules. +* `notify_on` - (Optional) Upon completion of a test run Runscope will send email notifications, allowed values: `all`, `failures`, `threshold` or `switch` +* `notify_threshold` (Optional) An integer between 1 and 10 for use with the `notify_on settings`: only used when `threshold` and `switch` values are given +* `recipient` (Optional) Block describing recipient, documented below. May be declared multiple times. Recipient (`recipient`), See [team api](https://www.runscope.com/docs/api/teams), supports the following: -* `name` - (Optional) The name of the person. -* `id` - (Optional) The unique identifier for this person's account. -* `email` - (Optional) The email address for this account. +* `id` - (Required) The unique identifier for this person's account. ## Attributes Reference diff --git a/docs/resources/schedule.md b/docs/resources/schedule.md index b75f58a..45d1ef5 100644 --- a/docs/resources/schedule.md +++ b/docs/resources/schedule.md @@ -9,8 +9,8 @@ using a unique Test-specific or Shared [Environment](environment.html). ```hcl resource "runscope_bucket" "bucket" { - name = "terraform-provider-test" - team_uuid = "d038db69-b5a9-45af-80d8-3be47c37e309" + name = "terraform-provider-test" + team_uuid = "d038db69-b5a9-45af-80d8-3be47c37e309" } resource "runscope_test" "test" { @@ -30,11 +30,11 @@ resource "runscope_environment" "environment" { } resource "runscope_schedule" "daily" { - bucket_id = runscope_bucket.bucket.id - test_id = runscope_test.test.id - interval = "1d" - note = "This is a daily schedule" - environment_id = runscope_environment.environment.id + bucket_id = runscope_bucket.bucket.id + test_id = runscope_test.test.id + interval = "1d" + note = "This is a daily schedule" + environment_id = runscope_environment.environment.id } ``` @@ -60,4 +60,4 @@ The following arguments are supported: The following attributes are exported: -* `id` - The ID of the schedule. +* `id` - The ID of the schedule. \ No newline at end of file diff --git a/docs/resources/test.md b/docs/resources/test.md index a7eecc2..152c54d 100644 --- a/docs/resources/test.md +++ b/docs/resources/test.md @@ -35,6 +35,10 @@ The following attributes are exported: * `id` - The unique identifier for the test. * `name` - The name of this test. * `description` - Human-readable description of the new test. +* `default_environment_id` - The default environment for the test. +* `created_at` - Date the test was created (in Epoch time). +* `created_by` - Details of the user who created this test. +* `trigger_url` - The trigger URL for this test. ## Import diff --git a/internal/provider/data_source_runscope_bucket.go b/internal/provider/data_source_runscope_bucket.go index 16ebb40..e2c9deb 100644 --- a/internal/provider/data_source_runscope_bucket.go +++ b/internal/provider/data_source_runscope_bucket.go @@ -24,6 +24,23 @@ func dataSourceRunscopeBucket() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "auth_token": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "default": { + Type: schema.TypeBool, + Computed: true, + }, + "verify_ssl": { + Type: schema.TypeBool, + Computed: true, + }, + "trigger_url": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -40,6 +57,10 @@ func dataSourceRunscopeBucketRead(ctx context.Context, d *schema.ResourceData, m d.SetId(bucket.Key) d.Set("name", bucket.Name) d.Set("team_uuid", bucket.Team.UUID) + d.Set("auth_token", bucket.AuthToken) + d.Set("default", bucket.Default) + d.Set("verify_ssl", bucket.VerifySSL) + d.Set("trigger_url", bucket.TriggerURL) return nil } diff --git a/internal/provider/data_source_runscope_bucket_test.go b/internal/provider/data_source_runscope_bucket_test.go index a250d55..28cbc22 100644 --- a/internal/provider/data_source_runscope_bucket_test.go +++ b/internal/provider/data_source_runscope_bucket_test.go @@ -21,6 +21,10 @@ func TestAccDataSourceRunscopeBucket(t *testing.T) { Config: fmt.Sprintf(testAccDataSourceRunscopeBucketConfig, bucketName, teamId), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.runscope_bucket.test", "name", bucketName), + resource.TestCheckResourceAttr("data.runscope_bucket.test", "team_uuid", teamId), + resource.TestCheckResourceAttrSet("data.runscope_bucket.test", "default"), + resource.TestCheckResourceAttrSet("data.runscope_bucket.test", "verify_ssl"), + resource.TestCheckResourceAttrSet("data.runscope_bucket.test", "trigger_url"), ), }, }, diff --git a/internal/provider/import_runscope_bucket_test.go b/internal/provider/import_runscope_bucket_test.go deleted file mode 100644 index 228b290..0000000 --- a/internal/provider/import_runscope_bucket_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package provider - -import ( - "fmt" - "os" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccImportRunscopeBucket(t *testing.T) { - teamId := os.Getenv("RUNSCOPE_TEAM_ID") - bucketName := testAccRandomBucketName() - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckBucketDestroy, - Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testAccImportRunscopeBucketConfig, bucketName, teamId), - }, - { - ResourceName: "runscope_bucket.test", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -const testAccImportRunscopeBucketConfig = ` -resource "runscope_bucket" "test" { - name = "%s" - team_uuid = "%s" -} -` diff --git a/internal/provider/resource_runscope_bucket.go b/internal/provider/resource_runscope_bucket.go index 1a2bc45..215c4a4 100644 --- a/internal/provider/resource_runscope_bucket.go +++ b/internal/provider/resource_runscope_bucket.go @@ -27,6 +27,23 @@ func resourceRunscopeBucket() *schema.Resource { Required: true, ForceNew: true, }, + "auth_token": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "default": { + Type: schema.TypeBool, + Computed: true, + }, + "verify_ssl": { + Type: schema.TypeBool, + Computed: true, + }, + "trigger_url": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -66,6 +83,10 @@ func resourceBucketRead(ctx context.Context, d *schema.ResourceData, meta interf d.SetId(bucket.Key) d.Set("name", bucket.Name) d.Set("team_uuid", bucket.Team.UUID) + d.Set("auth_token", bucket.AuthToken) + d.Set("default", bucket.Default) + d.Set("verify_ssl", bucket.VerifySSL) + d.Set("trigger_url", bucket.TriggerURL) return nil } diff --git a/internal/provider/resource_runscope_bucket_test.go b/internal/provider/resource_runscope_bucket_test.go index 4b1383c..91de79d 100644 --- a/internal/provider/resource_runscope_bucket_test.go +++ b/internal/provider/resource_runscope_bucket_test.go @@ -12,6 +12,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) +func init() { + resource.AddTestSweepers("runscope_bucket", &resource.Sweeper{ + Name: "runscope_bucket", + F: testAccSweepBuckets, + }) +} + func TestAccBucket_basic(t *testing.T) { var bucket runscope.Bucket teamId := os.Getenv("RUNSCOPE_TEAM_ID") @@ -23,43 +30,21 @@ func TestAccBucket_basic(t *testing.T) { CheckDestroy: testAccCheckBucketDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testRunscopeBucketConfigA, bucketName, teamId), + Config: fmt.Sprintf(testAccRunscopeBucketBasicConfig, bucketName, teamId), Check: resource.ComposeTestCheckFunc( testAccCheckBucketExists("runscope_bucket.bucket", &bucket), - resource.TestCheckResourceAttr( - "runscope_bucket.bucket", "name", bucketName), + resource.TestCheckResourceAttr("runscope_bucket.bucket", "name", bucketName), + resource.TestCheckResourceAttr("runscope_bucket.bucket", "team_uuid", teamId), + resource.TestCheckResourceAttrSet("runscope_bucket.bucket", "default"), + resource.TestCheckResourceAttrSet("runscope_bucket.bucket", "verify_ssl"), + resource.TestCheckResourceAttrSet("runscope_bucket.bucket", "trigger_url"), ), }, - }, - }) -} - -func init() { - resource.AddTestSweepers("runscope_bucket", &resource.Sweeper{ - Name: "runscope_bucket", - F: func(region string) error { - ctx := context.Background() - - client := runscope.NewClient(runscope.WithToken(os.Getenv("RUNSCOPE_ACCESS_TOKEN"))) - - buckets, err := client.Bucket.List(ctx) - if err != nil { - return fmt.Errorf("Couldn't list bucket for sweeping") - } - - for _, bucket := range buckets { - if !(strings.HasPrefix(bucket.Name, testAccBucketNamePrefix) || bucket.Name == "terraform-provider-test") { - continue - } - - opts := &runscope.BucketDeleteOpts{} - opts.Key = bucket.Key - if err := client.Bucket.Delete(ctx, opts); err != nil { - return err - } - } - - return nil + { + ResourceName: "runscope_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -69,53 +54,72 @@ func testAccCheckBucketDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*providerConfig).client for _, rs := range s.RootModule().Resources { - if rs.Type != "runscope_bucket" { - continue - } - - _, err := client.Bucket.Get(ctx, &runscope.BucketGetOpts{Key: rs.Primary.ID}) - - if err == nil { - return fmt.Errorf("Record %s still exists", rs.Primary.ID) + if rs.Type == "runscope_bucket" { + if _, err := client.Bucket.Get(ctx, &runscope.BucketGetOpts{Key: rs.Primary.ID}); err == nil { + return fmt.Errorf("Record %s still exists", rs.Primary.ID) + } } } return nil } -func testAccCheckBucketExists(n string, bucket *runscope.Bucket) resource.TestCheckFunc { +func testAccCheckBucketExists(n string, b *runscope.Bucket) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() - rs, ok := s.RootModule().Resources[n] + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { return fmt.Errorf("No Record ID is set") } client := testAccProvider.Meta().(*providerConfig).client - foundRecord, err := client.Bucket.Get(ctx, &runscope.BucketGetOpts{Key: rs.Primary.ID}) - + bucket, err := client.Bucket.Get(ctx, &runscope.BucketGetOpts{Key: rs.Primary.ID}) if err != nil { return err } - if foundRecord.Key != rs.Primary.ID { + if bucket.Key != rs.Primary.ID { return fmt.Errorf("Record not found") } - bucket = foundRecord + *b = *bucket return nil } } -const testRunscopeBucketConfigA = ` +const testAccRunscopeBucketBasicConfig = ` resource "runscope_bucket" "bucket" { name = "%s" team_uuid = "%s" }` + +func testAccSweepBuckets(_ string) error { + ctx := context.Background() + + client := runscope.NewClient(runscope.WithToken(os.Getenv("RUNSCOPE_ACCESS_TOKEN"))) + + buckets, err := client.Bucket.List(ctx) + if err != nil { + return fmt.Errorf("Couldn't list bucket for sweeping") + } + + for _, bucket := range buckets { + if !(strings.HasPrefix(bucket.Name, testAccBucketNamePrefix) || bucket.Name == "terraform-provider-test") { + continue + } + + opts := &runscope.BucketDeleteOpts{} + opts.Key = bucket.Key + if err := client.Bucket.Delete(ctx, opts); err != nil { + return err + } + } + + return nil +} diff --git a/internal/provider/resource_runscope_environment.go b/internal/provider/resource_runscope_environment.go index 3b80270..fb4bc25 100644 --- a/internal/provider/resource_runscope_environment.go +++ b/internal/provider/resource_runscope_environment.go @@ -29,23 +29,19 @@ func resourceRunscopeEnvironment() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ForceNew: false, }, "script": { Type: schema.TypeString, Optional: true, - ForceNew: false, }, "preserve_cookies": { Type: schema.TypeBool, Optional: true, - ForceNew: false, }, "initial_variables": { Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - ForceNew: false, }, "integrations": { Type: schema.TypeSet, @@ -77,6 +73,10 @@ func resourceRunscopeEnvironment() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "stop_on_failure": { + Type: schema.TypeBool, + Optional: true, + }, "verify_ssl": { Type: schema.TypeBool, Optional: true, @@ -135,6 +135,14 @@ func resourceRunscopeEnvironment() *schema.Resource { }, Optional: true, }, + "parent_environment_id": { + Type: schema.TypeString, + Optional: true, + }, + "client_certificate": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -142,6 +150,10 @@ func resourceRunscopeEnvironment() *schema.Resource { func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*providerConfig).client + if err := validateEnvironmentSchema(d); err != nil { + return err + } + opts := runscope.EnvironmentCreateOpts{} expandEnvironmentUriOpts(d, &opts.EnvironmentUriOpts) expandEnvironmentBase(d, &opts.EnvironmentBase) @@ -183,11 +195,14 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("initial_variables", env.InitialVariables) d.Set("integrations", env.Integrations) d.Set("retry_on_failure", env.RetryOnFailure) + d.Set("stop_on_failure", env.StopOnFailure) d.Set("verify_ssl", env.VerifySSL) d.Set("webhooks", env.Webhooks) if !env.Emails.IsDefault() { d.Set("email", flattenEmails(env.Emails)) } + d.Set("parent_environment_id", env.ParentEnvironmentId) + d.Set("client_certificate", env.ClientCertificate) return nil } @@ -195,6 +210,10 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*providerConfig).client + if err := validateEnvironmentSchema(d); err != nil { + return err + } + opts := runscope.EnvironmentUpdateOpts{} expandEnvironmentGetOpts(d, &opts.EnvironmentGetOpts) expandEnvironmentBase(d, &opts.EnvironmentBase) @@ -294,6 +313,9 @@ func expandEnvironmentBase(d *schema.ResourceData, opts *runscope.EnvironmentBas if v, ok := d.GetOk("retry_on_failure"); ok { opts.RetryOnFailure = v.(bool) } + if v, ok := d.GetOk("stop_on_failure"); ok { + opts.StopOnFailure = v.(bool) + } if v, ok := d.GetOk("webhooks"); ok { for _, w := range v.(*schema.Set).List() { opts.Webhooks = append(opts.Webhooks, w.(string)) @@ -306,14 +328,33 @@ func expandEnvironmentBase(d *schema.ResourceData, opts *runscope.EnvironmentBas opts.Emails.NotifyOn = ee["notify_on"].(string) opts.Emails.NotifyAll = ee["notify_all"].(bool) opts.Emails.NotifyThreshold = ee["notify_threshold"].(int) - for _, re := range ee["recipient"].(*schema.Set).List() { - rec := re.(map[string]interface{}) - opts.Emails.Recipients = append(opts.Emails.Recipients, runscope.Recipient{ - Id: rec["id"].(string), - Name: rec["name"].(string), - Email: rec["email"].(string), - }) + recipients := ee["recipient"].(*schema.Set).List() + if len(recipients) > 0 { + opts.Emails.Recipients = make([]runscope.Recipient, len(recipients)) + for i, recipient := range recipients { + r := recipient.(map[string]interface{}) + opts.Emails.Recipients[i].Id = r["id"].(string) + opts.Emails.Recipients[i].Name = r["name"].(string) + opts.Emails.Recipients[i].Email = r["email"].(string) + } } } } + if v, ok := d.GetOk("parent_environment_id"); ok { + opts.ParentEnvironmentId = v.(string) + } + if v, ok := d.GetOk("client_certificate"); ok { + opts.ClientCertificate = v.(string) + } +} + +func validateEnvironmentSchema(d *schema.ResourceData) diag.Diagnostics { + if _, hasTestId := d.GetOk("test_id"); hasTestId { + return nil + } + if _, hasParentEnvironmentId := d.GetOk("parent_environment_id"); !hasParentEnvironmentId { + return nil + } + + return diag.Errorf("parent_environment_id could be set only if test_id defined") } diff --git a/internal/provider/resource_runscope_environment_test.go b/internal/provider/resource_runscope_environment_test.go index b7227a3..8c42bc0 100644 --- a/internal/provider/resource_runscope_environment_test.go +++ b/internal/provider/resource_runscope_environment_test.go @@ -11,120 +11,87 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccEnvironment_basic(t *testing.T) { +func TestAccEnvironment_create_default_shared_environment(t *testing.T) { teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() environment := runscope.Environment{} resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckEnvironmentDestroy, Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testRunscopeEnvironmentMinimal, teamId), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "name", "test-environment"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "script", ""), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "preserve_cookies", "false"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "initial_variables.%", "0"), - resource.TestCheckNoResourceAttr("runscope_environment.environmentA", "integrations"), - resource.TestCheckNoResourceAttr("runscope_environment.environmentA", "regions"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "retry_on_failure", "false"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "verify_ssl", "true"), - resource.TestCheckNoResourceAttr("runscope_environment.environmentA", "webhooks"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.#", "0"), - ), - }, - { - Config: fmt.Sprintf(testRunscopeEnvironmentFull, teamId, teamId), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "name", "test-environment"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "script", "1;"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "preserve_cookies", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "initial_variables.%", "2"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "integrations.#", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "regions.#", "2"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "retry_on_failure", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "verify_ssl", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "webhooks.#", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.#", "0"), - ), - }, + testAccEnvironmentDefaultConfigStep(testAccEnvironmentSharedDefaultConfig, bucketId, teamId, &environment), }, }) } -func TestAccEnvironment_email(t *testing.T) { - teamID := os.Getenv("RUNSCOPE_TEAM_ID") +func TestAccEnvironment_create_custom_shared_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() environment := runscope.Environment{} + + recipientId, recipientIdOk := os.LookupEnv("RUNSCOPE_RECIPIENT_ID") + recipientName, recipientNameOk := os.LookupEnv("RUNSCOPE_RECIPIENT_NAME") + recipientEmail, recipientEmailOk := os.LookupEnv("RUNSCOPE_RECIPIENT_EMAIL") + + if !(recipientIdOk && recipientNameOk && recipientEmailOk) { + t.Skip("All of RUNSCOPE_RECIPIENT_ID, RUNSCOPE_RECIPIENT_NAME, RUNSCOPE_RECIPIENT_EMAIL should be set") + return + } + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckEnvironmentDestroy, Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testRunscopeEnvironmentConfigWithEmail, teamID), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "name", "test-environment"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "verify_ssl", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.#", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_all", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_on", "all"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_threshold", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.recipient.#", "0"), - ), - }, + testAccEnvironmentCustomConfigStep(testAccEnvironmentSharedCustomConfig, bucketId, teamId, recipientId, recipientName, recipientEmail, &environment), }, }) } -func TestAccEnvironment_update_email(t *testing.T) { - teamID := os.Getenv("RUNSCOPE_TEAM_ID") +func TestAccEnvironment_update_custom_shared_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() + environment := runscope.Environment{} + recipientId, recipientIdOk := os.LookupEnv("RUNSCOPE_RECIPIENT_ID") - if !recipientIdOk { - t.Skip("RUNSCOPE_RECIPIENT_ID should be set") + recipientName, recipientNameOk := os.LookupEnv("RUNSCOPE_RECIPIENT_NAME") + recipientEmail, recipientEmailOk := os.LookupEnv("RUNSCOPE_RECIPIENT_EMAIL") + + if !(recipientIdOk && recipientNameOk && recipientEmailOk) { + t.Skip("All of RUNSCOPE_RECIPIENT_ID, RUNSCOPE_RECIPIENT_NAME, RUNSCOPE_RECIPIENT_EMAIL should be set") return } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckEnvironmentDestroy, + Steps: []resource.TestStep{ + testAccEnvironmentDefaultConfigStep(testAccEnvironmentSharedDefaultConfig, bucketId, teamId, &environment), + testAccEnvironmentCustomConfigStep(testAccEnvironmentSharedCustomConfig, bucketId, teamId, recipientId, recipientName, recipientEmail, &environment), + }, + }) +} + +func TestAccEnvironment_create_default_test_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() environment := runscope.Environment{} resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckEnvironmentDestroy, Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testRunscopeEnvironmentMinimal, teamID), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - testAccCheckEnvironmentEmail(&environment, false, "", 0, 0), - ), - }, - { - Config: fmt.Sprintf(testRunscopeEnvironmentConfigWithEmailRecipient, teamID, recipientId), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - testAccCheckEnvironmentEmail(&environment, true, "all", 1, 1), - ), - }, - { - Config: fmt.Sprintf(testRunscopeEnvironmentMinimal, teamID), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - testAccCheckEnvironmentEmail(&environment, false, "", 0, 0), - ), - }, + testAccEnvironmentDefaultConfigStep(testAccEnvironmentTestDefaultConfig, bucketId, teamId, &environment), }, }) } -func TestAccEnvironment_email_recipient(t *testing.T) { - teamId, ok := os.LookupEnv("RUNSCOPE_TEAM_ID") - if !ok { - t.Skip("RUNSCOPE_TEAM_ID should be set") - return - } +func TestAccEnvironment_create_custom_test_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() + environment := runscope.Environment{} recipientId, recipientIdOk := os.LookupEnv("RUNSCOPE_RECIPIENT_ID") recipientName, recipientNameOk := os.LookupEnv("RUNSCOPE_RECIPIENT_NAME") @@ -134,49 +101,51 @@ func TestAccEnvironment_email_recipient(t *testing.T) { t.Skip("All of RUNSCOPE_RECIPIENT_ID, RUNSCOPE_RECIPIENT_NAME, RUNSCOPE_RECIPIENT_EMAIL should be set") return } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckEnvironmentDestroy, + Steps: []resource.TestStep{ + testAccEnvironmentCustomConfigStep(testAccEnvironmentTestCustomConfig, bucketId, teamId, recipientId, recipientName, recipientEmail, &environment), + }, + }) +} +func TestAccEnvironment_update_custom_test_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() environment := runscope.Environment{} + recipientId, recipientIdOk := os.LookupEnv("RUNSCOPE_RECIPIENT_ID") + recipientName, recipientNameOk := os.LookupEnv("RUNSCOPE_RECIPIENT_NAME") + recipientEmail, recipientEmailOk := os.LookupEnv("RUNSCOPE_RECIPIENT_EMAIL") + + if !(recipientIdOk && recipientNameOk && recipientEmailOk) { + t.Skip("All of RUNSCOPE_RECIPIENT_ID, RUNSCOPE_RECIPIENT_NAME, RUNSCOPE_RECIPIENT_EMAIL should be set") + return + } + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckEnvironmentDestroy, Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testRunscopeEnvironmentConfigWithEmailRecipient, teamId, recipientId), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentA", &environment), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "name", "test-environment"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.#", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_all", "true"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_on", "all"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.notify_threshold", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.recipient.#", "1"), - resource.TestCheckResourceAttr("runscope_environment.environmentA", "email.0.recipient.0.id", recipientId), - testAccCheckEnvironmentRecipient(&environment, recipientId, recipientName, recipientEmail), - ), - }, + testAccEnvironmentDefaultConfigStep(testAccEnvironmentTestDefaultConfig, bucketId, teamId, &environment), + testAccEnvironmentCustomConfigStep(testAccEnvironmentTestCustomConfig, bucketId, teamId, recipientId, recipientName, recipientEmail, &environment), }, }) } -func TestAccEnvironment_do_not_verify_ssl(t *testing.T) { - teamID := os.Getenv("RUNSCOPE_TEAM_ID") +func TestAccEnvironment_create_nested_test_environment(t *testing.T) { + teamId := os.Getenv("RUNSCOPE_TEAM_ID") + bucketId := testAccRandomBucketName() environment := runscope.Environment{} resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckEnvironmentDestroy, Steps: []resource.TestStep{ - { - Config: fmt.Sprintf(testRunscopeEnvironmentConfigB, teamID), - Check: resource.ComposeTestCheckFunc( - testAccCheckEnvironmentExists("runscope_environment.environmentB", &environment), - resource.TestCheckResourceAttr( - "runscope_environment.environmentB", "name", "test-no-ssl"), - resource.TestCheckResourceAttr( - "runscope_environment.environmentB", "verify_ssl", "false")), - }, + testAccEnvironmentDefaultConfigStep(testAccEnvironmentTestNestedConfig, bucketId, teamId, &environment), }, }) } @@ -238,24 +207,6 @@ func testAccCheckEnvironmentExists(n string, e *runscope.Environment) resource.T } } -func testAccCheckEnvironmentEmail(e *runscope.Environment, expectedNotifyAll bool, expectedNotifyOn string, expectedNotifyThreshold int, expectedNumRecipients int) resource.TestCheckFunc { - return func(s *terraform.State) error { - if e.Emails.NotifyAll != expectedNotifyAll { - return fmt.Errorf("expected NotifyAll '%v', got '%v'", expectedNotifyAll, e.Emails.NotifyAll) - } - if e.Emails.NotifyOn != expectedNotifyOn { - return fmt.Errorf("expected NotifyOn '%s', got '%s'", expectedNotifyOn, e.Emails.NotifyOn) - } - if e.Emails.NotifyThreshold != expectedNotifyThreshold { - return fmt.Errorf("expected NotifyThreshold '%d', got '%d'", expectedNotifyThreshold, e.Emails.NotifyThreshold) - } - if len(e.Emails.Recipients) != expectedNumRecipients { - return fmt.Errorf("expected '%d' recipients, got '%d'", expectedNumRecipients, len(e.Emails.Recipients)) - } - return nil - } -} - func testAccCheckEnvironmentRecipient(e *runscope.Environment, expectedId string, expectedName string, expectedEmail string) resource.TestCheckFunc { return func(s *terraform.State) error { id := e.Emails.Recipients[0].Id @@ -275,113 +226,244 @@ func testAccCheckEnvironmentRecipient(e *runscope.Environment, expectedId string } } -const testRunscopeEnvironmentMinimal = ` -resource "runscope_bucket" "bucket" { - name = "terraform-provider-test" - team_uuid = "%s" +func testAccEnvironmentDefaultConfigStep(config, bucketId, teamId string, environment *runscope.Environment) resource.TestStep { + return resource.TestStep{ + Config: fmt.Sprintf(config, bucketId, teamId), + Check: resource.ComposeTestCheckFunc( + testAccCheckEnvironmentExists("runscope_environment.environment", environment), + resource.TestCheckResourceAttr("runscope_environment.environment", "name", "environment"), + resource.TestCheckResourceAttr("runscope_environment.environment", "script", ""), + resource.TestCheckResourceAttr("runscope_environment.environment", "preserve_cookies", "false"), + resource.TestCheckResourceAttr("runscope_environment.environment", "initial_variables.%", "0"), + resource.TestCheckNoResourceAttr("runscope_environment.environment", "integrations"), + resource.TestCheckNoResourceAttr("runscope_environment.environment", "regions"), + resource.TestCheckResourceAttr("runscope_environment.environment", "retry_on_failure", "false"), + resource.TestCheckResourceAttr("runscope_environment.environment", "stop_on_failure", "false"), + resource.TestCheckResourceAttr("runscope_environment.environment", "verify_ssl", "true"), + resource.TestCheckNoResourceAttr("runscope_environment.environment", "webhooks"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.#", "0"), + resource.TestCheckResourceAttr("runscope_environment.environment", "parent_environment_id", ""), + resource.TestCheckResourceAttr("runscope_environment.environment", "client_certificate", ""), + ), + } } -resource "runscope_environment" "environmentA" { - bucket_id = runscope_bucket.bucket.id - name = "test-environment" +func testAccEnvironmentCustomConfigStep(config, bucketId, teamId, recipientId, recipientName, recipientEmail string, environment *runscope.Environment) resource.TestStep { + return resource.TestStep{ + Config: fmt.Sprintf(config, bucketId, teamId, recipientId), + Check: resource.ComposeTestCheckFunc( + testAccCheckEnvironmentExists("runscope_environment.environment", environment), + resource.TestCheckResourceAttr("runscope_environment.environment", "name", "environment"), + resource.TestCheckResourceAttr("runscope_environment.environment", "script", "1;"), + resource.TestCheckResourceAttr("runscope_environment.environment", "preserve_cookies", "true"), + resource.TestCheckResourceAttr("runscope_environment.environment", "initial_variables.%", "2"), + resource.TestCheckResourceAttr("runscope_environment.environment", "integrations.#", "1"), + resource.TestCheckResourceAttr("runscope_environment.environment", "regions.#", "2"), + resource.TestCheckResourceAttr("runscope_environment.environment", "retry_on_failure", "true"), + resource.TestCheckResourceAttr("runscope_environment.environment", "stop_on_failure", "true"), + resource.TestCheckResourceAttr("runscope_environment.environment", "verify_ssl", "true"), + resource.TestCheckResourceAttr("runscope_environment.environment", "webhooks.#", "1"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.#", "1"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.0.notify_all", "true"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.0.notify_on", "all"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.0.notify_threshold", "1"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.0.recipient.#", "1"), + resource.TestCheckResourceAttr("runscope_environment.environment", "email.0.recipient.0.id", recipientId), + testAccCheckEnvironmentRecipient(environment, recipientId, recipientName, recipientEmail), + resource.TestCheckResourceAttr("runscope_environment.environment", "parent_environment_id", ""), + resource.TestCheckResourceAttr("runscope_environment.environment", "client_certificate", testAccEnvironmentClientCertficate), + ), + } } + +const testAccEnvironmentClientCertficate = `-----BEGIN CERTIFICATE----- +MIIDDTCCAfWgAwIBAgIUd8JoBoWhPUHSqxgMvDqTgBFmHTswDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UECwwLZXhhbXBsZS5vcmcwHhcNMjEwNDExMjEyMzE5WhcNMzEw +NDA5MjEyMzE5WjAWMRQwEgYDVQQLDAtleGFtcGxlLm9yZzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAK1FEXUn5I85YIxc88ql7T4Vn1nIdcKsrGfMR3oH +ZEFTXP/TG0GCgAPLgCmBCLJZUJVcgpENiIzqO+JoZ0daBm5Cf6Y/ZZFX/VXxZtSV +hsLnNozf3IXl5T00JXPg2JYTSqZZfBbAREQQAZuucsSgP4t7kP0Q9L/fiCUkEGRe +jU4oncwRnyNv85qf6rRHtK0+REdvMm56oVHqvXR4k5EvtEpf1qOfeeuJ+ZCh/0yu +zUarhY5jwdXircRiDmWfkk3PhqP5lsBiaDbXemLb0DDWDBzFGj9aebptp0bfSZ/0 +KsQdYD4MM0eDep9a4JvT0nxXZ+RvWb3o081i+pz/AcT85nkCAwEAAaNTMFEwHQYD +VR0OBBYEFLzbHqu0i6Kkz2kto5olS3nmR9OEMB8GA1UdIwQYMBaAFLzbHqu0i6Kk +z2kto5olS3nmR9OEMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +ADmmcH+Ie8KscvEuNm5kWVCidixtm31atlFtLUHLRn9898e5k5FviGY13RS9NAJB +0h5PLwIytm2+FkjBZ1hukWCQgijrKKKfdcKzes6MQglGru8RqUqlP9r/T2ly+YpM +D6vzUeIN4QT1fxlR017n8OFATsifdaFMBezST84wIoGWSf+njIqsgGpMhn/8/Xb3 +5cQ9MngrRvpmwlBNgujPNo08X9MxduLX4Yz19jeXufG72ebrnriTxRxJNTW+j/Pt +JlalxgJ13chSeQkb2U9/1Es4PGfYvNXIJ6YmOeT8O6CIt+cHK5yAiarKJE59GJPJ +upwBSEp4QTHSUGFgnR6PZfQ= +-----END CERTIFICATE----- ` -const testRunscopeEnvironmentFull = ` -resource "runscope_test" "test" { +const testAccEnvironmentSharedDefaultConfig = ` +resource "runscope_bucket" "bucket" { + name = "%s" + team_uuid = "%s" +} + +resource "runscope_environment" "environment" { bucket_id = runscope_bucket.bucket.id - name = "runscope test" - description = "This is a test test..." + name = "environment" } +` +const testAccEnvironmentSharedCustomConfig = ` resource "runscope_bucket" "bucket" { - name = "terraform-provider-test" - team_uuid = "%s" + name = "%[1]s" + team_uuid = "%[2]s" } data "runscope_integration" "slack" { - team_uuid = "%s" + team_uuid = "%[2]s" type = "slack" } -resource "runscope_environment" "environmentA" { - bucket_id = runscope_bucket.bucket.id - name = "test-environment" - - script = "1;" - - preserve_cookies = true - - initial_variables = { - var1 = "true" - var2 = "value2" - } - - integrations = [ - data.runscope_integration.slack.id, - ] +resource "runscope_environment" "environment" { + bucket_id = runscope_bucket.bucket.id + name = "environment" + + script = "1;" + + preserve_cookies = true + + initial_variables = { + var1 = "true" + var2 = "value2" + } + + integrations = [ + data.runscope_integration.slack.id, + ] + + regions = ["us1", "eu1"] + + remote_agent { + name = "test agent" + uuid = "arbitrary-string" + } + + retry_on_failure = true + stop_on_failure = true + webhooks = ["https://example.com"] + + email { + notify_all = true + notify_on = "all" + notify_threshold = 1 + recipient { + id = "%[3]s" + } + } + + client_certificate = < 0 { @@ -107,3 +137,19 @@ func expandStepAuth(auth []interface{}) runscope.StepAuth { } return result } + +func flattenTime(t time.Time) string { + if t.Unix() == 0 { + return "" + } + + return t.Format(time.RFC1123) +} + +func flattenCreatedBy(c *runscope.CreatedBy) []map[string]interface{} { + return []map[string]interface{}{{ + "id": c.Id, + "name": c.Name, + "email": c.Email, + }} +} diff --git a/internal/runscope/bucket.go b/internal/runscope/bucket.go index 8de812a..7fa53b3 100644 --- a/internal/runscope/bucket.go +++ b/internal/runscope/bucket.go @@ -8,9 +8,13 @@ import ( ) type Bucket struct { - Key string - Name string - Team Team + Key string + Name string + Team Team + AuthToken string + Default bool + VerifySSL bool + TriggerURL string } type Team struct { @@ -22,7 +26,7 @@ type BucketClient struct { client *Client } -func BucketFromSchema(s schema.Bucket) *Bucket { +func BucketFromSchema(s *schema.Bucket) *Bucket { return &Bucket{ Key: s.Key, Name: s.Name, @@ -30,6 +34,10 @@ func BucketFromSchema(s schema.Bucket) *Bucket { Name: s.Team.Name, UUID: s.Team.Id, }, + AuthToken: s.AuthToken, + Default: s.Default, + VerifySSL: s.VerifySSL, + TriggerURL: s.TriggerURL, } } @@ -57,7 +65,7 @@ func (c *BucketClient) Create(ctx context.Context, opts *BucketCreateOpts) (*Buc return nil, err } - return BucketFromSchema(resp.Bucket), err + return BucketFromSchema(&resp.Bucket), err } type BucketGetOpts struct { @@ -80,7 +88,7 @@ func (c *BucketClient) Get(ctx context.Context, opts *BucketGetOpts) (*Bucket, e return nil, err } - return BucketFromSchema(resp.Bucket), nil + return BucketFromSchema(&resp.Bucket), nil } func (c *BucketClient) List(ctx context.Context) ([]*Bucket, error) { @@ -96,7 +104,7 @@ func (c *BucketClient) List(ctx context.Context) ([]*Bucket, error) { } buckets := make([]*Bucket, len(resp.Buckets), len(resp.Buckets)) for i, bucket := range resp.Buckets { - buckets[i] = BucketFromSchema(bucket) + buckets[i] = BucketFromSchema(&bucket) } return buckets, nil diff --git a/internal/runscope/environment.go b/internal/runscope/environment.go index f4cfbc4..94eb3cf 100644 --- a/internal/runscope/environment.go +++ b/internal/runscope/environment.go @@ -7,17 +7,56 @@ import ( ) type EnvironmentBase struct { - Name string - Script string - PreserveCookies bool - InitialVariables map[string]string - Integrations []string - Regions []string - RemoteAgents []RemoteAgent - RetryOnFailure bool - VerifySSL bool - Webhooks []string - Emails Emails + Name string + Script string + PreserveCookies bool + InitialVariables map[string]string + Integrations []string + Regions []string + RemoteAgents []RemoteAgent + RetryOnFailure bool + StopOnFailure bool + VerifySSL bool + Webhooks []string + Emails Emails + ParentEnvironmentId string + ClientCertificate string +} + +func (eb *EnvironmentBase) setRequest(seb *schema.EnvironmentBase) { + seb.Name = eb.Name + seb.Script = eb.Script + seb.PreserveCookies = eb.PreserveCookies + seb.InitialVariables = eb.InitialVariables + seb.Regions = eb.Regions + seb.RetryOnFailure = eb.RetryOnFailure + seb.StopOnFailure = eb.StopOnFailure + seb.VerifySSL = eb.VerifySSL + seb.Webhooks = eb.Webhooks + seb.Emails = schema.Emails{ + NotifyAll: eb.Emails.NotifyAll, + NotifyOn: eb.Emails.NotifyOn, + NotifyThreshold: eb.Emails.NotifyThreshold, + } + for _, id := range eb.Integrations { + seb.Integrations = append(seb.Integrations, schema.EnvironmentIntegration{Id: id}) + } + for _, agent := range eb.RemoteAgents { + seb.RemoteAgents = append(seb.RemoteAgents, schema.RemoteAgent{ + Name: agent.Name, + UUID: agent.UUID, + }) + } + seb.Emails.Recipients = make([]schema.Recipient, len(eb.Emails.Recipients)) + for i, recipient := range eb.Emails.Recipients { + seb.Emails.Recipients[i] = schema.Recipient{ + Id: recipient.Id, + Name: recipient.Name, + Email: recipient.Email, + } + } + seb.ParentEnvironmentId = eb.ParentEnvironmentId + seb.ClientCertificate = eb.ClientCertificate } type Environment struct { @@ -60,6 +99,7 @@ func EnvironmentFromSchema(s *schema.Environment) *Environment { env.InitialVariables = s.InitialVariables env.Regions = s.Regions env.RetryOnFailure = s.RetryOnFailure + env.StopOnFailure = s.StopOnFailure env.VerifySSL = s.VerifySSL env.Webhooks = s.Webhooks env.Emails = Emails{ @@ -84,6 +124,8 @@ func EnvironmentFromSchema(s *schema.Environment) *Environment { Email: r.Email, }) } + env.ParentEnvironmentId = s.ParentEnvironmentId + env.ClientCertificate = s.ClientCertificate return env } @@ -96,7 +138,7 @@ func (opts *EnvironmentUriOpts) BaseURL() string { if opts.TestId == "" { return fmt.Sprintf("/buckets/%s/environments", opts.BucketId) } - return fmt.Sprintf("/buckets/%s/test/%s/environments", opts.BucketId, opts.TestId) + return fmt.Sprintf("/buckets/%s/tests/%s/environments", opts.BucketId, opts.TestId) } type EnvironmentCreateOpts struct { @@ -106,35 +148,7 @@ type EnvironmentCreateOpts struct { func (c *EnvironmentClient) Create(ctx context.Context, opts *EnvironmentCreateOpts) (*Environment, error) { body := &schema.EnvironmentCreateRequest{} - body.Name = opts.Name - body.Script = opts.Script - body.PreserveCookies = opts.PreserveCookies - body.InitialVariables = opts.InitialVariables - body.Regions = opts.Regions - body.RetryOnFailure = opts.RetryOnFailure - body.VerifySSL = opts.VerifySSL - body.Webhooks = opts.Webhooks - body.Emails = schema.Emails{ - NotifyAll: opts.Emails.NotifyAll, - NotifyOn: opts.Emails.NotifyOn, - NotifyThreshold: opts.Emails.NotifyThreshold, - } - for _, id := range opts.Integrations { - body.Integrations = append(body.Integrations, schema.EnvironmentIntegration{Id: id}) - } - for _, agent := range opts.RemoteAgents { - body.RemoteAgents = append(body.RemoteAgents, schema.RemoteAgent{ - Name: agent.Name, - UUID: agent.UUID, - }) - } - for _, recipient := range opts.Emails.Recipients { - body.Emails.Recipients = append(body.Emails.Recipients, schema.Recipient{ - Id: recipient.Id, - Name: recipient.Name, - Email: recipient.Email, - }) - } + opts.EnvironmentBase.setRequest(&body.EnvironmentBase) req, err := c.client.NewRequest(ctx, "POST", opts.BaseURL(), &body) if err != nil { @@ -181,35 +195,8 @@ type EnvironmentUpdateOpts struct { func (c *EnvironmentClient) Update(ctx context.Context, opts *EnvironmentUpdateOpts) (*Environment, error) { body := &schema.EnvironmentUpdateRequest{} - body.Name = opts.Name - body.Script = opts.Script - body.PreserveCookies = opts.PreserveCookies - body.InitialVariables = opts.InitialVariables - body.Regions = opts.Regions - body.RetryOnFailure = opts.RetryOnFailure - body.VerifySSL = opts.VerifySSL - body.Webhooks = opts.Webhooks - body.Emails = schema.Emails{ - NotifyAll: opts.Emails.NotifyAll, - NotifyOn: opts.Emails.NotifyOn, - NotifyThreshold: opts.Emails.NotifyThreshold, - } - for _, id := range opts.Integrations { - body.Integrations = append(body.Integrations, schema.EnvironmentIntegration{Id: id}) - } - for _, agent := range opts.RemoteAgents { - body.RemoteAgents = append(body.RemoteAgents, schema.RemoteAgent{ - Name: agent.Name, - UUID: agent.UUID, - }) - } - for _, recipient := range opts.Emails.Recipients { - body.Emails.Recipients = append(body.Emails.Recipients, schema.Recipient{ - Id: recipient.Id, - Name: recipient.Name, - Email: recipient.Email, - }) - } + opts.EnvironmentBase.setRequest(&body.EnvironmentBase) + req, err := c.client.NewRequest(ctx, "PUT", opts.URL(), &body) if err != nil { return nil, err diff --git a/internal/runscope/schedule.go b/internal/runscope/schedule.go index 8bdb408..ae82ef3 100644 --- a/internal/runscope/schedule.go +++ b/internal/runscope/schedule.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/terraform-providers/terraform-provider-runscope/internal/runscope/schema" + "time" ) type ScheduleBase struct { @@ -14,7 +15,8 @@ type ScheduleBase struct { type Schedule struct { ScheduleBase - Id string + Id string + ExportedAt time.Time } type ScheduleClient struct { @@ -27,20 +29,21 @@ func ScheduleFromSchema(s *schema.Schedule) *Schedule { schedule.EnvironmentId = s.EnvironmentId schedule.Interval = s.Interval schedule.Note = s.Note + schedule.ExportedAt = time.Unix(s.ExportedAt, 0) return schedule } -type ScheduleBaseOpts struct { +type ScheduleURLOpts struct { BucketId string TestId string } -func (opts *ScheduleBaseOpts) URL() string { +func (opts *ScheduleURLOpts) URL() string { return fmt.Sprintf("/buckets/%s/tests/%s/schedules", opts.BucketId, opts.TestId) } type ScheduleCreateOpts struct { - ScheduleBaseOpts + ScheduleURLOpts ScheduleBase } @@ -65,12 +68,12 @@ func (c *ScheduleClient) Create(ctx context.Context, opts *ScheduleCreateOpts) ( } type ScheduleGetOpts struct { - ScheduleBaseOpts + ScheduleURLOpts Id string } func (opts *ScheduleGetOpts) URL() string { - return fmt.Sprintf("%s/%s", opts.ScheduleBaseOpts.URL(), opts.Id) + return fmt.Sprintf("%s/%s", opts.ScheduleURLOpts.URL(), opts.Id) } func (c *ScheduleClient) Get(ctx context.Context, opts *ScheduleGetOpts) (*Schedule, error) { @@ -88,6 +91,35 @@ func (c *ScheduleClient) Get(ctx context.Context, opts *ScheduleGetOpts) (*Sched return ScheduleFromSchema(&resp.Schedule), err } +type ScheduleUpdateOpts struct { + ScheduleGetOpts + ScheduleBase +} + +func (opts *ScheduleUpdateOpts) setRequest(body *schema.ScheduleUpdateRequest) { + body.Note = opts.Note + body.Interval = opts.Interval + body.EnvironmentId = opts.EnvironmentId +} + +func (c *ScheduleClient) Update(ctx context.Context, opts *ScheduleUpdateOpts) (*Schedule, error) { + body := schema.ScheduleUpdateRequest{} + opts.setRequest(&body) + + req, err := c.client.NewRequest(ctx, "PUT", opts.URL(), &body) + if err != nil { + return nil, err + } + + var resp schema.ScheduleUpdateResponse + err = c.client.Do(req, &resp) + if err != nil { + return nil, err + } + + return ScheduleFromSchema(&resp.Schedule), err +} + type ScheduleDeleteOpts struct { ScheduleGetOpts } diff --git a/internal/runscope/schema/bucket.go b/internal/runscope/schema/bucket.go index 36af8c0..5c69771 100644 --- a/internal/runscope/schema/bucket.go +++ b/internal/runscope/schema/bucket.go @@ -1,9 +1,13 @@ package schema type Bucket struct { - Key string `json:"key"` - Name string `json:"name"` - Team BucketTeam `json:"team"` + Key string `json:"key"` + Name string `json:"name"` + Team BucketTeam `json:"team"` + AuthToken string `json:"auth_token"` + Default bool `json:"default"` + VerifySSL bool `json:"verify_ssl"` + TriggerURL string `json:"trigger_url"` } type BucketTeam struct { diff --git a/internal/runscope/schema/environment.go b/internal/runscope/schema/environment.go index d3e0787..8f29fd0 100644 --- a/internal/runscope/schema/environment.go +++ b/internal/runscope/schema/environment.go @@ -1,17 +1,20 @@ package schema type EnvironmentBase struct { - Name string `json:"name"` - Script string `json:"script"` - PreserveCookies bool `json:"preserve_cookies"` - InitialVariables map[string]string `json:"initial_variables"` - Integrations []EnvironmentIntegration `json:"integrations"` - Regions []string `json:"regions"` - RemoteAgents []RemoteAgent `json:"remote_agents"` - RetryOnFailure bool `json:"retry_on_failure"` - VerifySSL bool `json:"verify_ssl"` - Webhooks []string `json:"webhooks"` - Emails Emails `json:"emails"` + Name string `json:"name"` + Script string `json:"script"` + PreserveCookies bool `json:"preserve_cookies"` + InitialVariables map[string]string `json:"initial_variables"` + Integrations []EnvironmentIntegration `json:"integrations"` + Regions []string `json:"regions"` + RemoteAgents []RemoteAgent `json:"remote_agents"` + RetryOnFailure bool `json:"retry_on_failure"` + StopOnFailure bool `json:"stop_on_failure"` + VerifySSL bool `json:"verify_ssl"` + Webhooks []string `json:"webhooks"` + Emails Emails `json:"emails"` + ParentEnvironmentId string `json:"parent_environment_id,omitempty"` + ClientCertificate string `json:"client_certificate"` } type Environment struct { diff --git a/internal/runscope/schema/schedule.go b/internal/runscope/schema/schedule.go index 138b6ac..169d0b0 100644 --- a/internal/runscope/schema/schedule.go +++ b/internal/runscope/schema/schedule.go @@ -8,7 +8,8 @@ type ScheduleBase struct { type Schedule struct { ScheduleBase - Id string `json:"id"` + Id string `json:"id"` + ExportedAt int64 `json:"exported_at"` } type ScheduleGetResponse struct { @@ -22,3 +23,11 @@ type ScheduleCreateRequest struct { type ScheduleCreateResponse struct { Schedule `json:"data"` } + +type ScheduleUpdateRequest struct { + ScheduleBase +} + +type ScheduleUpdateResponse struct { + Schedule `json:"data"` +} diff --git a/internal/runscope/schema/step.go b/internal/runscope/schema/step.go index c2d8e17..964e867 100644 --- a/internal/runscope/schema/step.go +++ b/internal/runscope/schema/step.go @@ -9,9 +9,11 @@ type StepBase struct { Headers map[string][]string `json:"headers"` Auth StepAuth `json:"auth"` Body string `json:"body"` + Form map[string][]string `json:"form"` Scripts []string `json:"scripts"` BeforeScripts []string `json:"before_scripts"` Note string `json:"note"` + Skipped bool `json:"skipped"` } type Step struct { @@ -55,5 +57,5 @@ type StepUpdateRequest struct { } type StepUpdateResponse struct { - Step []Step `json:"data"` + Step Step `json:"data"` } diff --git a/internal/runscope/schema/test_.go b/internal/runscope/schema/test_.go index 50f3a83..4b75587 100644 --- a/internal/runscope/schema/test_.go +++ b/internal/runscope/schema/test_.go @@ -12,14 +12,23 @@ type TestBase struct { type Test struct { TestBase - Id string `json:"id"` - Steps []TestStep `json:"steps"` + Id string `json:"id"` + Steps []TestStep `json:"steps"` + CreatedAt int64 `json:"created_at"` + CreatedBy CreatedBy `json:"created_by"` + TriggerURL string `json:"trigger_url"` } type TestStep struct { Id string `json:"id"` } +type CreatedBy struct { + Id string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + type TestGetResponse struct { Test `json:"data"` } diff --git a/internal/runscope/schema/test_test.go b/internal/runscope/schema/test_test.go index fa6251e..5c7d0e0 100644 --- a/internal/runscope/schema/test_test.go +++ b/internal/runscope/schema/test_test.go @@ -31,6 +31,20 @@ func TestUnmarshallTestCreateResponse(t *testing.T) { if resp.Test.DefaultEnvironmentId != expectedDefaultEnvironmentId { t.Errorf("expected DefaultEnvironmentId '%s', got '%s'", expectedDefaultEnvironmentId, resp.Test.DefaultEnvironmentId) } + + var expectedCreatedAt int64 = 1438832081 + if resp.CreatedAt != expectedCreatedAt { + t.Errorf("expected CreatedAt '%d', got '%d'", expectedCreatedAt, resp.CreatedAt) + } + + expectedCreatedBy := CreatedBy{ + Id: "4ee15ecc-7fe1-43cb-aa12-ef50420f2cf9", + Name: "Grace Hopper", + Email: "grace@example.com", + } + if resp.CreatedBy != expectedCreatedBy { + t.Errorf("expected CreatedBy '%+v', got '%+v'", expectedCreatedBy, resp.CreatedBy) + } } const runscopeTestCreateOkResponse = `{ diff --git a/internal/runscope/step.go b/internal/runscope/step.go index 95b7346..5c8a10b 100644 --- a/internal/runscope/step.go +++ b/internal/runscope/step.go @@ -15,9 +15,11 @@ type StepBase struct { Headers map[string][]string Auth StepAuth Body string + Form map[string][]string Scripts []string BeforeScripts []string Note string + Skipped bool } func (sb *StepBase) setFromSchema(s *schema.Step) { @@ -33,9 +35,11 @@ func (sb *StepBase) setFromSchema(s *schema.Step) { AuthType: s.Auth.AuthType, } sb.Body = s.Body + sb.Form = map[string][]string{} sb.Scripts = make([]string, len(s.Scripts)) sb.BeforeScripts = make([]string, len(s.BeforeScripts)) sb.Note = s.Note + sb.Skipped = s.Skipped for i, v := range s.Variables { sb.Variables[i] = StepVariable{ @@ -58,6 +62,12 @@ func (sb *StepBase) setFromSchema(s *schema.Step) { sb.Headers[header][i] = v } } + for name, values := range s.Form { + sb.Form[name] = make([]string, len(values)) + for i, v := range values { + sb.Form[name][i] = v + } + } for i, s := range s.Scripts { sb.Scripts[i] = s } @@ -125,9 +135,11 @@ type StepBaseOpts struct { Headers map[string][]string Auth StepAuth Body string + Form map[string][]string Scripts []string BeforeScripts []string Note string + Skipped bool } func (sbo *StepBaseOpts) setRequest(sb *schema.StepBase) { @@ -143,9 +155,11 @@ func (sbo *StepBaseOpts) setRequest(sb *schema.StepBase) { AuthType: sbo.Auth.AuthType, } sb.Body = sbo.Body + sb.Form = map[string][]string{} sb.Scripts = make([]string, len(sbo.Scripts)) sb.BeforeScripts = make([]string, len(sbo.BeforeScripts)) sb.Note = sbo.Note + sb.Skipped = sbo.Skipped for i, v := range sbo.Variables { sb.Variables[i] = schema.StepVariable{ @@ -168,6 +182,12 @@ func (sbo *StepBaseOpts) setRequest(sb *schema.StepBase) { sb.Headers[header][i] = v } } + for name, values := range sbo.Form { + sb.Form[name] = make([]string, len(values)) + for i, v := range values { + sb.Form[name][i] = v + } + } for i, s := range sbo.Scripts { sb.Scripts[i] = s } @@ -244,7 +264,7 @@ func (c *StepClient) Update(ctx context.Context, opts *StepUpdateOpts) (*Step, e return nil, err } - return StepFromSchema(&resp.Step[0]), nil + return StepFromSchema(&resp.Step), nil } type StepDeleteOpts struct { diff --git a/internal/runscope/test_.go b/internal/runscope/test_.go index 8ad1a43..31f85e7 100644 --- a/internal/runscope/test_.go +++ b/internal/runscope/test_.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/terraform-providers/terraform-provider-runscope/internal/runscope/schema" + "time" ) type TestMinimal struct { @@ -21,12 +22,22 @@ type Test struct { Id string DefaultEnvironmentId string Steps []TestStep + CreatedAt time.Time + CreatedBy CreatedBy + LastRun time.Time + TriggerURL string } type TestStep struct { Id string } +type CreatedBy struct { + Id string + Name string + Email string +} + type TestClient struct { client *Client } @@ -41,6 +52,13 @@ func TestFromSchema(s schema.Test) *Test { for i, step := range s.Steps { test.Steps[i].Id = step.Id } + test.CreatedAt = time.Unix(s.CreatedAt, 0) + test.CreatedBy = CreatedBy{ + Id: s.CreatedBy.Id, + Name: s.CreatedBy.Name, + Email: s.CreatedBy.Email, + } + test.TriggerURL = s.TriggerURL return test }