diff --git a/CHANGELOG.md b/CHANGELOG.md index 133af08f..ee2b738e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ IMPROVEMENTS: * resource/xray_security_policy: Add `applicable_cves_only` attribute to support JFrog Advanced Security feature. PR: [#223](https://github.com/jfrog/terraform-provider-xray/pull/223) Issue: [#221](https://github.com/jfrog/terraform-provider-xray/issues/221) +BUG FIXES: + +* resource/xray_ignore_rule: Fix error when creating project specific ignore rule with build filter. PR: [#224](https://github.com/jfrog/terraform-provider-xray/pull/224) Issue: [#213](https://github.com/jfrog/terraform-provider-xray/issues/213) + ## 2.8.2 (June 21, 2024). Tested on Artifactory 7.84.15 and Xray 3.96.1 with Terraform 1.8.5 and OpenTofu 1.7.2 * resource/xray_custom_issue: Migrate from SDKv2 to Plugin Framework. PR: [#207](https://github.com/jfrog/terraform-provider-xray/pull/207) diff --git a/pkg/xray/resource/resource_xray_ignore_rule.go b/pkg/xray/resource/resource_xray_ignore_rule.go index 4ae5a5fc..b1dab5a5 100644 --- a/pkg/xray/resource/resource_xray_ignore_rule.go +++ b/pkg/xray/resource/resource_xray_ignore_rule.go @@ -75,6 +75,19 @@ func unpackFilterNameVersion(elem attr.Value, _ int) IgnoreFilterNameVersionAPIM } } +func unpackFilterNameVersionProject(projectKey string) func(elem attr.Value, _ int) IgnoreFilterNameVersionProjectAPIModel { + return func(elem attr.Value, _ int) IgnoreFilterNameVersionProjectAPIModel { + attrs := elem.(types.Object).Attributes() + return IgnoreFilterNameVersionProjectAPIModel{ + IgnoreFilterNameVersionAPIModel: IgnoreFilterNameVersionAPIModel{ + Name: attrs["name"].(types.String).ValueString(), + Version: attrs["version"].(types.String).ValueString(), + }, + Project: projectKey, + } + } +} + func unpackFilterNameVersionPath(elem attr.Value, _ int) IgnoreFilterNameVersionPathAPIModel { attrs := elem.(types.Object).Attributes() return IgnoreFilterNameVersionPathAPIModel{ @@ -139,7 +152,7 @@ func (m IgnoreRuleResourceModel) toAPIModel(ctx context.Context, apiModel *Ignor builds := lo.Map( m.Builds.Elements(), - unpackFilterNameVersion, + unpackFilterNameVersionProject(m.ProjectKey.ValueString()), ) components := lo.Map( @@ -235,6 +248,43 @@ func packNameVersion(models []IgnoreFilterNameVersionAPIModel) (basetypes.SetVal return nameVersionSet, diags } +func packNameVersionProject(models []IgnoreFilterNameVersionProjectAPIModel) (basetypes.SetValue, diag.Diagnostics) { + diags := diag.Diagnostics{} + + nameVersions := lo.Map( + models, + func(property IgnoreFilterNameVersionProjectAPIModel, _ int) attr.Value { + nameVersionMap := map[string]attr.Value{ + "name": types.StringNull(), + "version": types.StringNull(), + } + + if property.Name != "" { + nameVersionMap["name"] = types.StringValue(property.Name) + } + + if property.Version != "" { + nameVersionMap["version"] = types.StringValue(property.Version) + } + + return types.ObjectValueMust( + nameVersionResourceModelAttributeTypes, + nameVersionMap, + ) + }, + ) + + nameVersionSet, d := types.SetValue( + nameVersionSetResourceModelAttributeTypes, + nameVersions, + ) + if d != nil { + diags.Append(d...) + } + + return nameVersionSet, diags +} + func packNameVersionPath(models []IgnoreFilterNameVersionPathAPIModel) (basetypes.SetValue, diag.Diagnostics) { diags := diag.Diagnostics{} @@ -351,7 +401,7 @@ func (m *IgnoreRuleResourceModel) fromAPIModel(ctx context.Context, apiModel Ign } m.ReleaseBundles = releaseBundles - builds, d := packNameVersion(apiModel.IgnoreFilters.Builds) + builds, d := packNameVersionProject(apiModel.IgnoreFilters.Builds) if d != nil { diags.Append(d...) } @@ -383,17 +433,17 @@ type IgnoreRuleAPIModel struct { } type IgnoreFiltersAPIModel struct { - Vulnerabilities []string `json:"vulnerabilities,omitempty"` - Licenses []string `json:"licenses,omitempty"` - CVEs []string `json:"cves,omitempty"` - Policies []string `json:"policies,omitempty"` - Watches []string `json:"watches,omitempty"` - DockerLayers []string `json:"docker-layers,omitempty"` - OperationalRisks []string `json:"operational_risk,omitempty"` - ReleaseBundles []IgnoreFilterNameVersionAPIModel `json:"release_bundles,omitempty"` - Builds []IgnoreFilterNameVersionAPIModel `json:"builds,omitempty"` - Components []IgnoreFilterNameVersionAPIModel `json:"components,omitempty"` - Artifacts []IgnoreFilterNameVersionPathAPIModel `json:"artifacts,omitempty"` + Vulnerabilities []string `json:"vulnerabilities,omitempty"` + Licenses []string `json:"licenses,omitempty"` + CVEs []string `json:"cves,omitempty"` + Policies []string `json:"policies,omitempty"` + Watches []string `json:"watches,omitempty"` + DockerLayers []string `json:"docker-layers,omitempty"` + OperationalRisks []string `json:"operational_risk,omitempty"` + ReleaseBundles []IgnoreFilterNameVersionAPIModel `json:"release_bundles,omitempty"` + Builds []IgnoreFilterNameVersionProjectAPIModel `json:"builds,omitempty"` + Components []IgnoreFilterNameVersionAPIModel `json:"components,omitempty"` + Artifacts []IgnoreFilterNameVersionPathAPIModel `json:"artifacts,omitempty"` } type IgnoreFilterNameVersionAPIModel struct { @@ -401,6 +451,11 @@ type IgnoreFilterNameVersionAPIModel struct { Version string `json:"version,omitempty"` } +type IgnoreFilterNameVersionProjectAPIModel struct { + IgnoreFilterNameVersionAPIModel + Project string `json:"project,omitempty"` +} + type IgnoreFilterNameVersionPathAPIModel struct { IgnoreFilterNameVersionAPIModel Path string `json:"path,omitempty"` diff --git a/pkg/xray/resource/resource_xray_ignore_rule_test.go b/pkg/xray/resource/resource_xray_ignore_rule_test.go index 7c8ac75a..762eca0d 100644 --- a/pkg/xray/resource/resource_xray_ignore_rule_test.go +++ b/pkg/xray/resource/resource_xray_ignore_rule_test.go @@ -702,8 +702,6 @@ func TestAccIgnoreRule_invalid_artifact_path(t *testing.T) { } func TestAccIgnoreRule_with_project_key(t *testing.T) { - t.Skipf("skip for now as we haven't found a combo for ignore rule that works for projectKey query param") - _, fqrn, name := testutil.MkNames("ignore-rule-", "xray_ignore_rule") expirationDate := time.Now().Add(time.Hour * 48) projectKey := fmt.Sprintf("testproj%d", testutil.RandomInt()) @@ -725,6 +723,8 @@ func TestAccIgnoreRule_with_project_key(t *testing.T) { expiration_date = "{{ .expirationDate }}" project_key = project.{{ .projectKey }}.key + licenses = ["unknown"] + docker_layers = [ "2ae0e4835a9a6e22e35dd0fcce7d7354999476b7dad8698d2d7a77c80bfc647b", "a8db0e25d5916e70023114bb2d2497cd85327486bd6e0dc2092b349a1ab3a0a0" @@ -755,6 +755,60 @@ func TestAccIgnoreRule_with_project_key(t *testing.T) { }) } +func TestAccIgnoreRule_build_with_project_key(t *testing.T) { + _, fqrn, name := testutil.MkNames("ignore-rule-", "xray_ignore_rule") + expirationDate := time.Now().Add(time.Hour * 48) + projectKey := fmt.Sprintf("testproj%d", testutil.RandomInt()) + + config := util.ExecuteTemplate( + "TestAccIgnoreRule", + `resource "project" "{{ .projectKey }}" { + key = "{{ .projectKey }}" + display_name = "{{ .projectKey }}" + admin_privileges { + manage_members = true + manage_resources = true + index_resources = true + } + } + + resource "xray_ignore_rule" "{{ .name }}" { + notes = "fake notes" + expiration_date = "{{ .expirationDate }}" + project_key = project.{{ .projectKey }}.key + + licenses = ["unknown"] + + build { + name = "fake-name" + version = "fake-version" + } + }`, + map[string]interface{}{ + "name": name, + "expirationDate": expirationDate.Format("2006-01-02"), + "projectKey": projectKey, + }, + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "project": { + Source: "jfrog/project", + }, + }, + CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.TestCheckResourceAttr(fqrn, "project_key", projectKey), + }, + }, + }) +} + // testCheckIgnoreRule fetches the supposingly deleted ignore rule and verify it has been deleted // Xray applies soft delete to ignore rule and adds 'deleted_by' and 'deleted_at' // fields to the payload after a rule is deleted