From 02132efcb97fba6938f88166907637bcd9ad1f86 Mon Sep 17 00:00:00 2001 From: saf3dfsa Date: Sat, 28 Jun 2025 16:15:20 +0800 Subject: [PATCH] feat(CodeArts/Pipeline): support resource template --- docs/resources/codearts_build_task.md | 6 +- docs/resources/codearts_build_template.md | 140 +++++++++ huaweicloud/provider.go | 3 +- ...uaweicloud_codearts_build_template_test.go | 131 +++++++++ huaweicloud/services/codeartsbuild/common.go | 1 + ...esource_huaweicloud_codearts_build_task.go | 2 +- ...rce_huaweicloud_codearts_build_template.go | 272 ++++++++++++++++++ 7 files changed, 550 insertions(+), 5 deletions(-) create mode 100644 docs/resources/codearts_build_template.md create mode 100644 huaweicloud/services/acceptance/codeartsbuild/resource_huaweicloud_codearts_build_template_test.go create mode 100644 huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_template.go diff --git a/docs/resources/codearts_build_task.md b/docs/resources/codearts_build_task.md index 12aefb2126..8a86f80046 100644 --- a/docs/resources/codearts_build_task.md +++ b/docs/resources/codearts_build_task.md @@ -141,7 +141,7 @@ The `steps` block supports: * `enable` - (Optional, Bool) Specifies whether to enable the step. Defaults to **false**. -* `properties` - (Optional, Map) Specifies the build step properties. +* `properties` - (Optional, Map) Specifies the build step properties. Value is JSON format string. * `version` - (Optional, String) Specifies the build step version. @@ -166,13 +166,13 @@ In addition to all arguments above, the following attributes are exported: * `id` - The resource ID. -* `steps` - Specifies the build execution steps. +* `steps` - Indicates the build execution steps. The [steps](#attrblock--steps) structure is documented below. The `steps` block supports: -* `properties_all` - Specifies the build step properties. +* `properties_all` - Indicates the build step properties. ## Import diff --git a/docs/resources/codearts_build_template.md b/docs/resources/codearts_build_template.md new file mode 100644 index 0000000000..c7cadd3f74 --- /dev/null +++ b/docs/resources/codearts_build_template.md @@ -0,0 +1,140 @@ +--- +subcategory: "CodeArts Build" +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_codearts_build_template" +description: |- + Manages a CodeArts Build template resource within HuaweiCloud. +--- + +# huaweicloud_codearts_build_template + +Manages a CodeArts Build template resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_codearts_build_template" "test" { + name = "test-api" + description = "demo" + + steps { + enable = true + module_id = "devcloud2018.codeci_action_20057.action" + name = "update OBS" + properties = { + objectKey = jsonencode("./") + backetName = jsonencode("test") + uploadDirectory = jsonencode(true) + artifactSourcePath = jsonencode("bin/*") + authorizationUser = jsonencode({ + "displayName": "current user", + "value": "build" + }) + obsHeaders = jsonencode([ + { + "headerKey": "test", + "headerValue": "test" + } + ]) + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region in which to create the resource. + If omitted, the provider-level region will be used. + Changing this creates a new resource. + +* `name` - (Required, String, NonUpdatable) Specifies the name of the build template. + +* `steps` - (Required, List, NonUpdatable) Specifies the build execution steps. + The [steps](#block--steps) structure is documented below. + +* `description` - (Optional, String, NonUpdatable) Specifies the template description. + +* `parameters` - (Optional, List, NonUpdatable) Specifies the build execution parameter list. + The [parameters](#block--parameters) structure is documented below. + +* `tool_type` - (Optional, String, NonUpdatable) Specifies the tool type. + + +The `steps` block supports: + +* `module_id` - (Required, String, NonUpdatable) Specifies the build step module ID. + +* `name` - (Required, String, NonUpdatable) Specifies the build step name. + +* `enable` - (Optional, Bool, NonUpdatable) Specifies whether to enable the step. Defaults to **false**. + +* `properties` - (Optional, Map, NonUpdatable) Specifies the build step properties. Value is JSON format string. + +* `version` - (Optional, String, NonUpdatable) Specifies the build step version. + + +The `parameters` block supports: + +* `name` - (Optional, String, NonUpdatable) Specifies the parameter definition name. + +* `params` - (Optional, List, NonUpdatable) Specifies the build execution sub-parameters. + The [params](#block--parameters--params) structure is documented below. + + +The `params` block supports: + +* `name` - (Optional, String, NonUpdatable) Specifies the parameter field name. + +* `value` - (Optional, String, NonUpdatable) Specifies the parameter field value. + +* `limits` - (Optional, List, NonUpdatable) Specifies the enumeration parameter restrictions. + The [limits](#block--parameters--params--limits) structure is documented below. + + +The `limits` block supports: + +* `disable` - (Optional, String, NonUpdatable) Specifies whether it is effective. + +* `display_name` - (Optional, String, NonUpdatable) Specifies the displayed name of the parameter. + +* `name` - (Optional, String, NonUpdatable) Specifies the parameter name. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID. + +* `create_time` - Indicates the template creation time. + +* `favorite` - Indicates whether the template is favorite. + +* `nick_name` - Indicates the nick name. + +* `public` - Indicates whether the template is public. + +* `scope` - Indicates the scope. + +* `steps` - Indicates the build execution steps. + The [steps](#attrblock--steps) structure is documented below. + +* `template_id` - Indicates ID in database. + +* `type` - Indicates the template type. + +* `weight` - Indicates the weight of the template. + + +The `steps` block supports: + +* `properties_all` - Indicates the build step properties. + +## Import + +The template can be imported using `id`, e.g. + +```bash +$ terraform import huaweicloud_codearts_build_template.test +``` diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index dfa76ecf0a..ceebcca825 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -2726,7 +2726,8 @@ func Provider() *schema.Provider { "huaweicloud_codearts_pipeline_template": codeartspipeline.ResourceCodeArtsPipelineTemplate(), "huaweicloud_codearts_pipeline_service_endpoint": codeartspipeline.ResourceCodeArtsPipelineServiceEndpoint(), - "huaweicloud_codearts_build_task": codeartsbuild.ResourceCodeArtsBuildTask(), + "huaweicloud_codearts_build_task": codeartsbuild.ResourceCodeArtsBuildTask(), + "huaweicloud_codearts_build_template": codeartsbuild.ResourceCodeArtsBuildTemplate(), "huaweicloud_dsc_instance": dsc.ResourceDscInstance(), "huaweicloud_dsc_asset_obs": dsc.ResourceAssetObs(), diff --git a/huaweicloud/services/acceptance/codeartsbuild/resource_huaweicloud_codearts_build_template_test.go b/huaweicloud/services/acceptance/codeartsbuild/resource_huaweicloud_codearts_build_template_test.go new file mode 100644 index 0000000000..6a3e1e415f --- /dev/null +++ b/huaweicloud/services/acceptance/codeartsbuild/resource_huaweicloud_codearts_build_template_test.go @@ -0,0 +1,131 @@ +package codeartsbuild + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/codeartsbuild" +) + +func getBuildTemplateResourceFunc(conf *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := conf.NewServiceClient("codearts_build", acceptance.HW_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating CodeArts Build client: %s", err) + } + + return codeartsbuild.GetBuildTemplate(client, state.Primary.ID) +} + +func TestAccBuildTemplate_basic(t *testing.T) { + var obj interface{} + + name := acceptance.RandomAccResourceName() + rName := "huaweicloud_codearts_build_template.test" + + rc := acceptance.InitResourceCheck( + rName, + &obj, + getBuildTemplateResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acceptance.TestAccPreCheck(t) + }, + ProviderFactories: acceptance.TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testBuildTemplate_basic(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", name), + resource.TestCheckResourceAttr(rName, "description", "demo"), + resource.TestCheckResourceAttrSet(rName, "parameters.#"), + resource.TestCheckResourceAttrSet(rName, "steps.#"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"steps.1.properties"}, + }, + }, + }) +} + +func testBuildTemplate_basic(name string) string { + return fmt.Sprintf(` +resource "huaweicloud_codearts_build_template" "test" { + name = "%[1]s" + description = "demo" + + parameters { + name = "hudson.model.StringParameterDefinition" + + params { + name = "name" + value = "test" + } + params { + name = "type" + value = "customizeparam" + } + params { + name = "defaultValue" + value = "cs" + } + params { + name = "staticVar" + value = "false" + } + params { + name = "sensitiveVar" + value = "false" + } + params { + name = "deletion" + value = "false" + } + params { + name = "defaults" + value = "false" + } + } + + steps { + enable = true + module_id = "devcloud2018.codeci_action_20035.action" + name = "Docker Command" + } + + steps { + enable = true + module_id = "devcloud2018.codeci_action_20057.action" + name = "update OBS" + properties = { + objectKey = jsonencode("./") + backetName = jsonencode("test") + uploadDirectory = jsonencode(true) + artifactSourcePath = jsonencode("bin/*") + authorizationUser = jsonencode({ + "displayName": "current user", + "value": "build" + }) + obsHeaders = jsonencode([ + { + "headerKey": "1", + "headerValue": "1" + } + ]) + } + } +} +`, name) +} diff --git a/huaweicloud/services/codeartsbuild/common.go b/huaweicloud/services/codeartsbuild/common.go index 6f730557ad..d8774ef045 100644 --- a/huaweicloud/services/codeartsbuild/common.go +++ b/huaweicloud/services/codeartsbuild/common.go @@ -10,6 +10,7 @@ import ( const ( buildTaskNotFoundErr = "DEVCB.00031006" + templateNotFoundErr = "DEV.CB.0520002" ) // checkResponseError use to check whether the CodeArts Build API response with OkCode but body contains error code. diff --git a/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_task.go b/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_task.go index 17822e7719..361f63ba08 100644 --- a/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_task.go +++ b/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_task.go @@ -160,7 +160,7 @@ func resourceSchemeTaskSteps() *schema.Resource { Type: schema.TypeMap, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Description: `Specifies the build step properties.`, + Description: `Indicates the build step properties.`, }, }, } diff --git a/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_template.go b/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_template.go new file mode 100644 index 0000000000..d0fce4906d --- /dev/null +++ b/huaweicloud/services/codeartsbuild/resource_huaweicloud_codearts_build_template.go @@ -0,0 +1,272 @@ +package codeartsbuild + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/chnsz/golangsdk" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/common" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" +) + +var templateNonUpdatableParams = []string{ + "name", "description", "tool_type", "parameters", + "steps", "steps.*.module_id", "steps.*.name", "steps.*.properties", "steps.*.version", "steps.*.enable", +} + +// @API CodeArtsBuild POST /v1/template/create +// @API CodeArtsBuild GET /v1/template/custom +// @API CodeArtsBuild DELETE /v1/template/{uuid}/delete +func ResourceCodeArtsBuildTemplate() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceBuildTemplateCreate, + ReadContext: resourceBuildTemplateRead, + UpdateContext: resourceBuildTemplateUpdate, + DeleteContext: resourceBuildTemplateDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + CustomizeDiff: config.FlexibleForceNew(templateNonUpdatableParams), + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: `Specifies the name of the build template.`, + }, + "steps": { + Type: schema.TypeList, + Required: true, + Description: `Specifies the build execution steps.`, + Elem: resourceSchemeTaskSteps(), + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Specifies the template description.`, + }, + "tool_type": { + Type: schema.TypeString, + Optional: true, + Description: `Specifies the tool type.`, + }, + "parameters": { + Type: schema.TypeSet, + Optional: true, + Description: `Specifies the build execution parameter list.`, + Elem: resourceSchemeTaskParameters(), + }, + "enable_force_new": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"true", "false"}, false), + Description: utils.SchemaDesc("", utils.SchemaDescInput{Internal: true}), + }, + "favorite": { + Type: schema.TypeBool, + Computed: true, + Description: `Indicates whether the template is favorite.`, + }, + "nick_name": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the nick name.`, + }, + "template_id": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates ID in database.`, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the template type.`, + }, + "public": { + Type: schema.TypeBool, + Computed: true, + Description: `Indicates whether the template is public.`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the template creation time.`, + }, + "weight": { + Type: schema.TypeInt, + Computed: true, + Description: `Indicates the weight of the template.`, + }, + "scope": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the scope.`, + }, + }, + } +} + +func resourceBuildTemplateCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + client, err := cfg.NewServiceClient("codearts_build", cfg.GetRegion(d)) + if err != nil { + return diag.Errorf("error creating CodeArts Build client: %s", err) + } + + httpUrl := "v1/template/create" + createPath := client.Endpoint + httpUrl + createOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + JSONBody: utils.RemoveNil(buildCreateOrUpdateBuildTemplateBodyParams(d)), + } + + createResp, err := client.Request("POST", createPath, &createOpt) + if err != nil { + return diag.Errorf("error creating CodeArts Build template: %s", err) + } + createRespBody, err := utils.FlattenResponse(createResp) + if err != nil { + return diag.FromErr(err) + } + + if err := checkResponseError(createRespBody); err != nil { + return diag.Errorf("error creating CodeArts Build template: %s", err) + } + + id := utils.PathSearch("result.uuid", createRespBody, "").(string) + if id == "" { + return diag.Errorf("unable to find the CodeArts Build template ID from the API response") + } + + d.SetId(id) + + return resourceBuildTemplateRead(ctx, d, meta) +} + +func buildCreateOrUpdateBuildTemplateBodyParams(d *schema.ResourceData) map[string]interface{} { + bodyParams := map[string]interface{}{ + "name": d.Get("name"), + "description": d.Get("description"), + "tool_type": utils.ValueIgnoreEmpty(d.Get("tool_type")), + "parameters": buildBuildTaskParameters(d), + "template": map[string]interface{}{ + "steps": buildBuildTaskSteps(d), + }, + } + + return bodyParams +} + +func GetBuildTemplate(client *golangsdk.ServiceClient, id string) (interface{}, error) { + httpUrl := "v1/template/custom" + listPath := client.Endpoint + httpUrl + listOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + } + + pageSize := 100 + page := 1 + for { + currentPath := listPath + fmt.Sprintf("?page_size=%d&page=%d", pageSize, page) + listResp, err := client.Request("GET", currentPath, &listOpt) + if err != nil { + return nil, err + } + listRespBody, err := utils.FlattenResponse(listResp) + if err != nil { + return nil, err + } + + templates := utils.PathSearch("result.items", listRespBody, make([]interface{}, 0)).([]interface{}) + if len(templates) == 0 { + return nil, golangsdk.ErrDefault404{} + } + + searchPath := fmt.Sprintf("result.items[?uuid=='%s']|[0]", id) + result := utils.PathSearch(searchPath, listRespBody, nil) + if result != nil { + return result, nil + } + + page++ + } +} + +func resourceBuildTemplateRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + client, err := cfg.NewServiceClient("codearts_build", region) + if err != nil { + return diag.Errorf("error creating CodeArts Build client: %s", err) + } + + template, err := GetBuildTemplate(client, d.Id()) + if err != nil { + return common.CheckDeletedDiag(d, err, "error retrieving CodeArts Build template") + } + + mErr := multierror.Append(nil, + d.Set("region", region), + d.Set("name", utils.PathSearch("name", template, nil)), + d.Set("description", utils.PathSearch("description", template, nil)), + d.Set("tool_type", utils.PathSearch("tool_type", template, nil)), + d.Set("parameters", flattenBuildTaskParameters(template)), + d.Set("steps", flattenBuildTaskSteps(d, utils.PathSearch("template", template, nil))), + d.Set("nick_name", utils.PathSearch("nick_name", template, nil)), + d.Set("template_id", utils.PathSearch("id", template, nil)), + d.Set("type", utils.PathSearch("type", template, nil)), + d.Set("public", utils.PathSearch("public", template, nil)), + d.Set("create_time", utils.PathSearch("create_time", template, nil)), + d.Set("weight", utils.PathSearch("weight", template, nil)), + d.Set("scope", utils.PathSearch("scope", template, nil)), + + //nolint:misspell + d.Set("favorite", utils.PathSearch("favourite", template, nil)), + ) + + return diag.FromErr(mErr.ErrorOrNil()) +} + +func resourceBuildTemplateUpdate(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil +} + +func resourceBuildTemplateDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + client, err := cfg.NewServiceClient("codearts_build", region) + if err != nil { + return diag.Errorf("error creating CodeArts Build client: %s", err) + } + + httpUrl := "v1/template/{uuid}/delete" + deletePath := client.Endpoint + httpUrl + deletePath = strings.ReplaceAll(deletePath, "{uuid}", d.Id()) + deleteOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + } + + _, err = client.Request("DELETE", deletePath, &deleteOpt) + if err != nil { + return common.CheckDeletedDiag(d, common.ConvertUndefinedErrInto404Err(err, 422, "error_code", templateNotFoundErr), + "error deleting CodeArts Build template") + } + + return nil +}