diff --git a/docs/data-sources/platform_connector_terraform_cloud.md b/docs/data-sources/platform_connector_terraform_cloud.md new file mode 100644 index 000000000..ce996e110 --- /dev/null +++ b/docs/data-sources/platform_connector_terraform_cloud.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "harness_platform_connector_terraform_cloud Data Source - terraform-provider-harness" +subcategory: "" +description: |- + Resource for looking up a Terraform Cloud connector. +--- + +# harness_platform_connector_terraform_cloud (Data Source) + +Resource for looking up a Terraform Cloud connector. + +## Example Usage + +```terraform +data "harness_platform_connector_terraform_cloud" "example" { + identifier = "identifier" +} +``` + + +## Schema + +### Required + +- `credentials` (Block List, Min: 1, Max: 1) Credentials to connect to Terraform Cloud platform. (see [below for nested schema](#nestedblock--credentials)) + +### Optional + +- `identifier` (String) Unique identifier of the resource. +- `name` (String) Name of the resource. +- `org_id` (String) Unique identifier of the organization. +- `project_id` (String) Unique identifier of the project. + +### Read-Only + +- `delegate_selectors` (Set of String) Connect only using delegates with these tags. +- `description` (String) Description of the resource. +- `execute_on_delegate` (Boolean) Execute validation on delegate or not. +- `id` (String) The ID of this resource. +- `tags` (Set of String) Tags to associate with the resource. +- `url` (String) URL of the Terraform Cloud platform. + + +### Nested Schema for `credentials` + +Required: + +- `api_token` (Block List, Min: 1, Max: 1) API token credentials to use for authentication. (see [below for nested schema](#nestedblock--credentials--api_token)) + + +### Nested Schema for `credentials.api_token` + +Required: + +- `api_token_ref` (String) Reference to a secret containing the api token to use for authentication. To reference a secret at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference a secret at the account scope, prefix 'account` to the expression: account.{identifier}. + + diff --git a/docs/resources/platform_connector_terraform_cloud.md b/docs/resources/platform_connector_terraform_cloud.md new file mode 100644 index 000000000..01e74c0ac --- /dev/null +++ b/docs/resources/platform_connector_terraform_cloud.md @@ -0,0 +1,92 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "harness_platform_connector_terraform_cloud Resource - terraform-provider-harness" +subcategory: "" +description: |- + Resource for creating a TerraformCloud connector. +--- + +# harness_platform_connector_terraform_cloud (Resource) + +Resource for creating a TerraformCloud connector. + +## Example Usage + +```terraform +# Create Terraform Cloud connector using API token as secret + +resource "harness_platform_connector_terraform_cloud" "terraform_cloud" { + identifier = "example_terraform_cloud_connector" + name = "Example terraform cloud connector" + description = "description of terraform cloud connector" + tags = ["foo:bar"] + delegate_selectors = ["harness-delegate"] + credentials { + api_token { + api_token_ref = "account.TEST_terraform_cloud_api_token" + } + } +} + +# Add connectivity mode by providing execute_on_delegate value. Default is to execute on Delegate + +resource "harness_platform_connector_terraform_cloud" "terraform_cloud" { + identifier = "example_terraform_cloud_connector" + name = "Example terraform cloud connector" + description = "description of terraform cloud connector" + delegate_selectors = ["harness-delegate"] + tags = ["foo:bar"] + execute_on_delegate = false + credentials { + api_token { + api_token_ref = "account.TEST_terraform_cloud_api_token" + } + } +} +``` + + +## Schema + +### Required + +- `credentials` (Block List, Min: 1, Max: 1) Credentials to connect to Terraform Cloud platform. (see [below for nested schema](#nestedblock--credentials)) +- `identifier` (String) Unique identifier of the resource. +- `name` (String) Name of the resource. +- `url` (String) URL of Terraform Cloud platform. + +### Optional + +- `delegate_selectors` (Set of String) Connect only using delegates with these tags. +- `description` (String) Description of the resource. +- `execute_on_delegate` (Boolean) Execute validation on delegate or not. +- `org_id` (String) Unique identifier of the organization. +- `project_id` (String) Unique identifier of the project. +- `tags` (Set of String) Tags to associate with the resource. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `credentials` + +Required: + +- `api_token` (Block List, Min: 1, Max: 1) API token credentials to use for authentication. (see [below for nested schema](#nestedblock--credentials--api_token)) + + +### Nested Schema for `credentials.api_token` + +Required: + +- `api_token_ref` (String) Reference to a secret containing the api token to use for authentication. To reference a secret at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference a secret at the account scope, prefix 'account` to the expression: account.{identifier}. + +## Import + +Import is supported using the following syntax: + +```shell +# Import using terrafrm cloud provider connector id +terraform import harness_platform_connector_terraform_cloud.example +``` diff --git a/examples/data-sources/harness_platform_connector_terraform_cloud/data-source.tf b/examples/data-sources/harness_platform_connector_terraform_cloud/data-source.tf new file mode 100644 index 000000000..29ab6f2c9 --- /dev/null +++ b/examples/data-sources/harness_platform_connector_terraform_cloud/data-source.tf @@ -0,0 +1,3 @@ +data "harness_platform_connector_terraform_cloud" "example" { + identifier = "identifier" +} diff --git a/examples/resources/harness_platform_connector_terraform_cloud/import.sh b/examples/resources/harness_platform_connector_terraform_cloud/import.sh new file mode 100644 index 000000000..6e2db44e2 --- /dev/null +++ b/examples/resources/harness_platform_connector_terraform_cloud/import.sh @@ -0,0 +1,2 @@ +# Import using terrafrm cloud provider connector id +terraform import harness_platform_connector_terraform_cloud.example diff --git a/examples/resources/harness_platform_connector_terraform_cloud/resource.tf b/examples/resources/harness_platform_connector_terraform_cloud/resource.tf new file mode 100644 index 000000000..a47eb74dc --- /dev/null +++ b/examples/resources/harness_platform_connector_terraform_cloud/resource.tf @@ -0,0 +1,30 @@ +# Create Terraform Cloud connector using API token as secret + +resource "harness_platform_connector_terraform_cloud" "terraform_cloud" { + identifier = "example_terraform_cloud_connector" + name = "Example terraform cloud connector" + description = "description of terraform cloud connector" + tags = ["foo:bar"] + delegate_selectors = ["harness-delegate"] + credentials { + api_token { + api_token_ref = "account.TEST_terraform_cloud_api_token" + } + } +} + +# Add connectivity mode by providing execute_on_delegate value. Default is to execute on Delegate + +resource "harness_platform_connector_terraform_cloud" "terraform_cloud" { + identifier = "example_terraform_cloud_connector" + name = "Example terraform cloud connector" + description = "description of terraform cloud connector" + delegate_selectors = ["harness-delegate"] + tags = ["foo:bar"] + execute_on_delegate = false + credentials { + api_token { + api_token_ref = "account.TEST_terraform_cloud_api_token" + } + } +} diff --git a/go.mod b/go.mod index 50bdf9b08..8dbf48f07 100644 --- a/go.mod +++ b/go.mod @@ -84,5 +84,5 @@ require ( gotest.tools/v3 v3.3.0 // indirect ) -// replace github.com/harness/harness-go-sdk => ../harness-go-sdk +replace github.com/harness/harness-go-sdk => ../harness-go-sdk // replace github.com/harness/harness-openapi-go-client => ../harness-openapi-go-client diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 752c7e47e..b905aa251 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -153,6 +153,7 @@ func Provider(version string) func() *schema.Provider { "harness_platform_connector_prometheus": connector.DatasourceConnectorPrometheus(), "harness_platform_connector_splunk": connector.DatasourceConnectorSplunk(), "harness_platform_connector_spot": connector.DatasourceConnectorSpot(), + "harness_platform_connector_terraform_cloud": connector.DatasourceConnectorTerraformCloud(), "harness_platform_connector_sumologic": connector.DatasourceConnectorSumologic(), "harness_platform_current_user": pl_current_user.DataSourceCurrentUser(), "harness_platform_user": pl_user.DataSourceUser(), @@ -245,6 +246,7 @@ func Provider(version string) func() *schema.Provider { "harness_platform_connector_prometheus": connector.ResourceConnectorPrometheus(), "harness_platform_connector_splunk": connector.ResourceConnectorSplunk(), "harness_platform_connector_spot": connector.ResourceConnectorSpot(), + "harness_platform_connector_terraform_cloud": connector.ResourceConnectorTerraformCloud(), "harness_platform_connector_sumologic": connector.ResourceConnectorSumologic(), "harness_platform_environment": pl_environment.ResourceEnvironment(), "harness_platform_environment_group": pl_environment_group.ResourceEnvironmentGroup(), diff --git a/internal/service/platform/connector/terraform_cloud.go b/internal/service/platform/connector/terraform_cloud.go new file mode 100644 index 000000000..614b09d5b --- /dev/null +++ b/internal/service/platform/connector/terraform_cloud.go @@ -0,0 +1,155 @@ +package connector + +import ( + "context" + "fmt" + + "github.com/harness/harness-go-sdk/harness/nextgen" + "github.com/harness/terraform-provider-harness/helpers" + "github.com/harness/terraform-provider-harness/internal/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceConnectorTerraformCloud() *schema.Resource { + resource := &schema.Resource{ + Description: "Resource for creating a TerraformCloud connector.", + ReadContext: resourceConnectorTerraformCloudRead, + CreateContext: resourceConnectorTerraformCloudCreateOrUpdate, + UpdateContext: resourceConnectorTerraformCloudCreateOrUpdate, + DeleteContext: resourceConnectorDelete, + Importer: helpers.MultiLevelResourceImporter, + Schema: map[string]*schema.Schema{ + "url": { + Description: "URL of Terraform Cloud platform.", + Type: schema.TypeString, + Required: true, + }, + "credentials": { + Description: "Credentials to connect to Terraform Cloud platform.", + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "api_token": { + Description: "API token credentials to use for authentication.", + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "api_token_ref": { + Description: "Reference to a secret containing the api token to use for authentication." + secret_ref_text, + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "delegate_selectors": { + Description: "Connect only using delegates with these tags.", + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } + + helpers.SetMultiLevelResourceSchema(resource.Schema) + + return resource +} + +func resourceConnectorTerraformCloudRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn, err := resourceConnectorReadBase(ctx, d, meta, nextgen.ConnectorTypes.TerraformCloud) + if err != nil { + return err + } + + if conn == nil { + return nil + } + + if err := readConnectorTerraformCloud(d, conn); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceConnectorTerraformCloudCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := buildConnectorTerraformCloud(d) + + newConn, err := resourceConnectorCreateOrUpdateBase(ctx, d, meta, conn) + if err != nil { + return err + } + + if err := readConnectorTerraformCloud(d, newConn); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func buildConnectorTerraformCloud(d *schema.ResourceData) *nextgen.ConnectorInfo { + connector := &nextgen.ConnectorInfo{ + Type_: nextgen.ConnectorTypes.TerraformCloud, + TerraformCloud: &nextgen.TerraformCloudConnector{ + Credential: &nextgen.TerraformCloudCredential{ + Type_: nextgen.TerraformCloudAuthTypes.ApiToken, + }, + }, + } + + if attr, ok := d.GetOk("url"); ok { + connector.TerraformCloud.TerraformCloudUrl = attr.(string) + } + + if attr, ok := d.GetOk("delegate_selectors"); ok { + connector.TerraformCloud.DelegateSelectors = utils.InterfaceSliceToStringSlice(attr.(*schema.Set).List()) + } + + if attr, ok := d.GetOk("credentials"); ok { + config := attr.([]interface{})[0].(map[string]interface{}) + connector.TerraformCloud.Credential = &nextgen.TerraformCloudCredential{} + + if attr := config["api_token"].([]interface{}); len(attr) > 0 { + config := attr[0].(map[string]interface{}) + connector.TerraformCloud.Credential.Type_ = nextgen.TerraformCloudAuthTypes.ApiToken + connector.TerraformCloud.Credential.ApiToken = &nextgen.TerraformCloudTokenCredentials{} + + if attr, ok := config["api_token_ref"]; ok { + connector.TerraformCloud.Credential.ApiToken.ApiToken = attr.(string) + } + } + } + + return connector +} + +func readConnectorTerraformCloud(d *schema.ResourceData, connector *nextgen.ConnectorInfo) error { + d.Set("url", connector.TerraformCloud.TerraformCloudUrl) + d.Set("delegate_selectors", connector.TerraformCloud.DelegateSelectors) + + switch connector.TerraformCloud.Credential.Type_ { + case nextgen.TerraformCloudAuthTypes.ApiToken: + d.Set("credentials", []interface{}{ + map[string]interface{}{ + "api_token": []interface{}{ + map[string]interface{}{ + "api_token_ref": connector.TerraformCloud.Credential.ApiToken.ApiToken, + }, + }, + }, + }) + default: + return fmt.Errorf("unsupported Terraform Cloud credentials type: %s", connector.TerraformCloud.Credential.Type_) + } + + return nil +} diff --git a/internal/service/platform/connector/terraform_cloud_data_source.go b/internal/service/platform/connector/terraform_cloud_data_source.go new file mode 100644 index 000000000..ab1965697 --- /dev/null +++ b/internal/service/platform/connector/terraform_cloud_data_source.go @@ -0,0 +1,54 @@ +package connector + +import ( + "github.com/harness/terraform-provider-harness/helpers" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DatasourceConnectorTerraformCloud() *schema.Resource { + resource := &schema.Resource{ + Description: "Resource for looking up a Terraform Cloud connector.", + ReadContext: resourceConnectorTerraformCloudRead, + + Schema: map[string]*schema.Schema{ + "url": { + Description: "URL of the Terraform Cloud platform.", + Type: schema.TypeString, + Computed: true, + }, + "delegate_selectors": { + Description: "Tags to filter delegates for connection.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "credentials": { + Description: "Credentials to connect to Terraform Cloud platform.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "api_token": { + Description: "API token credentials to use for authentication.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "api_token_ref": { + Description: "Reference to a secret containing the api token to use for authentication." + secret_ref_text, + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } + + helpers.SetMultiLevelDatasourceSchema(resource.Schema) + + return resource +} diff --git a/internal/service/platform/connector/terraform_cloud_data_source_test.go b/internal/service/platform/connector/terraform_cloud_data_source_test.go new file mode 100644 index 000000000..e4c05fb23 --- /dev/null +++ b/internal/service/platform/connector/terraform_cloud_data_source_test.go @@ -0,0 +1,79 @@ +package connector_test + +import ( + "fmt" + "testing" + + "github.com/harness/harness-go-sdk/harness/utils" + "github.com/harness/terraform-provider-harness/internal/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceConnectorTerraformCloud(t *testing.T) { + + var ( + name = fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(4)) + resourceName = "data.harness_platform_connector_terraform_cloud.test" + ) + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceConnectorTerraformCloud(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", name), + resource.TestCheckResourceAttr(resourceName, "identifier", name), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "url", "https://app.terraform.io/"), + resource.TestCheckResourceAttr(resourceName, "description", "test"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "delegate_selectors.#", "1"), + ), + }, + }, + }) +} + +func testAccDataSourceConnectorTerraformCloud(name string) string { + return fmt.Sprintf(` + resource "harness_platform_secret_text" "test" { + identifier = "%[1]s" + name = "%[1]s" + description = "test" + tags = ["foo:bar"] + + secret_manager_identifier = "harnessSecretManager" + value_type = "Inline" + value = "secret" + } + + resource "harness_platform_connector_terraform_cloud" "test" { + url = "https://app.terraform.io/" + identifier = "%[1]s" + name = "%[1]s" + description = "test" + tags = ["foo:bar"] + delegate_selectors = ["harness-delegate"] + credentials { + api_token { + api_token_ref = "account.${harness_platform_secret_text.test.id}" + } + } + depends_on = [time_sleep.wait_4_seconds] + } + + resource "time_sleep" "wait_4_seconds" { + depends_on = [harness_platform_secret_text.test] + destroy_duration = "4s" + } + + data "harness_platform_connector_terraform_cloud" "test" { + identifier = harness_platform_connector_terraform_cloud.test.identifier + } + `, name) +} diff --git a/internal/service/platform/connector/terraform_cloud_test.go b/internal/service/platform/connector/terraform_cloud_test.go new file mode 100644 index 000000000..87e25dffe --- /dev/null +++ b/internal/service/platform/connector/terraform_cloud_test.go @@ -0,0 +1,83 @@ +package connector_test + +import ( + "fmt" + "testing" + + "github.com/harness/harness-go-sdk/harness/utils" + "github.com/harness/terraform-provider-harness/internal/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceConnectorTerraformCloud(t *testing.T) { + id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(5)) + name := id + executeTfCloudTest(t, testAccResourceConnectorTerraformCloud(id, name), "true", id, name) +} + +func executeTfCloudTest(t *testing.T, config string, executeOnDelegate string, id string, name string) { + resourceName := "harness_platform_connector_terraform_cloud.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + CheckDestroy: testAccConnectorDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", id), + resource.TestCheckResourceAttr(resourceName, "identifier", id), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "url", "https://app.terraform.io/"), + resource.TestCheckResourceAttr(resourceName, "description", "test"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "delegate_selectors.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccResourceConnectorTerraformCloud(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_secret_text" "test" { + identifier = "%[1]s" + name = "%[2]s" + description = "test" + tags = ["foo:bar"] + + secret_manager_identifier = "harnessSecretManager" + value_type = "Inline" + value = "secret" + } + + resource "harness_platform_connector_terraform_cloud" "test" { + url = "https://app.terraform.io/" + identifier = "%[1]s" + name = "%[2]s" + description = "test" + tags = ["foo:bar"] + delegate_selectors = ["harness-delegate"] + credentials { + api_token { + api_token_ref = "account.${harness_platform_secret_text.test.id}" + } + } + depends_on = [time_sleep.wait_4_seconds] + } + + resource "time_sleep" "wait_4_seconds" { + depends_on = [harness_platform_secret_text.test] + destroy_duration = "4s" + } +`, id, name) +}