From bafb673c96b884333e4371bd74ccaca5bb6b3e51 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 19 Jun 2024 09:54:06 -0700 Subject: [PATCH 1/4] Return error if get xray version fails Remove get artifactory version since it is unnecessary --- pkg/xray/provider/framework.go | 4 ++-- pkg/xray/provider/sdkv2.go | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/pkg/xray/provider/framework.go b/pkg/xray/provider/framework.go index 1fb47bda..365345ba 100644 --- a/pkg/xray/provider/framework.go +++ b/pkg/xray/provider/framework.go @@ -158,9 +158,9 @@ func (p *XrayProvider) Configure(ctx context.Context, req provider.ConfigureRequ version, err := util.GetXrayVersion(restyClient) if err != nil { - resp.Diagnostics.AddWarning( + resp.Diagnostics.AddError( "Error getting Xray version", - fmt.Sprintf("The provider functionality might be affected by the absence of Xray version in the context. %v", err), + err.Error(), ) return } diff --git a/pkg/xray/provider/sdkv2.go b/pkg/xray/provider/sdkv2.go index d440d337..975208d7 100644 --- a/pkg/xray/provider/sdkv2.go +++ b/pkg/xray/provider/sdkv2.go @@ -122,11 +122,6 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVer } } - artifactoryVersion, err := util.GetArtifactoryVersion(restyClient) - if err != nil { - return nil, diag.FromErr(err) - } - xrayVersion, err := util.GetXrayVersion(restyClient) if err != nil { return nil, diag.FromErr(err) @@ -136,9 +131,8 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVer go util.SendUsage(ctx, restyClient.R(), productId, featureUsage) return util.ProviderMetadata{ - Client: restyClient, - ArtifactoryVersion: artifactoryVersion, - XrayVersion: xrayVersion, + Client: restyClient, + XrayVersion: xrayVersion, }, nil } From eb63bce443096a231aaa038753c642d06e86ee50 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 19 Jun 2024 09:54:30 -0700 Subject: [PATCH 2/4] Migrate xray_ignore_rule to Plugin Framework --- pkg/xray/provider/framework.go | 1 + pkg/xray/provider/sdkv2.go | 1 - .../resource/resource_xray_ignore_rule.go | 1181 ++++++++++------- .../resource_xray_ignore_rule_test.go | 40 +- 4 files changed, 737 insertions(+), 486 deletions(-) diff --git a/pkg/xray/provider/framework.go b/pkg/xray/provider/framework.go index 365345ba..e47bf866 100644 --- a/pkg/xray/provider/framework.go +++ b/pkg/xray/provider/framework.go @@ -187,6 +187,7 @@ func (p *XrayProvider) Resources(ctx context.Context) []func() resource.Resource xray_resource.NewBinaryManagerReposResource, xray_resource.NewBinaryManagerBuildsResource, xray_resource.NewCustomIssueResource, + xray_resource.NewIgnoreRuleResource, xray_resource.NewSettingsResource, xray_resource.NewWebhookResource, xray_resource.NewWorkersCountResource, diff --git a/pkg/xray/provider/sdkv2.go b/pkg/xray/provider/sdkv2.go index 975208d7..136e0c8a 100644 --- a/pkg/xray/provider/sdkv2.go +++ b/pkg/xray/provider/sdkv2.go @@ -59,7 +59,6 @@ func SdkV2() *schema.Provider { "xray_license_policy": xray.ResourceXrayLicensePolicyV2(), "xray_operational_risk_policy": xray.ResourceXrayOperationalRiskPolicy(), "xray_watch": xray.ResourceXrayWatch(), - "xray_ignore_rule": xray.ResourceXrayIgnoreRule(), "xray_repository_config": xray.ResourceXrayRepositoryConfig(), "xray_vulnerabilities_report": xray.ResourceXrayVulnerabilitiesReport(), "xray_licenses_report": xray.ResourceXrayLicensesReport(), diff --git a/pkg/xray/resource/resource_xray_ignore_rule.go b/pkg/xray/resource/resource_xray_ignore_rule.go index dabcd581..c8bba43a 100644 --- a/pkg/xray/resource/resource_xray_ignore_rule.go +++ b/pkg/xray/resource/resource_xray_ignore_rule.go @@ -6,575 +6,838 @@ import ( "regexp" "time" - "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/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/jfrog/terraform-provider-shared/util" - "github.com/jfrog/terraform-provider-shared/util/sdk" + utilfw "github.com/jfrog/terraform-provider-shared/util/fw" + validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" + "github.com/samber/lo" ) -type IgnoreRule struct { - Id string `json:"id,omitempty"` - ProjectKey string `json:"-"` - Author string `json:"author,omitempty"` - Created *time.Time `json:"created,omitempty"` - IsExpired bool `json:"is_expired,omitempty"` - Notes string `json:"notes"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` - IgnoreFilters IgnoreFilters `json:"ignore_filters"` +const ( + IgnoreRulesEndpoint = "xray/api/v1/ignore_rules" + IgnoreRuleEndpoint = "xray/api/v1/ignore_rules/{id}" +) + +var _ resource.Resource = &IgnoreRuleResource{} + +func NewIgnoreRuleResource() resource.Resource { + return &IgnoreRuleResource{} +} + +type IgnoreRuleResource struct { + ProviderData util.ProviderMetadata + TypeName string +} + +func (r *IgnoreRuleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_ignore_rule" + r.TypeName = resp.TypeName +} + +type IgnoreRuleResourceModel struct { + ID types.String `tfsdk:"id"` + ProjectKey types.String `tfsdk:"project_key"` + Notes types.String `tfsdk:"notes"` + ExpiredAt types.String `tfsdk:"expiration_date"` + Author types.String `tfsdk:"author"` + Created types.String `tfsdk:"created"` + IsExpired types.Bool `tfsdk:"is_expired"` + Vulnerabilities types.Set `tfsdk:"vulnerabilities"` + CVEs types.Set `tfsdk:"cves"` + Licenses types.Set `tfsdk:"licenses"` + OperationalRisks types.Set `tfsdk:"operational_risk"` + Policies types.Set `tfsdk:"policies"` + Watches types.Set `tfsdk:"watches"` + DockerLayers types.Set `tfsdk:"docker_layers"` + ReleaseBundles types.Set `tfsdk:"release_bundle"` + Builds types.Set `tfsdk:"build"` + Components types.Set `tfsdk:"component"` + Artifacts types.Set `tfsdk:"artifact"` +} + +func unpackFilterNameVersion(elem attr.Value, _ int) IgnoreFilterNameVersionAPIModel { + attrs := elem.(types.Object).Attributes() + return IgnoreFilterNameVersionAPIModel{ + Name: attrs["name"].(types.String).ValueString(), + Version: attrs["version"].(types.String).ValueString(), + } +} + +func unpackFilterNameVersionPath(elem attr.Value, _ int) IgnoreFilterNameVersionPathAPIModel { + attrs := elem.(types.Object).Attributes() + return IgnoreFilterNameVersionPathAPIModel{ + IgnoreFilterNameVersionAPIModel: IgnoreFilterNameVersionAPIModel{ + Name: attrs["name"].(types.String).ValueString(), + Version: attrs["version"].(types.String).ValueString(), + }, + Path: attrs["path"].(types.String).ValueString(), + } +} + +func (m IgnoreRuleResourceModel) toAPIModel(ctx context.Context, apiModel *IgnoreRuleAPIModel) (ds diag.Diagnostics) { + var created *time.Time + if m.Created.ValueString() != "" { + parsedTime, err := time.Parse("2006-01-02", m.Created.ValueString()) + if err != nil { + ds.AddError( + "failed to parse date/time string", + err.Error(), + ) + } + created = &parsedTime + } + + var expiresAt *time.Time + if m.ExpiredAt.ValueString() != "" { + parsedTime, err := time.Parse("2006-01-02", m.ExpiredAt.ValueString()) + if err != nil { + ds.AddError( + "failed to parse date/time string", + err.Error(), + ) + } + expiresAt = &parsedTime + } + + var vulnerabilities []string + ds.Append(m.Vulnerabilities.ElementsAs(ctx, &vulnerabilities, false)...) + + var cves []string + ds.Append(m.CVEs.ElementsAs(ctx, &cves, false)...) + + var licenses []string + ds.Append(m.Licenses.ElementsAs(ctx, &licenses, false)...) + + var watches []string + ds.Append(m.Watches.ElementsAs(ctx, &watches, false)...) + + var policies []string + ds.Append(m.Policies.ElementsAs(ctx, &policies, false)...) + + var operationalRisks []string + ds.Append(m.OperationalRisks.ElementsAs(ctx, &operationalRisks, false)...) + + var dockerLayers []string + ds.Append(m.DockerLayers.ElementsAs(ctx, &dockerLayers, false)...) + + releaseBundles := lo.Map( + m.ReleaseBundles.Elements(), + unpackFilterNameVersion, + ) + + builds := lo.Map( + m.Builds.Elements(), + unpackFilterNameVersion, + ) + + components := lo.Map( + m.Components.Elements(), + unpackFilterNameVersion, + ) + + artifacts := lo.Map( + m.Artifacts.Elements(), + unpackFilterNameVersionPath, + ) + + ignoreFilters := IgnoreFiltersAPIModel{ + Vulnerabilities: vulnerabilities, + CVEs: cves, + Licenses: licenses, + Watches: watches, + Policies: policies, + OperationalRisks: operationalRisks, + DockerLayers: dockerLayers, + ReleaseBundles: releaseBundles, + Builds: builds, + Components: components, + Artifacts: artifacts, + } + + *apiModel = IgnoreRuleAPIModel{ + ID: m.ID.ValueString(), + Author: m.Author.ValueString(), + Created: created, + IsExpired: m.IsExpired.ValueBool(), + Notes: m.Notes.ValueString(), + ExpiresAt: expiresAt, + IgnoreFilters: ignoreFilters, + } + + return +} + +var nameVersionPathResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "name": types.StringType, + "version": types.StringType, + "path": types.StringType, +} + +var nameVersionPathSetResourceModelAttributeTypes types.ObjectType = types.ObjectType{ + AttrTypes: nameVersionPathResourceModelAttributeTypes, } -type IgnoreFilters 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 []IgnoreFilterNameVersion `json:"release_bundles,omitempty"` - Builds []IgnoreFilterNameVersion `json:"builds,omitempty"` - Components []IgnoreFilterNameVersion `json:"components,omitempty"` - Artifacts []IgnoreFilterNameVersionPath `json:"artifacts,omitempty"` +var nameVersionResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "name": types.StringType, + "version": types.StringType, +} + +var nameVersionSetResourceModelAttributeTypes types.ObjectType = types.ObjectType{ + AttrTypes: nameVersionResourceModelAttributeTypes, +} + +func packNameVersion(models []IgnoreFilterNameVersionAPIModel) (basetypes.SetValue, diag.Diagnostics) { + diags := diag.Diagnostics{} + + nameVersions := lo.Map( + models, + func(property IgnoreFilterNameVersionAPIModel, _ 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 } -type IgnoreFilterNameVersion struct { +func packNameVersionPath(models []IgnoreFilterNameVersionPathAPIModel) (basetypes.SetValue, diag.Diagnostics) { + diags := diag.Diagnostics{} + + nameVersionPaths := lo.Map( + models, + func(property IgnoreFilterNameVersionPathAPIModel, _ int) attr.Value { + nameVersionPathMap := map[string]attr.Value{ + "name": types.StringNull(), + "version": types.StringNull(), + "path": types.StringNull(), + } + + if property.Name != "" { + nameVersionPathMap["name"] = types.StringValue(property.Name) + } + + if property.Version != "" { + nameVersionPathMap["version"] = types.StringValue(property.Version) + } + + if property.Version != "" { + nameVersionPathMap["path"] = types.StringValue(property.Path) + } + + return types.ObjectValueMust( + nameVersionPathResourceModelAttributeTypes, + nameVersionPathMap, + ) + }, + ) + + nameVersionPathSet, d := types.SetValue( + nameVersionPathSetResourceModelAttributeTypes, + nameVersionPaths, + ) + if d != nil { + diags.Append(d...) + } + + return nameVersionPathSet, diags +} + +func (m *IgnoreRuleResourceModel) fromAPIModel(ctx context.Context, apiModel IgnoreRuleAPIModel) diag.Diagnostics { + diags := diag.Diagnostics{} + + m.ID = types.StringValue(apiModel.ID) + m.Notes = types.StringValue(apiModel.Notes) + + author := types.StringNull() + if apiModel.Author != "" { + author = types.StringValue(apiModel.Author) + } + m.Author = author + + created := types.StringNull() + if apiModel.Created != nil { + created = types.StringValue(apiModel.Created.Format(time.RFC3339)) + } + m.Created = created + + expiresAt := types.StringNull() + if apiModel.ExpiresAt != nil { + expiresAt = types.StringValue(apiModel.ExpiresAt.Format("2006-01-02")) + } + m.ExpiredAt = expiresAt + + m.IsExpired = types.BoolValue(apiModel.IsExpired) + + vulnerabilities, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.Vulnerabilities) + if d != nil { + diags.Append(d...) + } + m.Vulnerabilities = vulnerabilities + + liceneses, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.Licenses) + if d != nil { + diags.Append(d...) + } + m.Licenses = liceneses + + cves, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.CVEs) + if d != nil { + diags.Append(d...) + } + m.CVEs = cves + + operationalRisks, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.OperationalRisks) + if d != nil { + diags.Append(d...) + } + m.OperationalRisks = operationalRisks + + watches, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.Watches) + if d != nil { + diags.Append(d...) + } + m.Watches = watches + + policies, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.Policies) + if d != nil { + diags.Append(d...) + } + m.Policies = policies + + dockerLayers, d := types.SetValueFrom(ctx, types.StringType, apiModel.IgnoreFilters.DockerLayers) + if d != nil { + diags.Append(d...) + } + m.DockerLayers = dockerLayers + + releaseBundles, d := packNameVersion(apiModel.IgnoreFilters.ReleaseBundles) + if d != nil { + diags.Append(d...) + } + m.ReleaseBundles = releaseBundles + + builds, d := packNameVersion(apiModel.IgnoreFilters.Builds) + if d != nil { + diags.Append(d...) + } + m.Builds = builds + + components, d := packNameVersion(apiModel.IgnoreFilters.Components) + if d != nil { + diags.Append(d...) + } + m.Components = components + + artifacts, d := packNameVersionPath(apiModel.IgnoreFilters.Artifacts) + if d != nil { + diags.Append(d...) + } + m.Artifacts = artifacts + + return diags +} + +type IgnoreRuleAPIModel struct { + ID string `json:"id,omitempty"` + Author string `json:"author,omitempty"` + Created *time.Time `json:"created,omitempty"` + IsExpired bool `json:"is_expired,omitempty"` + Notes string `json:"notes"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + IgnoreFilters IgnoreFiltersAPIModel `json:"ignore_filters"` +} + +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"` +} + +type IgnoreFilterNameVersionAPIModel struct { Name string `json:"name"` Version string `json:"version,omitempty"` } -type IgnoreFilterNameVersionPath struct { - IgnoreFilterNameVersion +type IgnoreFilterNameVersionPathAPIModel struct { + IgnoreFilterNameVersionAPIModel Path string `json:"path,omitempty"` } -func ResourceXrayIgnoreRule() *schema.Resource { - var ignoreRuleSchema = sdk.MergeMaps( - getProjectKeySchema(true, ""), - map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, +func (r *IgnoreRuleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ Computed: true, Description: "ID of the ignore rule", }, - "notes": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "project_key": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + validatorfw_string.ProjectKey(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Project key for assigning this resource to. Must be 2 - 10 lowercase alphanumeric and hyphen characters.", + }, + "notes": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Notes of the ignore rule", }, - "expiration_date": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(regexp.MustCompile(`^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$`), "Date must be in YYYY-MM-DD format")), - Description: "The Ignore Rule will be active until the expiration date. At that date it will automatically get deleted. The rule with the expiration date less than current day, will error out.", + "expiration_date": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$`), + "Date must be in YYYY-MM-DD format", + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "The Ignore Rule will be active until the expiration date. At that date it will automatically get deleted. The rule with the expiration date less than current day, will error out.", }, - "author": { - Type: schema.TypeString, + "author": schema.StringAttribute{ Computed: true, }, - "created": { - Type: schema.TypeString, + "created": schema.StringAttribute{ Computed: true, }, - "is_expired": { - Type: schema.TypeBool, + "is_expired": schema.BoolAttribute{ Computed: true, }, - "vulnerabilities": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Description: "List of specific vulnerabilities to ignore. Omit to apply to all.", - ConflictsWith: []string{"licenses", "operational_risk"}, - Elem: &schema.Schema{ - Type: schema.TypeString, + "vulnerabilities": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Validators: []validator.Set{ + setvalidator.ConflictsWith( + path.MatchRoot("licenses"), + path.MatchRoot("operational_risk"), + ), }, - }, - "cves": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Description: "List of specific CVEs to ignore. Omit to apply to all. Should set to 'any' when 'vulnerabilities' is set to 'any'.", - ConflictsWith: []string{"licenses", "operational_risk"}, - Elem: &schema.Schema{ - Type: schema.TypeString, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), }, + Description: "List of specific vulnerabilities to ignore. Omit to apply to all.", }, - "licenses": { - Type: schema.TypeSet, + "cves": schema.SetAttribute{ + ElementType: types.StringType, Optional: true, - ForceNew: true, - Description: "List of specific licenses to ignore. Omit to apply to all.", - Elem: &schema.Schema{ - Type: schema.TypeString, + Validators: []validator.Set{ + setvalidator.ConflictsWith( + path.MatchRoot("licenses"), + path.MatchRoot("operational_risk"), + ), }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of specific CVEs to ignore. Omit to apply to all. Should set to 'any' when 'vulnerabilities' is set to 'any'.", }, - "operational_risk": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Description: "Operational risk to ignore. Only accept 'any'", - ConflictsWith: []string{"vulnerabilities", "cves", "licenses"}, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"any"}, true)), + "licenses": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), }, + Description: "List of specific licenses to ignore. Omit to apply to all.", }, - "policies": { - Type: schema.TypeSet, + "operational_risk": schema.SetAttribute{ + ElementType: types.StringType, Optional: true, - ForceNew: true, - Description: "List of specific policies to ignore. Omit to apply to all.", - Elem: &schema.Schema{ - Type: schema.TypeString, + Validators: []validator.Set{ + setvalidator.ConflictsWith( + path.MatchRoot("licenses"), + path.MatchRoot("vulnerabilities"), + path.MatchRoot("cves"), + ), + setvalidator.ValueStringsAre( + stringvalidator.OneOf("any"), + ), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), }, + Description: "Operational risk to ignore. Only accept 'any'", }, - "watches": { - Type: schema.TypeSet, + "policies": schema.SetAttribute{ + ElementType: types.StringType, Optional: true, - ForceNew: true, - Description: "List of specific watches to ignore. Omit to apply to all.", - Elem: &schema.Schema{ - Type: schema.TypeString, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), }, + Description: "List of specific policies to ignore. Omit to apply to all.", }, - "docker_layers": { - Type: schema.TypeSet, + "watches": schema.SetAttribute{ + ElementType: types.StringType, Optional: true, - ForceNew: true, - Description: "List of Docker layer SHA256 hashes to ignore. Omit to apply to all.", - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(regexp.MustCompile(`^[0-9a-z]{64}$`), "Must be SHA256 hash")), + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), }, + Description: "List of specific watches to ignore. Omit to apply to all.", }, - "release_bundle": { - Type: schema.TypeSet, + "docker_layers": schema.SetAttribute{ + ElementType: types.StringType, Optional: true, - ForceNew: true, - Description: "List of specific release bundles to ignore. Omit to apply to all.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(`^[0-9a-z]{64}$`), "Must be SHA256 hash"), + ), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of Docker layer SHA256 hashes to ignore. Omit to apply to all.", + }, + }, + Blocks: map[string]schema.Block{ + "release_bundle": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Name of the release bundle", }, - "version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Version of the release bundle", - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), + "version": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Version of the release bundle", }, }, }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of specific release bundles to ignore. Omit to apply to all.", }, - "build": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Description: "List of specific builds to ignore. Omit to apply to all.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "build": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Name of the build", }, - "version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Version of the build", - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), + "version": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Version of the build", }, }, }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of specific builds to ignore. Omit to apply to all.", }, - "component": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Description: "List of specific components to ignore. Omit to apply to all.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "component": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Name of the component", }, - "version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Version of the component", - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), + "version": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Version of the component", }, }, }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of specific components to ignore. Omit to apply to all.", }, - "artifact": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"build", "release_bundle"}, - Description: "List of specific artifacts to ignore. Omit to apply to all.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Name of the artifact. Wildcards are not supported.", + "artifact": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Name of the artifact", }, - "version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Version of the artifact", - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), + "version": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Version of the artifact", }, - "path": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + "path": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.RegexMatches(regexp.MustCompile(`^.+\/$`), "Must end with a '/'"), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Path of the artifact. Must end with a '/'", - ValidateDiagFunc: validation.ToDiagFunc( - validation.All( - validation.StringIsNotEmpty, - validation.StringMatch(regexp.MustCompile(`^.+\/$`), "Must end with a '/'"), - ), - ), }, }, }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Description: "List of specific artifacts to ignore. Omit to apply to all.", }, }, - ) - - var packFilterNameVersion = func(filters []IgnoreFilterNameVersion) []interface{} { - var fs []interface{} - - for _, filter := range filters { - f := map[string]interface{}{ - "name": filter.Name, - "version": filter.Version, - } - - fs = append(fs, f) - } - - return fs + Description: "Provides an Xray ignore rule resource. See [Xray Ignore Rules](https://www.jfrog.com/confluence/display/JFROG/Ignore+Rules) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-IGNORERULES) for more details. Notice: at least one of the 'vulnerabilities/cves/liceneses', 'component', and 'docker_layers/artifact/build/release_bundle' should not be empty. When selecting the ignore criteria, take note of the combinations you choose. Some combinations such as omitting everything is not allowed as it will ignore all future violations (in the watch or in the system).", } +} - var packFilterNameVersionPath = func(filters []IgnoreFilterNameVersionPath) []interface{} { - var fs []interface{} +func (r *IgnoreRuleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + r.ProviderData = req.ProviderData.(util.ProviderMetadata) +} - for _, filter := range filters { - f := map[string]interface{}{ - "name": filter.Name, - "version": filter.Version, - "path": filter.Path, - } +func (r *IgnoreRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - fs = append(fs, f) - } + var plan IgnoreRuleResourceModel - return fs + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return } - var packIgnoreRule = func(ignoreRule IgnoreRule, d *schema.ResourceData) diag.Diagnostics { - if err := d.Set("id", ignoreRule.Id); err != nil { - return diag.FromErr(err) - } - if err := d.Set("notes", ignoreRule.Notes); err != nil { - return diag.FromErr(err) - } - if err := d.Set("author", ignoreRule.Author); err != nil { - return diag.FromErr(err) - } - if ignoreRule.Created != nil { - if err := d.Set("created", ignoreRule.Created.Format(time.RFC3339)); err != nil { - return diag.FromErr(err) - } - } - if ignoreRule.ExpiresAt != nil { - if err := d.Set("expiration_date", ignoreRule.ExpiresAt.Format("2006-01-02")); err != nil { - return diag.FromErr(err) - } - } - if err := d.Set("is_expired", ignoreRule.IsExpired); err != nil { - return diag.FromErr(err) - } - if len(ignoreRule.IgnoreFilters.Vulnerabilities) > 0 { - if err := d.Set("vulnerabilities", ignoreRule.IgnoreFilters.Vulnerabilities); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.Licenses) > 0 { - if err := d.Set("licenses", ignoreRule.IgnoreFilters.Licenses); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.CVEs) > 0 { - if err := d.Set("cves", ignoreRule.IgnoreFilters.CVEs); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.OperationalRisks) > 0 { - if err := d.Set("operational_risk", ignoreRule.IgnoreFilters.OperationalRisks); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.Watches) > 0 { - if err := d.Set("watches", ignoreRule.IgnoreFilters.Watches); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.Policies) > 0 { - if err := d.Set("policies", ignoreRule.IgnoreFilters.Policies); err != nil { - return diag.FromErr(err) - } - } - if len(ignoreRule.IgnoreFilters.DockerLayers) > 0 { - if err := d.Set("docker_layers", ignoreRule.IgnoreFilters.DockerLayers); err != nil { - return diag.FromErr(err) - } - } - if len(packFilterNameVersion(ignoreRule.IgnoreFilters.ReleaseBundles)) > 0 { - if err := d.Set("release_bundle", packFilterNameVersion(ignoreRule.IgnoreFilters.ReleaseBundles)); err != nil { - return diag.FromErr(err) - } - } - if len(packFilterNameVersion(ignoreRule.IgnoreFilters.Builds)) > 0 { - if err := d.Set("build", packFilterNameVersion(ignoreRule.IgnoreFilters.Builds)); err != nil { - return diag.FromErr(err) - } - } - if len(packFilterNameVersion(ignoreRule.IgnoreFilters.Components)) > 0 { - if err := d.Set("component", packFilterNameVersion(ignoreRule.IgnoreFilters.Components)); err != nil { - return diag.FromErr(err) - } - } - if len(packFilterNameVersionPath(ignoreRule.IgnoreFilters.Artifacts)) > 0 { - if err := d.Set("artifact", packFilterNameVersionPath(ignoreRule.IgnoreFilters.Artifacts)); err != nil { - return diag.FromErr(err) - } - } - - return nil + request, err := getRestyRequest(r.ProviderData.Client, plan.ProjectKey.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "failed to get Resty client", + err.Error(), + ) + return } - var unpackFilterNameVersion = func(attributeName string, d *schema.ResourceData) []IgnoreFilterNameVersion { - var filters []IgnoreFilterNameVersion - if v, ok := d.GetOk(attributeName); ok { - for _, f := range v.(*schema.Set).List() { - fMap := f.(map[string]interface{}) - filter := IgnoreFilterNameVersion{ - Name: fMap["name"].(string), - Version: fMap["version"].(string), - } - filters = append(filters, filter) - } - } - - return filters + type IgnoreRuleCreateResult struct { + Info string `json:"info"` } - var unpackFilterNameVersionPath = func(attributeName string, d *schema.ResourceData) []IgnoreFilterNameVersionPath { - var filters []IgnoreFilterNameVersionPath - if v, ok := d.GetOk(attributeName); ok { - for _, f := range v.(*schema.Set).List() { - fMap := f.(map[string]interface{}) - filter := IgnoreFilterNameVersionPath{ - IgnoreFilterNameVersion: IgnoreFilterNameVersion{ - Name: fMap["name"].(string), - Version: fMap["version"].(string), - }, - Path: fMap["path"].(string), - } - filters = append(filters, filter) - } - } - - return filters + var ignoreRule IgnoreRuleAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, &ignoreRule)...) + if resp.Diagnostics.HasError() { + return } - var unpackIgnnoreRule = func(d *schema.ResourceData) (IgnoreRule, error) { - ignoreRule := IgnoreRule{} - - ignoreRule.Id = d.Get("id").(string) - if v, ok := d.GetOk("project_key"); ok { - ignoreRule.ProjectKey = v.(string) - } - if v, ok := d.GetOk("notes"); ok { - ignoreRule.Notes = v.(string) - } - if v, ok := d.GetOk("expiration_date"); ok { - expirationDate, err := time.Parse("2006-01-02", v.(string)) - if err != nil { - return ignoreRule, err - } - ignoreRule.ExpiresAt = &expirationDate - } - - ignoreFilters := IgnoreFilters{} - data := &sdk.ResourceData{ResourceData: d} + var result IgnoreRuleCreateResult + response, err := request. + SetBody(ignoreRule). + SetResult(&result). + Post(IgnoreRulesEndpoint) + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, response.String()) + return + } - vulnerabilities := data.GetSet("vulnerabilities") - if len(vulnerabilities) > 0 { - ignoreFilters.Vulnerabilities = vulnerabilities - } + // response is in this json structure: + // { + // info": "Successfully added Ignore rule with id: c0e5b540-1988-42b2-6a86-b444cda1c521" + // } + // use regex to match the group for the ID + re := regexp.MustCompile(`(?m)^Successfully added Ignore rule with id: (.+)$`) + matches := re.FindStringSubmatch(result.Info) + if len(matches) > 1 { + plan.ID = types.StringValue(matches[1]) + } - cves := data.GetSet("cves") - if len(cves) > 0 { - ignoreFilters.CVEs = cves - } + // Fetch the ignore rule to fill out computed fields + response, err = request. + SetPathParam("id", plan.ID.ValueString()). + SetResult(&ignoreRule). + Get(IgnoreRuleEndpoint) + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } - licenses := data.GetSet("licenses") - if len(licenses) > 0 { - ignoreFilters.Licenses = licenses - } + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, response.String()) + return + } - watches := data.GetSet("watches") - if len(watches) > 0 { - ignoreFilters.Watches = watches - } + resp.Diagnostics.Append(plan.fromAPIModel(ctx, ignoreRule)...) + if resp.Diagnostics.HasError() { + return + } - policies := data.GetSet("policies") - if len(policies) > 0 { - ignoreFilters.Policies = policies - } + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} - operationalRisks := data.GetList("operational_risk") - if len(operationalRisks) > 0 { - ignoreFilters.OperationalRisks = operationalRisks - } +func (r *IgnoreRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - dockerLayers := data.GetSet("docker_layers") - if len(dockerLayers) > 0 { - ignoreFilters.DockerLayers = dockerLayers - } - ignoreFilters.ReleaseBundles = unpackFilterNameVersion("release_bundle", d) - ignoreFilters.Builds = unpackFilterNameVersion("build", d) - ignoreFilters.Components = unpackFilterNameVersion("component", d) - ignoreFilters.Artifacts = unpackFilterNameVersionPath("artifact", d) + var state IgnoreRuleResourceModel - ignoreRule.IgnoreFilters = ignoreFilters + // Read Terraform state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } - return ignoreRule, nil + request, err := getRestyRequest(r.ProviderData.Client, state.ProjectKey.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "failed to get Resty client", + err.Error(), + ) + return } - var resourceXrayIgnoreRuleRead = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var ignoreRule IgnoreRule + var ignoreRule IgnoreRuleAPIModel - projectKey := d.Get("project_key").(string) - req, err := getRestyRequest(m.(util.ProviderMetadata).Client, projectKey) - if err != nil { - return diag.FromErr(err) - } + response, err := request. + SetPathParam("id", state.ID.ValueString()). + SetResult(&ignoreRule). + Get(IgnoreRuleEndpoint) + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } - resp, err := req. - SetResult(&ignoreRule). - SetPathParam("id", d.Id()). - Get("xray/api/v1/ignore_rules/{id}") - if err != nil { - return diag.FromErr(err) - } - if resp.StatusCode() == http.StatusNotFound { - d.SetId("") - return diag.Errorf("ignore rule (%s) not found, removing from state", d.Id()) - } - if resp.IsError() { - return diag.Errorf("%s", resp.String()) - } + if response.StatusCode() == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } - return packIgnoreRule(ignoreRule, d) + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, response.String()) + return } - var resourceXrayIgnoreRuleCreate = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - ignoreRule, err := unpackIgnnoreRule(d) - if err != nil { - return diag.FromErr(err) - } + resp.Diagnostics.Append(state.fromAPIModel(ctx, ignoreRule)...) + if resp.Diagnostics.HasError() { + return + } - req, err := getRestyRequest(m.(util.ProviderMetadata).Client, ignoreRule.ProjectKey) - if err != nil { - return diag.FromErr(err) - } + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} - type IgnoreRuleCreateResponse struct { - Info string `json:"info"` - } +func (r *IgnoreRuleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // noop +} - var response IgnoreRuleCreateResponse +func (r *IgnoreRuleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - resp, err := req. - SetBody(ignoreRule). - SetResult(&response). - Post("xray/api/v1/ignore_rules") - if err != nil { - return diag.FromErr(err) - } - if resp.IsError() { - return diag.Errorf("%s", resp.String()) - } + var state IgnoreRuleResourceModel - // response is in this json structure: - // { - // info": "Successfully added Ignore rule with id: c0e5b540-1988-42b2-6a86-b444cda1c521" - // } - // use regex to match the group for the ID - re := regexp.MustCompile(`(?m)^Successfully added Ignore rule with id: (.+)$`) - matches := re.FindStringSubmatch(response.Info) - if len(matches) > 1 { - d.SetId(matches[1]) - } + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - return resourceXrayIgnoreRuleRead(ctx, d, m) + request, err := getRestyRequest(r.ProviderData.Client, state.ProjectKey.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "failed to get Resty client", + err.Error(), + ) + return } - var resourceXrayIgnoreRuleDelete = func(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - ignoreRule, err := unpackIgnnoreRule(d) - if err != nil { - return diag.FromErr(err) - } - - req, err := getRestyRequest(m.(util.ProviderMetadata).Client, ignoreRule.ProjectKey) - if err != nil { - return diag.FromErr(err) - } - - resp, err := req. - SetPathParam("id", d.Id()). - Delete("xray/api/v1/ignore_rules/{id}") - if err != nil { - return diag.FromErr(err) - } - if resp.IsError() { - return diag.Errorf("%s", resp.String()) - } + response, err := request. + SetPathParam("id", state.ID.ValueString()). + Delete(IgnoreRuleEndpoint) - d.SetId("") + if err != nil { + utilfw.UnableToDeleteResourceError(resp, err.Error()) + return + } - return nil + if response.IsError() { + utilfw.UnableToDeleteResourceError(resp, response.String()) + return } - return &schema.Resource{ - CreateContext: resourceXrayIgnoreRuleCreate, - ReadContext: resourceXrayIgnoreRuleRead, - DeleteContext: resourceXrayIgnoreRuleDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} - Schema: ignoreRuleSchema, - Description: "Provides an Xray ignore rule resource. See [Xray Ignore Rules](https://www.jfrog.com/confluence/display/JFROG/Ignore+Rules) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-IGNORERULES) for more details. Notice: at least one of the 'vulnerabilities/cves/liceneses', 'component', and 'docker_layers/artifact/build/release_bundle' should not be empty. When selecting the ignore criteria, take note of the combinations you choose. Some combinations such as omitting everything is not allowed as it will ignore all future violations (in the watch or in the system).", - } +// ImportState imports the resource into the Terraform state. +func (r *IgnoreRuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/pkg/xray/resource/resource_xray_ignore_rule_test.go b/pkg/xray/resource/resource_xray_ignore_rule_test.go index 7bc12b8c..7c8ac75a 100644 --- a/pkg/xray/resource/resource_xray_ignore_rule_test.go +++ b/pkg/xray/resource/resource_xray_ignore_rule_test.go @@ -9,7 +9,6 @@ import ( "github.com/go-resty/resty/v2" "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/jfrog/terraform-provider-shared/client" "github.com/jfrog/terraform-provider-shared/testutil" "github.com/jfrog/terraform-provider-shared/util" @@ -43,7 +42,7 @@ func TestAccIgnoreRule_UpgradeFromSDKv2(t *testing.T) { { ExternalProviders: map[string]resource.ExternalProvider{ "xray": { - VersionConstraint: "2.4.0", + VersionConstraint: "2.8.1", Source: "jfrog/xray", }, }, @@ -60,18 +59,9 @@ func TestAccIgnoreRule_UpgradeFromSDKv2(t *testing.T) { ), }, { - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Config: config, - // ConfigPlanChecks is a terraform-plugin-testing feature. - // If acceptance testing is still using terraform-plugin-sdk/v2, - // use `PlanOnly: true` instead. When migrating to - // terraform-plugin-testing, switch to `ConfigPlanChecks` or you - // will likely experience test failures. - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectEmptyPlan(), - }, - }, + ConfigPlanChecks: testutil.ConfigPlanChecks(""), }, }, }) @@ -103,7 +93,7 @@ func objectiveTestCase(objective string, t *testing.T) (*testing.T, resource.Tes return t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), Steps: []resource.TestStep{ { @@ -147,7 +137,7 @@ func TestAccIgnoreRule_operational_risk(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), Steps: []resource.TestStep{ { @@ -191,11 +181,11 @@ func TestAccIgnoreRule_invalid_operational_risk(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: config, - ExpectError: regexp.MustCompile(`expected operational_risk to be one of \["any"\], got invalid-risk`), + ExpectError: regexp.MustCompile(`.*Attribute operational_risk\[Value\(".+"\)\] value must be one of:.*`), }, }, }) @@ -516,7 +506,7 @@ func TestAccIgnoreRule_docker_layers(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), Steps: []resource.TestStep{ { @@ -606,7 +596,7 @@ func sourceTestCase(source string, t *testing.T) (*testing.T, resource.TestCase) return t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: config, @@ -653,7 +643,7 @@ func TestAccIgnoreRule_artifact(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), Steps: []resource.TestStep{ { @@ -701,7 +691,7 @@ func TestAccIgnoreRule_invalid_artifact_path(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: config, @@ -712,8 +702,7 @@ func TestAccIgnoreRule_invalid_artifact_path(t *testing.T) { } func TestAccIgnoreRule_with_project_key(t *testing.T) { - // skip for now as we haven't found a combo for ignore rule that works for projectKey query param - t.SkipNow() + 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) @@ -750,11 +739,10 @@ func TestAccIgnoreRule_with_project_key(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, ExternalProviders: map[string]resource.ExternalProvider{ "project": { - Source: "jfrog/project", - VersionConstraint: "1.5.1", + Source: "jfrog/project", }, }, CheckDestroy: acctest.VerifyDeleted(fqrn, "", testCheckIgnoreRule), From 0cea4657b77040f4028e11cacc1d741cd4a45aee Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 19 Jun 2024 09:57:30 -0700 Subject: [PATCH 3/4] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 113b8e98..83b387aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.8.2 (June 19, 2024) * resource/xray_custom_issue: Migrate from SDKv2 to Plugin Framework. PR: [#207](https://github.com/jfrog/terraform-provider-xray/pull/207) +* resource/xray_ignore_rule: Migrate from SDKv2 to Plugin Framework. PR: [#209](https://github.com/jfrog/terraform-provider-xray/pull/209) ## 2.8.1 (June 14, 2024). Tested on Artifactory 7.84.14 and Xray 3.96.1 with Terraform 1.8.5 and OpenTofu 1.7.2 From 720fd8d0a18f78014eb5f97cd858ea56f27e9857 Mon Sep 17 00:00:00 2001 From: JFrog CI Date: Wed, 19 Jun 2024 17:29:35 +0000 Subject: [PATCH 4/4] JFrog Pipelines - Add Artifactory version to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b387aa..630f05fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.8.2 (June 19, 2024) +## 2.8.2 (June 19, 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) * resource/xray_ignore_rule: Migrate from SDKv2 to Plugin Framework. PR: [#209](https://github.com/jfrog/terraform-provider-xray/pull/209)