From ae7270e0a1d87d04be700e8df4753db980e17281 Mon Sep 17 00:00:00 2001 From: Ved Tambat Date: Tue, 12 Sep 2023 22:16:46 +0530 Subject: [PATCH] Terraform changes for ImportFromGit feature (#682) * Terraform changes for ImportFromGit feature * Template Terraform changes * changes * testing complete * changelog * go-client version update * go.mod * changes * changes * changes-3 * resource.tf * go generate * changes * changes * changes --- .changelog/682.txt | 4 + docs/resources/platform_input_set.md | 46 +++- docs/resources/platform_template.md | 88 ++++++++ .../harness_platform_input_set/resource.tf | 20 ++ .../harness_platform_template/resource.tf | 63 ++++++ go.mod | 4 +- go.sum | 4 +- .../platform/input_set/input_set_resource.go | 169 ++++++++++++++- .../input_set/input_set_resource_test.go | 61 ++++++ .../platform/template/resource_template.go | 201 +++++++++++++++--- .../template/resource_template_test.go | 172 +++++++++++++++ 11 files changed, 783 insertions(+), 49 deletions(-) create mode 100644 .changelog/682.txt diff --git a/.changelog/682.txt b/.changelog/682.txt new file mode 100644 index 000000000..b020e3b07 --- /dev/null +++ b/.changelog/682.txt @@ -0,0 +1,4 @@ +```release-note:enhancement +harness_platform_template - Added support to import Template entity from git. +harness_platform_input_set - Added support to import InputSet entity from git. +``` \ No newline at end of file diff --git a/docs/resources/platform_input_set.md b/docs/resources/platform_input_set.md index 44ceb3a8d..6fda4a1c8 100644 --- a/docs/resources/platform_input_set.md +++ b/docs/resources/platform_input_set.md @@ -73,6 +73,26 @@ inputSet: value: "value" EOT } + +# Importing Input Set from Git +resource "harness_platform_input_set" "test" { + identifier = "inputset" + org_id = "default" + project_id = "V" + name = "inputset" + pipeline_id = "DoNotDeletePipeline" + import_from_git = true + git_import_info { + branch_name = "main" + file_path = ".harness/inputset.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + input_set_import_request { + input_set_name = "inputset" + input_set_description = "" + } +} ``` @@ -85,13 +105,16 @@ EOT - `org_id` (String) Unique identifier of the organization. - `pipeline_id` (String) Identifier of the pipeline - `project_id` (String) Unique identifier of the project. -- `yaml` (String) Input Set YAML. In YAML, to reference an entity at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference an entity at the account scope, prefix 'account` to the expression: account.{identifier}. For eg, to reference a connector with identifier 'connectorId' at the organization scope in a stage mention it as connectorRef: org.connectorId. ### Optional - `description` (String) Description of the resource. - `git_details` (Block List, Max: 1) Contains parameters related to creating an Entity for Git Experience. (see [below for nested schema](#nestedblock--git_details)) +- `git_import_info` (Block List, Max: 1) Contains Git Information for importing entities from Git (see [below for nested schema](#nestedblock--git_import_info)) +- `import_from_git` (Boolean) Flag to set if importing from Git +- `input_set_import_request` (Block List, Max: 1) Contains parameters for importing a input set (see [below for nested schema](#nestedblock--input_set_import_request)) - `tags` (Set of String) Tags to associate with the resource. +- `yaml` (String) Input Set YAML. In YAML, to reference an entity at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference an entity at the account scope, prefix 'account` to the expression: account.{identifier}. For eg, to reference a connector with identifier 'connectorId' at the organization scope in a stage mention it as connectorRef: org.connectorId. ### Read-Only @@ -114,6 +137,27 @@ Optional: - `repo_name` (String) Name of the repository. - `store_type` (String) Specifies whether the Entity is to be stored in Git or not. Possible values: INLINE, REMOTE. + + +### Nested Schema for `git_import_info` + +Optional: + +- `branch_name` (String) Name of the branch. +- `connector_ref` (String) Identifier of the Harness Connector used for importing entity from Git To reference a connector at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference a connector at the account scope, prefix 'account` to the expression: account.{identifier}. +- `file_path` (String) File path of the Entity in the repository. +- `is_force_import` (Boolean) +- `repo_name` (String) Name of the repository. + + + +### Nested Schema for `input_set_import_request` + +Optional: + +- `input_set_description` (String) Description of the input set. +- `input_set_name` (String) Name of the input set. + ## Import Import is supported using the following syntax: diff --git a/docs/resources/platform_template.md b/docs/resources/platform_template.md index 1294dd316..819865676 100644 --- a/docs/resources/platform_template.md +++ b/docs/resources/platform_template.md @@ -1098,6 +1098,69 @@ resource "time_sleep" "wait_10_seconds" { depends_on = [harness_platform_template.test2] destroy_duration = "10s" } + +##Importing Account Level Templates +resource "harness_platform_template" "test" { + identifier = "accounttemplate" + name = "accounttemplate" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/accounttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "accounttemplate" + template_version = "v2" + template_description = "" + } +} + +##Importing Org Level Templates +resource "harness_platform_template" "test" { + identifier = "orgtemplate" + name = "orgtemplate" + org_id = "org" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/orgtemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "orgtemplate" + template_version = "v2" + template_description = "" + } +} + +##Importing Project Level Templates +resource "harness_platform_template" "test" { + identifier = "projecttemplate" + name = "projecttemplate" + org_id = "org" + project_id = "project" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/projecttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "projecttemplate" + template_version = "v2" + template_description = "" + } +} ``` @@ -1115,10 +1178,13 @@ resource "time_sleep" "wait_10_seconds" { - `description` (String, Deprecated) Description of the entity. Description field is deprecated - `force_delete` (String) Enable this flag for force deletion of template. It will delete the Harness entity even if your pipelines or other entities reference it - `git_details` (Block List, Max: 1) Contains parameters related to creating an Entity for Git Experience. (see [below for nested schema](#nestedblock--git_details)) +- `git_import_details` (Block List, Max: 1) Contains Git Information for importing entities from Git (see [below for nested schema](#nestedblock--git_import_details)) +- `import_from_git` (Boolean) Flag to set if importing from Git - `is_stable` (Boolean) True if given version for template to be set as stable. - `org_id` (String) Organization Identifier for the Entity - `project_id` (String) Project Identifier for the Entity - `tags` (Set of String) Tags to associate with the resource. +- `template_import_request` (Block List, Max: 1) Contains parameters for importing template. (see [below for nested schema](#nestedblock--template_import_request)) - `template_yaml` (String) Yaml for creating new Template. In YAML, to reference an entity at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference an entity at the account scope, prefix 'account` to the expression: account.{identifier}. For eg, to reference a connector with identifier 'connectorId' at the organization scope in a stage mention it as connectorRef: org.connectorId. ### Read-Only @@ -1140,6 +1206,28 @@ Optional: - `repo_name` (String) Name of the repository. - `store_type` (String) Specifies whether the Entity is to be stored in Git or not. Possible values: INLINE, REMOTE. + + +### Nested Schema for `git_import_details` + +Optional: + +- `branch_name` (String) Name of the branch. +- `connector_ref` (String) Identifier of the Harness Connector used for importing entity from Git To reference a connector at the organization scope, prefix 'org' to the expression: org.{identifier}. To reference a connector at the account scope, prefix 'account` to the expression: account.{identifier}. +- `file_path` (String) File path of the Entity in the repository. +- `is_force_import` (Boolean) +- `repo_name` (String) Name of the repository. + + + +### Nested Schema for `template_import_request` + +Optional: + +- `template_description` (String) Description of the template. +- `template_name` (String) Name of the template. +- `template_version` (String) Version of the template. + ## Import Import is supported using the following syntax: diff --git a/examples/resources/harness_platform_input_set/resource.tf b/examples/resources/harness_platform_input_set/resource.tf index 1be23f489..6e6a8e814 100644 --- a/examples/resources/harness_platform_input_set/resource.tf +++ b/examples/resources/harness_platform_input_set/resource.tf @@ -58,3 +58,23 @@ inputSet: value: "value" EOT } + +# Importing Input Set from Git +resource "harness_platform_input_set" "test" { + identifier = "inputset" + org_id = "default" + project_id = "V" + name = "inputset" + pipeline_id = "DoNotDeletePipeline" + import_from_git = true + git_import_info { + branch_name = "main" + file_path = ".harness/inputset.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + input_set_import_request { + input_set_name = "inputset" + input_set_description = "" + } +} diff --git a/examples/resources/harness_platform_template/resource.tf b/examples/resources/harness_platform_template/resource.tf index ea0811484..11cc9b76a 100644 --- a/examples/resources/harness_platform_template/resource.tf +++ b/examples/resources/harness_platform_template/resource.tf @@ -1082,4 +1082,67 @@ resource "harness_platform_template" "template_v1" { resource "time_sleep" "wait_10_seconds" { depends_on = [harness_platform_template.test2] destroy_duration = "10s" +} + +##Importing Account Level Templates +resource "harness_platform_template" "test" { + identifier = "accounttemplate" + name = "accounttemplate" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/accounttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "accounttemplate" + template_version = "v2" + template_description = "" + } +} + +##Importing Org Level Templates +resource "harness_platform_template" "test" { + identifier = "orgtemplate" + name = "orgtemplate" + org_id = "org" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/orgtemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "orgtemplate" + template_version = "v2" + template_description = "" + } +} + +##Importing Project Level Templates +resource "harness_platform_template" "test" { + identifier = "projecttemplate" + name = "projecttemplate" + org_id = "org" + project_id = "project" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/projecttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "projecttemplate" + template_version = "v2" + template_description = "" + } } \ No newline at end of file diff --git a/go.mod b/go.mod index dd55c62d5..5ce7c98f9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/antihax/optional v1.0.0 github.com/docker/docker v24.0.5+incompatible github.com/harness/harness-go-sdk v0.3.49 - github.com/harness/harness-openapi-go-client v0.0.18 + github.com/harness/harness-openapi-go-client v0.0.19 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 @@ -99,4 +99,4 @@ require ( //replace github.com/harness/harness-go-sdk => ../harness-go-sdk -// replace github.com/harness/harness-openapi-go-client => ../harness-openapi-go-client +// replace github.com/harness/harness-openapi-go-client => ../harness-openapi-go-client \ No newline at end of file diff --git a/go.sum b/go.sum index ab928276b..90eeac675 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,8 @@ github.com/harness/harness-go-sdk v0.3.48 h1:j/c52s/GwABtoetw35MfzEDqWZO85lZgdB+ github.com/harness/harness-go-sdk v0.3.48/go.mod h1:CPXydorp4zd5Dz2u2FXiHyWL4yd5PQafOMN69cgPSvk= github.com/harness/harness-go-sdk v0.3.49 h1:wtgo8/GEMjEjXyhfliKl1qlNqOGrnLpq8gdRghY1G9M= github.com/harness/harness-go-sdk v0.3.49/go.mod h1:CPXydorp4zd5Dz2u2FXiHyWL4yd5PQafOMN69cgPSvk= -github.com/harness/harness-openapi-go-client v0.0.18 h1:gPhLOSOwjmZJ3aLjJiBbWPOMSBm8h72wbVSfx19eWZM= -github.com/harness/harness-openapi-go-client v0.0.18/go.mod h1:u0vqYb994BJGotmEwJevF4L3BNAdU9i8ui2d22gmLPA= +github.com/harness/harness-openapi-go-client v0.0.19 h1:8XuZvSPZrNqKRLh7Qksdz78WvRMRzRf88LgzxoT5u7k= +github.com/harness/harness-openapi-go-client v0.0.19/go.mod h1:u0vqYb994BJGotmEwJevF4L3BNAdU9i8ui2d22gmLPA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/internal/service/platform/input_set/input_set_resource.go b/internal/service/platform/input_set/input_set_resource.go index 5bdca9a07..0aed581a5 100644 --- a/internal/service/platform/input_set/input_set_resource.go +++ b/internal/service/platform/input_set/input_set_resource.go @@ -32,29 +32,34 @@ func ResourceInputSet() *schema.Resource { "yaml": { Description: "Input Set YAML." + helpers.Descriptions.YamlText.String(), Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, }, "git_details": { Description: "Contains parameters related to creating an Entity for Git Experience.", Type: schema.TypeList, MaxItems: 1, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "branch_name": { Description: "Name of the branch.", Type: schema.TypeString, Optional: true, + Computed: true, }, "file_path": { Description: "File path of the Entity in the repository.", Type: schema.TypeString, Optional: true, + Computed: true, }, "commit_message": { Description: "Commit message used for the merge commit.", Type: schema.TypeString, Optional: true, + Computed: true, }, "base_branch": { Description: "Name of the default branch (this checks out a new branch titled by branch_name).", @@ -66,17 +71,20 @@ func ResourceInputSet() *schema.Resource { Description: "Identifier of the Harness Connector used for CRUD operations on the Entity." + helpers.Descriptions.ConnectorRefText.String(), Type: schema.TypeString, Optional: true, + Computed: true, }, "store_type": { Description: "Specifies whether the Entity is to be stored in Git or not. Possible values: INLINE, REMOTE.", Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringInSlice([]string{"INLINE", "REMOTE"}, false), }, "repo_name": { Description: "Name of the repository.", Type: schema.TypeString, Optional: true, + Computed: true, }, "last_object_id": { Description: "Last object identifier (for Github). To be provided only when updating Pipeline.", @@ -105,6 +113,66 @@ func ResourceInputSet() *schema.Resource { }, }, }, + "import_from_git": { + Description: "Flag to set if importing from Git", + Type: schema.TypeBool, + Optional: true, + }, + "git_import_info": { + Description: "Contains Git Information for importing entities from Git", + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "branch_name": { + Description: "Name of the branch.", + Type: schema.TypeString, + Optional: true, + }, + "file_path": { + Description: "File path of the Entity in the repository.", + Type: schema.TypeString, + Optional: true, + }, + "connector_ref": { + Description: "Identifier of the Harness Connector used for importing entity from Git" + helpers.Descriptions.ConnectorRefText.String(), + Type: schema.TypeString, + Optional: true, + }, + "repo_name": { + Description: "Name of the repository.", + Type: schema.TypeString, + Optional: true, + }, + "is_force_import": { + Description: "", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "input_set_import_request": { + Description: "Contains parameters for importing a input set", + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "input_set_name": { + Description: "Name of the input set.", + Type: schema.TypeString, + Optional: true, + }, + "input_set_description": { + Description: "Description of the input set.", + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, }, } helpers.SetProjectLevelResourceSchema(resource.Schema) @@ -161,7 +229,9 @@ func resourceInputSetCreateOrUpdate(ctx context.Context, d *schema.ResourceData, c, ctx := meta.(*internal.Session).GetClientWithContext(ctx) var err error + var inputSet_id string var resp nextgen.InputSetResponseBody + var response nextgen.InputSetSaveResponseBody var httpResp *http.Response id := d.Id() @@ -175,17 +245,30 @@ func resourceInputSetCreateOrUpdate(ctx context.Context, d *schema.ResourceData, var connector_ref optional.String if id == "" { - inputSet := buildCreateInputSet(d) - if inputSet.GitDetails != nil { - base_branch = optional.NewString(inputSet.GitDetails.BaseBranch) - store_type = optional.NewString(inputSet.GitDetails.StoreType) - commit_message = optional.NewString(inputSet.GitDetails.CommitMessage) - connector_ref = optional.NewString(inputSet.GitDetails.ConnectorRef) - } + if d.Get("import_from_git").(bool) { + inputSet_id = d.Get("input_set_import_request.0.input_set_name").(string) + + input_set_import_request_body := createImportFromGitRequest(d) + + response, httpResp, err = c.InputSetsApi.ImportInputSetsFromGit(ctx, orgIdentifier, projectIdentifier, pipelineIdentifier, inputSet_id, + &nextgen.InputSetsApiImportInputSetsFromGitOpts{ + Body: optional.NewInterface(input_set_import_request_body), + HarnessAccount: optional.NewString(c.AccountId)}) + + } else{ + inputSet := buildCreateInputSet(d) + if inputSet.GitDetails != nil { + base_branch = optional.NewString(inputSet.GitDetails.BaseBranch) + store_type = optional.NewString(inputSet.GitDetails.StoreType) + commit_message = optional.NewString(inputSet.GitDetails.CommitMessage) + connector_ref = optional.NewString(inputSet.GitDetails.ConnectorRef) + } - resp, httpResp, err = c.InputSetsApi.CreateInputSet(ctx, inputSet, pipelineIdentifier, orgIdentifier, projectIdentifier, &nextgen.InputSetsApiCreateInputSetOpts{ - HarnessAccount: optional.NewString(c.AccountId), - }) + inputSet_id = inputSet.Identifier + resp, httpResp, err = c.InputSetsApi.CreateInputSet(ctx, inputSet, pipelineIdentifier, orgIdentifier, projectIdentifier, &nextgen.InputSetsApiCreateInputSetOpts{ + HarnessAccount: optional.NewString(c.AccountId), + }) + } } else { inputSet := buildUpdateInputSet(d) if inputSet.GitDetails != nil { @@ -204,11 +287,75 @@ func resourceInputSetCreateOrUpdate(ctx context.Context, d *schema.ResourceData, return helpers.HandleApiError(err, d, httpResp) } + if d.Get("import_from_git").(bool) { + + inputSet_id = response.Identifier + + branch_name := helpers.BuildField(d, "git_import_info.0.branch_name") + parent_entity_connector_ref := helpers.BuildField(d, "git_import_info.0.connector_ref") + parent_entity_repo_name := helpers.BuildField(d, "git_import_info.0.repo_name") + + resp, httpResp, err := c.InputSetsApi.GetInputSet(ctx, orgIdentifier, projectIdentifier, inputSet_id, pipelineIdentifier, &nextgen.InputSetsApiGetInputSetOpts{ + HarnessAccount: optional.NewString(c.AccountId), + BranchName: branch_name, + ParentEntityConnectorRef: parent_entity_connector_ref, + ParentEntityRepoName: parent_entity_repo_name, + }) + + if err != nil { + return helpers.HandleApiError(err, d, httpResp) + } + + readInputSet(d, &resp, pipelineIdentifier, optional.NewString("REMOTE"), optional.EmptyString() , optional.EmptyString(), parent_entity_connector_ref) + + return nil + } + readInputSet(d, &resp, pipelineIdentifier, store_type, base_branch, commit_message, connector_ref) return nil } +func createImportFromGitRequest(d *schema.ResourceData) *nextgen.InputSetsImportRequestBody { + + input_set_git_import_info := &nextgen.GitImportInfo{} + if attr, ok := d.GetOk("git_import_info"); ok { + config := attr.([]interface{})[0].(map[string]interface{}) + if attr, ok := config["branch_name"]; ok { + input_set_git_import_info.BranchName = attr.(string) + } + if attr, ok := config["file_path"]; ok { + input_set_git_import_info.FilePath = attr.(string) + } + if attr, ok := config["connector_ref"]; ok { + input_set_git_import_info.ConnectorRef = attr.(string) + } + if attr, ok := config["repo_name"]; ok { + input_set_git_import_info.RepoName = attr.(string) + } + if attr, ok := config["is_force_import"]; ok { + input_set_git_import_info.IsForceImport = attr.(bool) + } + } + + input_set_import_request := &nextgen.InputSetsImportRequestDto{} + if attr, ok := d.GetOk("input_set_import_request"); ok { + config := attr.([]interface{})[0].(map[string]interface{}) + if attr, ok := config["input_set_name"]; ok { + input_set_import_request.InputSetName = attr.(string) + } + if attr, ok := config["input_set_description"]; ok { + input_set_import_request.InputSetDescription = attr.(string) + } + } + + input_set_import_request_body := &nextgen.InputSetsImportRequestBody{} + input_set_import_request_body.GitImportInfo = input_set_git_import_info + input_set_import_request_body.InputSetsImportRequest = input_set_import_request + + return input_set_import_request_body +} + func resourceInputSetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c, ctx := meta.(*internal.Session).GetClientWithContext(ctx) orgIdentifier := helpers.BuildField(d, "org_id").Value() diff --git a/internal/service/platform/input_set/input_set_resource_test.go b/internal/service/platform/input_set/input_set_resource_test.go index d8b2e9817..3df891f4c 100644 --- a/internal/service/platform/input_set/input_set_resource_test.go +++ b/internal/service/platform/input_set/input_set_resource_test.go @@ -159,6 +159,40 @@ func TestAccResourceInputSetInline(t *testing.T) { } +func TestAccResourceInputSetImportFromGit(t *testing.T) { + name := t.Name() + id := fmt.Sprintf("%s_%s", name, utils.RandStringBytes(5)) + + resourceName := "harness_platform_input_set.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + CheckDestroy: testAccInputSetDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceInputSetImportFromGit(id, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", "inputset"), + resource.TestCheckResourceAttr(resourceName, "pipeline_id", "DoNotDeletePipeline"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.PipelineResourceImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"git_import_info.0.branch_name", "git_import_info.0.connector_ref", "git_import_info.0.file_path","git_import_info.0.repo_name", "import_from_git", "pipeline_import_request.0.pipeline_description", "pipeline_import_request.0.pipeline_name", "git_import_info.#", "git_import_info.0.%", "pipeline_import_request.#", "pipeline_import_request.0.%", "git_details.0.connector_ref", "git_details.0.connector_ref", "git_details.0.store_type", "git_details.0.store_type", "git_import_info.0.is_force_import","input_set_import_request.#", "input_set_import_request.0.%", "input_set_import_request.0.input_set_description", "input_set_import_request.0.input_set_name"}, + }, + }, + }) + +} + + func testAccGetInputSet(resourceName string, state *terraform.State) (*nextgen.InputSetResponseBody, error) { r := acctest.TestAccGetResource(resourceName, state) c, ctx := acctest.TestAccGetClientWithContext() @@ -688,3 +722,30 @@ func testAccResourceInputSet(id string, name string) string { } `, id, name) } + +func testAccResourceInputSetImportFromGit(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_organization" "test" { + identifier = "%[1]s" + name = "%[2]s" + } + resource "harness_platform_input_set" "test" { + identifier = "inputset" + org_id = "default" + project_id = "V" + name = "inputset" + pipeline_id = "DoNotDeletePipeline" + import_from_git = true + git_import_info { + branch_name = "main" + file_path = ".harness/inputset.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + input_set_import_request { + input_set_name = "inputset" + input_set_description = "" + } + } + `, id, name) +} diff --git a/internal/service/platform/template/resource_template.go b/internal/service/platform/template/resource_template.go index 57099749f..0b2e15a0d 100644 --- a/internal/service/platform/template/resource_template.go +++ b/internal/service/platform/template/resource_template.go @@ -152,6 +152,71 @@ func ResourceTemplate() *schema.Resource { }, Optional: true, }, + "import_from_git": { + Description: "Flag to set if importing from Git", + Type: schema.TypeBool, + Optional: true, + }, + "git_import_details": { + Description: "Contains Git Information for importing entities from Git", + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "branch_name": { + Description: "Name of the branch.", + Type: schema.TypeString, + Optional: true, + }, + "file_path": { + Description: "File path of the Entity in the repository.", + Type: schema.TypeString, + Optional: true, + }, + "connector_ref": { + Description: "Identifier of the Harness Connector used for importing entity from Git" + helpers.Descriptions.ConnectorRefText.String(), + Type: schema.TypeString, + Optional: true, + }, + "repo_name": { + Description: "Name of the repository.", + Type: schema.TypeString, + Optional: true, + }, + "is_force_import": { + Description: "", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "template_import_request": { + Description: "Contains parameters for importing template.", + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "template_name": { + Description: "Name of the template.", + Type: schema.TypeString, + Optional: true, + }, + "template_version": { + Description: "Version of the template.", + Type: schema.TypeString, + Optional: true, + }, + "template_description": { + Description: "Description of the template.", + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, }, } @@ -249,43 +314,69 @@ func resourceTemplateCreateOrUpdate(ctx context.Context, d *schema.ResourceData, is_stable := d.Get("is_stable").(bool) if id == "" { - template := buildCreateTemplate(d) - if template.GitDetails != nil { - base_branch = optional.NewString(template.GitDetails.BaseBranch) - store_type = optional.NewString(template.GitDetails.StoreType) - commit_message = optional.NewString(template.GitDetails.CommitMessage) - connector_ref = optional.NewString(template.GitDetails.ConnectorRef) - branch_name = template.GitDetails.BranchName - } - if project_id != "" { - resp, httpResp, err = c.ProjectTemplateApi.CreateTemplatesProject(ctx, org_id, project_id, &nextgen.ProjectTemplateApiCreateTemplatesProjectOpts{ - Body: optional.NewInterface(template), - HarnessAccount: optional.NewString(c.AccountId), - }) - if resp.Identifier != "" { - template_id = resp.Identifier + + if d.Get("import_from_git").(bool) { + + template_id = d.Get("template_import_request.0.template_name").(string) + + template_import_request_body := createImportFromGitRequestForTemplates(d) + + if project_id != "" { + _, httpResp, err = c.ProjectTemplateApi.ImportProjectTemplatesFromGit(ctx, org_id, project_id, template_id, + &nextgen.TemplatesApiImportProjectTemplatesFromGitOpts{ + Body: optional.NewInterface(template_import_request_body), + HarnessAccount: optional.NewString(c.AccountId)}) + } else if org_id != "" && project_id == "" { + _, httpResp, err = c.OrgTemplateApi.ImportOrgTemplatesFromGit(ctx, org_id, template_id, + &nextgen.TemplatesApiImportOrgTemplatesFromGitOpts{ + Body: optional.NewInterface(template_import_request_body), + HarnessAccount: optional.NewString(c.AccountId)}) } else { - template_id = resp.Slug + _, httpResp, err = c.AccountTemplateApi.ImportAccountLevelTemplatesFromGit(ctx, template_id, + &nextgen.TemplatesApiImportTemplateFromGitOpts{ + Body: optional.NewInterface(template_import_request_body), + HarnessAccount: optional.NewString(c.AccountId)}) } - } else if org_id != "" && project_id == "" { - resp, httpResp, err = c.OrgTemplateApi.CreateTemplatesOrg(ctx, org_id, &nextgen.OrgTemplateApiCreateTemplatesOrgOpts{ - HarnessAccount: optional.NewString(c.AccountId), - Body: optional.NewInterface(template), - }) - if resp.Identifier != "" { - template_id = resp.Identifier - } else { - template_id = resp.Slug + + } else{ + template := buildCreateTemplate(d) + if template.GitDetails != nil { + base_branch = optional.NewString(template.GitDetails.BaseBranch) + store_type = optional.NewString(template.GitDetails.StoreType) + commit_message = optional.NewString(template.GitDetails.CommitMessage) + connector_ref = optional.NewString(template.GitDetails.ConnectorRef) + branch_name = template.GitDetails.BranchName } - } else { - resp, httpResp, err = c.AccountTemplateApi.CreateTemplatesAcc(ctx, &nextgen.AccountTemplateApiCreateTemplatesAccOpts{ - HarnessAccount: optional.NewString(c.AccountId), - Body: optional.NewInterface(template), - }) - if resp.Identifier != "" { - template_id = resp.Identifier + if project_id != "" { + resp, httpResp, err = c.ProjectTemplateApi.CreateTemplatesProject(ctx, org_id, project_id, &nextgen.ProjectTemplateApiCreateTemplatesProjectOpts{ + Body: optional.NewInterface(template), + HarnessAccount: optional.NewString(c.AccountId), + }) + if resp.Identifier != "" { + template_id = resp.Identifier + } else { + template_id = resp.Slug + } + } else if org_id != "" && project_id == "" { + resp, httpResp, err = c.OrgTemplateApi.CreateTemplatesOrg(ctx, org_id, &nextgen.OrgTemplateApiCreateTemplatesOrgOpts{ + HarnessAccount: optional.NewString(c.AccountId), + Body: optional.NewInterface(template), + }) + if resp.Identifier != "" { + template_id = resp.Identifier + } else { + template_id = resp.Slug + } } else { - template_id = resp.Slug + resp, httpResp, err = c.AccountTemplateApi.CreateTemplatesAcc(ctx, &nextgen.AccountTemplateApiCreateTemplatesAccOpts{ + HarnessAccount: optional.NewString(c.AccountId), + Body: optional.NewInterface(template), + }) + if resp.Identifier != "" { + template_id = resp.Identifier + } else { + template_id = resp.Slug + } } } } else { @@ -404,6 +495,50 @@ func resourceTemplateCreateOrUpdate(ctx context.Context, d *schema.ResourceData, return nil } +func createImportFromGitRequestForTemplates(d *schema.ResourceData) *nextgen.TemplatesImportRequestBody { + + template_git_import_details := &nextgen.GitImportDetails{} + if attr, ok := d.GetOk("git_import_details"); ok { + config := attr.([]interface{})[0].(map[string]interface{}) + if attr, ok := config["branch_name"]; ok { + template_git_import_details.BranchName = attr.(string) + } + if attr, ok := config["file_path"]; ok { + template_git_import_details.FilePath = attr.(string) + } + if attr, ok := config["connector_ref"]; ok { + template_git_import_details.ConnectorRef = attr.(string) + } + if attr, ok := config["repo_name"]; ok { + template_git_import_details.RepoName = attr.(string) + } + if attr, ok := config["is_force_import"]; ok { + template_git_import_details.IsForceImport = attr.(bool) + } + } + + template_import_request := &nextgen.TemplatesImportRequestDto{} + if attr, ok := d.GetOk("template_import_request"); ok { + config := attr.([]interface{})[0].(map[string]interface{}) + if attr, ok := config["template_name"]; ok { + template_import_request.TemplateName = attr.(string) + } + if attr, ok := config["template_version"]; ok { + template_import_request.TemplateVersion = attr.(string) + } + if attr, ok := config["template_description"]; ok { + template_import_request.TemplateDescription = attr.(string) + } + + } + + template_import_request_body := &nextgen.TemplatesImportRequestBody{} + template_import_request_body.GitImportDetails = template_git_import_details + template_import_request_body.TemplatesImportRequest = template_import_request + + return template_import_request_body +} + func resourceTemplateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c, ctx := meta.(*internal.Session).GetClientWithContext(ctx) diff --git a/internal/service/platform/template/resource_template_test.go b/internal/service/platform/template/resource_template_test.go index a58ded120..771308f22 100644 --- a/internal/service/platform/template/resource_template_test.go +++ b/internal/service/platform/template/resource_template_test.go @@ -169,6 +169,93 @@ func TestAccResourceTemplate_OrgScopeInline(t *testing.T) { }) } +func TestAccResourceTemplate_OrgScopeImportFromGit(t *testing.T) { + id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(6)) + name := id + + resourceName := "harness_platform_template.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccTemplateDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceTemplateOrgScopeImportFromGit(id, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", "orgtemplate"), + resource.TestCheckResourceAttr(resourceName, "name", "orgtemplate"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.OrgResourceImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"git_details.0.commit_message", "git_details.0.connector_ref", "git_details.0.store_type", "comments", "git_details.0.branch_name", "git_details.0.file_path", "git_details.0.last_commit_id", "git_details.0.repo_name", "git_import_details.#", "git_import_details.0.%", "git_import_details.0.branch_name", "git_import_details.0.connector_ref", "git_import_details.0.file_path", "git_import_details.0.is_force_import", "git_import_details.0.repo_name", "import_from_git", "is_stable", "template_import_request.#", "template_import_request.0.%", "template_import_request.0.template_description", "template_import_request.0.template_name", "template_import_request.0.template_version", "template_yaml", "version", "git_details.0.last_object_id"}, + }, + }, + }) +} + +func TestAccResourceTemplate_ProjectScopeImportFromGit(t *testing.T) { + id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(6)) + name := id + + resourceName := "harness_platform_template.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccTemplateDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceTemplateProjectScopeImportFromGit(id, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", "projecttemplate"), + resource.TestCheckResourceAttr(resourceName, "name", "projecttemplate"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.ProjectResourceImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"git_details.0.commit_message", "git_details.0.connector_ref", "git_details.0.store_type", "comments", "git_details.0.branch_name", "git_details.0.file_path", "git_details.0.last_commit_id", "git_details.0.repo_name", "git_import_details.#", "git_import_details.0.%", "git_import_details.0.branch_name", "git_import_details.0.connector_ref", "git_import_details.0.file_path", "git_import_details.0.is_force_import", "git_import_details.0.repo_name", "import_from_git", "is_stable", "template_import_request.#", "template_import_request.0.%", "template_import_request.0.template_description", "template_import_request.0.template_name", "template_import_request.0.template_version", "template_yaml", "version", "git_details.0.last_object_id"}, + }, + }, + }) +} + +func TestAccResourceTemplate_AccountScopeImportFromGit(t *testing.T) { + id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(6)) + name := id + + resourceName := "harness_platform_template.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccTemplateDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceTemplateAccountScopeImportFromGit(id, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", "accounttemplate"), + resource.TestCheckResourceAttr(resourceName, "name", "accounttemplate"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.OrgResourceImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"git_details.0.commit_message", "git_details.0.connector_ref", "git_details.0.store_type", "comments", "git_details.0.branch_name", "git_details.0.file_path", "git_details.0.last_commit_id", "git_details.0.repo_name", "git_import_details.#", "git_import_details.0.%", "git_import_details.0.branch_name", "git_import_details.0.connector_ref", "git_import_details.0.file_path", "git_import_details.0.is_force_import", "git_import_details.0.repo_name", "import_from_git", "is_stable", "template_import_request.#", "template_import_request.0.%", "template_import_request.0.template_description", "template_import_request.0.template_name", "template_import_request.0.template_version", "template_yaml", "version", "git_details.0.last_object_id"}, + }, + }, + }) +} + func TestAccResourceTemplate_OrgScopeInline_UpdateStable(t *testing.T) { id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(6)) name := id @@ -1220,3 +1307,88 @@ func testAccResourceTemplateProjectScopeInline(id string, name string) string { } `, id, name) } + +func testAccResourceTemplateOrgScopeImportFromGit(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_organization" "test" { + identifier = "%[1]s" + name = "%[2]s" + } + resource "harness_platform_template" "test" { + identifier = "orgtemplate" + org_id = "default" + name = "orgtemplate" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/orgtemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "orgtemplate" + template_version = "v2" + template_description = "" + } + } + `, id, name) +} + +func testAccResourceTemplateProjectScopeImportFromGit(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_organization" "test" { + identifier = "%[1]s" + name = "%[2]s" + } + resource "harness_platform_template" "test" { + identifier = "projecttemplate" + org_id = "default" + project_id = "V" + name = "projecttemplate" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/projecttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "projecttemplate" + template_version = "v2" + template_description = "" + } + } + `, id, name) +} + +func testAccResourceTemplateAccountScopeImportFromGit(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_organization" "test" { + identifier = "%[1]s" + name = "%[2]s" + } + resource "harness_platform_template" "test" { + identifier = "accounttemplate" + name = "accounttemplate" + version = "v2" + is_stable = false + import_from_git = true + git_import_details { + branch_name = "main" + file_path = ".harness/accounttemplate.yaml" + connector_ref = "account.DoNotDeleteGithub" + repo_name = "open-repo" + } + template_import_request { + template_name = "accounttemplate" + template_version = "v2" + template_description = "" + } + } + `, id, name) +} +