From ccf759c17c6a9767aa7f8bb24909ae7fdeaf990b Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 16 Aug 2024 12:17:51 -0700 Subject: [PATCH 01/11] Migrate 'xray_repository_config' to Plugin Framework --- docs/resources/repository_config.md | 20 +- pkg/xray/provider/framework.go | 1 + pkg/xray/provider/sdkv2.go | 1 - .../resource/resource_xray_custom_issue.go | 7 +- .../resource_xray_repository_config.go | 1712 ++++++++++++----- .../resource_xray_repository_config_test.go | 173 +- 6 files changed, 1345 insertions(+), 569 deletions(-) diff --git a/docs/resources/repository_config.md b/docs/resources/repository_config.md index 0519d17e..1f610126 100644 --- a/docs/resources/repository_config.md +++ b/docs/resources/repository_config.md @@ -56,29 +56,25 @@ resource "xray_repository_config" "xray-repo-config" { ### Optional -- `config` (Block Set, Max: 1) Single repository configuration. Only one of 'config' or 'paths_config' can be set. (see [below for nested schema](#nestedblock--config)) +- `config` (Block Set) Single repository configuration. Only one of 'config' or 'paths_config' can be set. (see [below for nested schema](#nestedblock--config)) - `jas_enabled` (Boolean) Specified if JFrog Advanced Security is enabled or not. Default to 'false' -- `paths_config` (Block Set, Max: 1) Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned (see [below for nested schema](#nestedblock--paths_config)) - -### Read-Only - -- `id` (String) The ID of this resource. +- `paths_config` (Block Set) Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned (see [below for nested schema](#nestedblock--paths_config)) ### Nested Schema for `config` Optional: -- `exposures` (Block Set, Max: 1) Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type. (see [below for nested schema](#nestedblock--config--exposures)) +- `exposures` (Block Set) Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type. (see [below for nested schema](#nestedblock--config--exposures)) - `retention_in_days` (Number) The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository. - `vuln_contextual_analysis` (Boolean) Only for SaaS instances, will be available after Xray 3.59. Enables vulnerability contextual analysis. Must be set together with `exposures`. Supported for Docker, OCI, and Maven package types. ### Nested Schema for `config.exposures` -Required: +Optional: -- `scanners_category` (Block Set, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--config--exposures--scanners_category)) +- `scanners_category` (Block Set) (see [below for nested schema](#nestedblock--config--exposures--scanners_category)) ### Nested Schema for `config.exposures.scanners_category` @@ -96,10 +92,10 @@ Optional: ### Nested Schema for `paths_config` -Required: +Optional: -- `all_other_artifacts` (Block Set, Min: 1, Max: 1) If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting. (see [below for nested schema](#nestedblock--paths_config--all_other_artifacts)) -- `pattern` (Block List, Min: 1) Pattern, applied to the repositories. (see [below for nested schema](#nestedblock--paths_config--pattern)) +- `all_other_artifacts` (Block Set) If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting. (see [below for nested schema](#nestedblock--paths_config--all_other_artifacts)) +- `pattern` (Block Set) Pattern, applied to the repositories. (see [below for nested schema](#nestedblock--paths_config--pattern)) ### Nested Schema for `paths_config.all_other_artifacts` diff --git a/pkg/xray/provider/framework.go b/pkg/xray/provider/framework.go index 7bd85273..e9b9da7c 100644 --- a/pkg/xray/provider/framework.go +++ b/pkg/xray/provider/framework.go @@ -189,6 +189,7 @@ func (p *XrayProvider) Resources(ctx context.Context) []func() resource.Resource xray_resource.NewBinaryManagerReleaseBundlesV2Resource, xray_resource.NewCustomIssueResource, xray_resource.NewIgnoreRuleResource, + xray_resource.NewRepositoryConfigResource, xray_resource.NewSettingsResource, xray_resource.NewWatchResource, xray_resource.NewWebhookResource, diff --git a/pkg/xray/provider/sdkv2.go b/pkg/xray/provider/sdkv2.go index 5b08cff0..e8240f12 100644 --- a/pkg/xray/provider/sdkv2.go +++ b/pkg/xray/provider/sdkv2.go @@ -58,7 +58,6 @@ func SdkV2() *schema.Provider { "xray_security_policy": xray.ResourceXraySecurityPolicyV2(), "xray_license_policy": xray.ResourceXrayLicensePolicyV2(), "xray_operational_risk_policy": xray.ResourceXrayOperationalRiskPolicy(), - "xray_repository_config": xray.ResourceXrayRepositoryConfig(), "xray_vulnerabilities_report": xray.ResourceXrayVulnerabilitiesReport(), "xray_licenses_report": xray.ResourceXrayLicensesReport(), "xray_violations_report": xray.ResourceXrayViolationsReport(), diff --git a/pkg/xray/resource/resource_xray_custom_issue.go b/pkg/xray/resource/resource_xray_custom_issue.go index e7570c06..33da5b40 100644 --- a/pkg/xray/resource/resource_xray_custom_issue.go +++ b/pkg/xray/resource/resource_xray_custom_issue.go @@ -57,7 +57,9 @@ var validPackageTypes = []string{ var _ resource.Resource = &CustomIssueResource{} func NewCustomIssueResource() resource.Resource { - return &CustomIssueResource{} + return &CustomIssueResource{ + TypeName: "xray_custom_issue", + } } type CustomIssueResource struct { @@ -66,8 +68,7 @@ type CustomIssueResource struct { } func (r *CustomIssueResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_custom_issue" - r.TypeName = resp.TypeName + resp.TypeName = r.TypeName } type CustomIssueResourceModel struct { diff --git a/pkg/xray/resource/resource_xray_repository_config.go b/pkg/xray/resource/resource_xray_repository_config.go index cd2923b0..126332ae 100644 --- a/pkg/xray/resource/resource_xray_repository_config.go +++ b/pkg/xray/resource/resource_xray_repository_config.go @@ -6,595 +6,1365 @@ import ( "strconv" "strings" - "github.com/go-resty/resty/v2" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "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/jfrog/terraform-provider-shared/util" - "github.com/jfrog/terraform-provider-shared/util/sdk" - "github.com/jfrog/terraform-provider-shared/validator" + utilfw "github.com/jfrog/terraform-provider-shared/util/fw" + "github.com/samber/lo" "golang.org/x/exp/slices" ) -type RepoConfiguration struct { - // Omitempty is used because 'vuln_contextual_analysis' is not supported by self-hosted Xray installation. - VulnContextualAnalysis *bool `json:"vuln_contextual_analysis,omitempty"` - RetentionInDays int `json:"retention_in_days,omitempty"` - Exposures *Exposures `json:"exposures,omitempty"` -} +var _ resource.Resource = &RepoConfigResource{} -type Exposures struct { - ScannersCategory map[string]bool `json:"scanners_category"` +func NewRepositoryConfigResource() resource.Resource { + return &RepoConfigResource{ + TypeName: "xray_repository_config", + } } -type PathsConfiguration struct { - Patterns []Pattern `json:"patterns,omitempty"` - OtherArtifacts AllOtherArtifacts `json:"all_other_artifacts,omitempty"` +type RepoConfigResource struct { + ProviderData util.ProviderMetadata + TypeName string } -type Pattern struct { - Include string `json:"include"` - Exclude string `json:"exclude"` - IndexNewArtifacts bool `json:"index_new_artifacts"` - RetentionInDays int `json:"retention_in_days"` +type RepoConfigResourceModel struct { + RepoName types.String `tfsdk:"repo_name"` + JASEnabled types.Bool `tfsdk:"jas_enabled"` + Config types.Set `tfsdk:"config"` + PathsConfig types.Set `tfsdk:"paths_config"` } -type AllOtherArtifacts struct { - IndexNewArtifacts bool `json:"index_new_artifacts"` - RetentionInDays int `json:"retention_in_days"` -} +func (m RepoConfigResourceModel) toAPIModel(ctx context.Context, xrayVersion, packageType string, apiModel *RepositoryConfigurationAPIModel) (ds diag.Diagnostics) { + var repoConfig *RepoConfigurationAPIModel + if !m.Config.IsNull() && len(m.Config.Elements()) > 0 { + c := m.Config.Elements()[0].(types.Object) + configAttrs := c.Attributes() -type RepositoryConfiguration struct { - RepoName string `json:"repo_name"` - // Pointer is used to be able to verify if the RepoConfig or PathsConfiguration struct is nil - RepoConfig *RepoConfiguration `json:"repo_config,omitempty"` - RepoPathsConfig *PathsConfiguration `json:"repo_paths_config,omitempty"` -} + var vulnContextualAnalysis *bool + var exposures *ExposuresAPIModel -var exposuresPackageTypes = func(xrayVersion string) []string { - packageTypes := []string{"docker", "terraformbackend"} + if m.JASEnabled.ValueBool() { + if slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { + vulnContextualAnalysis = configAttrs["vuln_contextual_analysis"].(types.Bool).ValueBoolPointer() + } - if ok, err := util.CheckVersion(xrayVersion, "3.78.9"); err == nil && ok { - packageTypes = append(packageTypes, "maven", "npm", "pypi") + if slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { + exps := configAttrs["exposures"].(types.Set).Elements() + + if len(exps) > 0 { + expsAttrs := exps[0].(types.Object).Attributes() + scannerCategory := expsAttrs["scanners_category"].(types.Set).Elements() + + if len(scannerCategory) > 0 { + scannerCategoryAttrs := scannerCategory[0].(types.Object).Attributes() + + exp := ExposuresAPIModel{} + + switch packageType { + case "docker": + exp.ScannersCategory = map[string]bool{ + "services_scan": scannerCategoryAttrs["services"].(types.Bool).ValueBool(), + "secrets_scan": scannerCategoryAttrs["secrets"].(types.Bool).ValueBool(), + "applications_scan": scannerCategoryAttrs["applications"].(types.Bool).ValueBool(), + } + case "maven": + exp.ScannersCategory = map[string]bool{ + "secrets_scan": scannerCategoryAttrs["secrets"].(types.Bool).ValueBool(), + } + case "npm", "pypi": + exp.ScannersCategory = map[string]bool{ + "secrets_scan": scannerCategoryAttrs["secrets"].(types.Bool).ValueBool(), + "applications_scan": scannerCategoryAttrs["applications"].(types.Bool).ValueBool(), + } + case "terraformbackend": + exp.ScannersCategory = map[string]bool{ + "iac_scan": scannerCategoryAttrs["iac"].(types.Bool).ValueBool(), + } + } + + exposures = &exp + } + } + } + } + + repoConfig = &RepoConfigurationAPIModel{ + RetentionInDays: configAttrs["retention_in_days"].(types.Int64).ValueInt64(), + Exposures: exposures, + VulnContextualAnalysis: vulnContextualAnalysis, + } } - return packageTypes -} + var pathsConfig *PathsConfigurationAPIModel + if !m.PathsConfig.IsNull() && len(m.PathsConfig.Elements()) > 0 { + c := m.PathsConfig.Elements()[0].(types.Object) + configAttrs := c.Attributes() + patternsSet := configAttrs["pattern"].(types.Set) + + patterns := lo.Map( + patternsSet.Elements(), + func(elem attr.Value, _ int) PatternAPIModel { + attrs := elem.(types.Object).Attributes() + + return PatternAPIModel{ + Include: attrs["include"].(types.String).ValueString(), + Exclude: attrs["exclude"].(types.String).ValueString(), + IndexNewArtifacts: attrs["index_new_artifacts"].(types.Bool).ValueBool(), + RetentionInDays: attrs["retention_in_days"].(types.Int64).ValueInt64(), + } + }, + ) -var vulnContextualAnalysisPackageTypes = func(xrayVersion string) []string { - packageTypes := []string{"docker"} + allOtherArtifacts := configAttrs["all_other_artifacts"].(types.Set).Elements()[0] + allOtherArtifactsAttrs := allOtherArtifacts.(types.Object).Attributes() - if ok, err := util.CheckVersion(xrayVersion, "3.77.4"); err == nil && ok { - packageTypes = append(packageTypes, "maven") + pathsConfig = &PathsConfigurationAPIModel{ + Patterns: patterns, + OtherArtifacts: AllOtherArtifactsAPIModel{ + IndexNewArtifacts: allOtherArtifactsAttrs["index_new_artifacts"].(types.Bool).ValueBool(), + RetentionInDays: allOtherArtifactsAttrs["retention_in_days"].(types.Int64).ValueInt64(), + }, + } } - return packageTypes -} - -func ResourceXrayRepositoryConfig() *schema.Resource { - var repositoryConfigSchema = map[string]*schema.Schema{ - "repo_name": { - Type: schema.TypeString, - Required: true, - Description: "Repository name.", - ValidateDiagFunc: validator.StringIsNotEmpty, - }, - "jas_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Specified if JFrog Advanced Security is enabled or not. Default to 'false'", - }, - "config": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Description: "Single repository configuration. Only one of 'config' or 'paths_config' can be set.", - AtLeastOneOf: []string{"paths_config"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "vuln_contextual_analysis": { - Type: schema.TypeBool, - Optional: true, - Description: "Only for SaaS instances, will be available after Xray 3.59. Enables vulnerability contextual analysis. Must be set together with `exposures`. Supported for Docker, OCI, and Maven package types.", - }, - "retention_in_days": { - Type: schema.TypeInt, - Optional: true, - Default: 90, - Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", - ValidateDiagFunc: validator.IntAtLeast(0), - }, - "exposures": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Description: "Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "scanners_category": { - Type: schema.TypeSet, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "services": { - Type: schema.TypeBool, - Optional: true, - Description: "Detect whether common OSS libraries and services are configured securely, so application can be easily hardened by default.", - }, - "secrets": { - Type: schema.TypeBool, - Optional: true, - Description: "Detect any secret left exposed in any containers stored in Artifactory to stop any accidental leak of internal tokens or credentials.", - }, - "iac": { - Type: schema.TypeBool, - Optional: true, - Description: "Scans IaC files stored in Artifactory for early detection of cloud and infrastructure misconfigurations to prevent attacks and data leak. Only supported by Terraform Backend package type.", - }, - "applications": { - Type: schema.TypeBool, - Optional: true, - Description: "Detect whether common OSS libraries and services are used securely by the application.", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - "paths_config": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Description: "Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned", - AtLeastOneOf: []string{"config"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "pattern": { - Type: schema.TypeList, - Required: true, - MinItems: 1, - Description: "Pattern, applied to the repositories.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "include": { - Type: schema.TypeString, - Required: true, - Description: "Include pattern.", - ValidateDiagFunc: validator.StringIsNotEmpty, - }, - "exclude": { - Type: schema.TypeString, - Optional: true, - Description: "Exclude pattern.", - ValidateDiagFunc: validator.StringIsNotEmpty, - }, - "index_new_artifacts": { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", - }, - "retention_in_days": { - Type: schema.TypeInt, - Optional: true, - Default: 90, - Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", - ValidateDiagFunc: validator.IntAtLeast(0), - }, - }, - }, - }, - "all_other_artifacts": { - Type: schema.TypeSet, - Required: true, - Description: "If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting.", - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "index_new_artifacts": { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", - }, - "retention_in_days": { - Type: schema.TypeInt, - Optional: true, - Default: 90, - Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", - ValidateDiagFunc: validator.IntAtLeast(0), - }, - }, - }, - }, - }, - }, - }, + *apiModel = RepositoryConfigurationAPIModel{ + RepoName: m.RepoName.ValueString(), + RepoConfig: repoConfig, + RepoPathsConfig: pathsConfig, } - var unpackPattern = func(s []interface{}) []Pattern { - var patterns []Pattern + return +} - for _, raw := range s { - data := raw.(map[string]interface{}) - pattern := Pattern{ - Include: data["include"].(string), - Exclude: data["exclude"].(string), - IndexNewArtifacts: data["index_new_artifacts"].(bool), - RetentionInDays: data["retention_in_days"].(int), - } - patterns = append(patterns, pattern) - } +var configExposuresScannersCategoryResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "services": types.BoolType, + "secrets": types.BoolType, + "iac": types.BoolType, + "applications": types.BoolType, +} - return patterns - } +var configExposuresScannersCategorySetResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: configExposuresScannersCategoryResourceModelAttributeTypes, +} - var unpackAllOtherArtifacts = func(config *schema.Set) AllOtherArtifacts { - allOtherArtifacts := AllOtherArtifacts{} +var configExposuresResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "scanners_category": types.SetType{ + ElemType: configExposuresScannersCategorySetResourceModelElementTypes, + }, +} - if config != nil { - data := config.List()[0].(map[string]interface{}) - allOtherArtifacts.IndexNewArtifacts = data["index_new_artifacts"].(bool) - allOtherArtifacts.RetentionInDays = data["retention_in_days"].(int) - } +var configExposuresSetResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: configExposuresResourceModelAttributeTypes, +} - return allOtherArtifacts - } +var configResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "vuln_contextual_analysis": types.BoolType, + "retention_in_days": types.Int64Type, + "exposures": types.SetType{ + ElemType: configExposuresSetResourceModelElementTypes, + }, +} - var unpackRepoPathConfig = func(config *schema.Set) *PathsConfiguration { - repoPathsConfiguration := new(PathsConfiguration) - configList := config.List() - if len(configList) == 0 { - return nil - } +var configSetResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: configResourceModelAttributeTypes, +} - m := configList[0].(map[string]interface{}) +var pathsConfigPatternResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "include": types.StringType, + "exclude": types.StringType, + "index_new_artifacts": types.BoolType, + "retention_in_days": types.Int64Type, +} - otherArtifacts := unpackAllOtherArtifacts(m["all_other_artifacts"].(*schema.Set)) - repoPathsConfiguration.OtherArtifacts = otherArtifacts +var pathsConfigPatternResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: pathsConfigPatternResourceModelAttributeTypes, +} - repoPathsConfiguration.Patterns = unpackPattern(m["pattern"].([]interface{})) +var pathsConfigAllOtherArtifactsResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "index_new_artifacts": types.BoolType, + "retention_in_days": types.Int64Type, +} - return repoPathsConfiguration - } +var pathsConfigAllOtherArtifactsResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: pathsConfigAllOtherArtifactsResourceModelAttributeTypes, +} - var unpackExposures = func(config *schema.Set, xrayVersion, packageType string) *Exposures { - if !slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { - return nil - } +var pathsConfigResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{ + "pattern": types.SetType{ + ElemType: pathsConfigPatternResourceModelElementTypes, + }, + "all_other_artifacts": types.SetType{ + ElemType: pathsConfigAllOtherArtifactsResourceModelElementTypes, + }, +} - if len(config.List()) == 0 { - return nil - } +var pathsConfigSetResourceModelElementTypes types.ObjectType = types.ObjectType{ + AttrTypes: pathsConfigResourceModelAttributeTypes, +} - e := config.List()[0].(map[string]interface{}) - s := e["scanners_category"].(*schema.Set) - if len(s.List()) == 0 { - return nil - } +func (m *RepoConfigResourceModel) fromAPIModel(_ context.Context, xrayVersion, packageType string, apiModel RepositoryConfigurationAPIModel) diag.Diagnostics { + diags := diag.Diagnostics{} - category := s.List()[0].(map[string]interface{}) + m.RepoName = types.StringValue(apiModel.RepoName) + m.Config = types.SetNull(configSetResourceModelElementTypes) - exposures := Exposures{} + if apiModel.RepoConfig != nil { + vulnContextualAnalysis := types.BoolNull() + exposures := types.SetNull(configExposuresSetResourceModelElementTypes) - switch packageType { - case "docker": - exposures.ScannersCategory = map[string]bool{ - "services_scan": category["services"].(bool), - "secrets_scan": category["secrets"].(bool), - "applications_scan": category["applications"].(bool), - } - case "maven": - exposures.ScannersCategory = map[string]bool{ - "secrets_scan": category["secrets"].(bool), - } - case "npm", "pypi": - exposures.ScannersCategory = map[string]bool{ - "secrets_scan": category["secrets"].(bool), - "applications_scan": category["applications"].(bool), - } - case "terraformbackend": - exposures.ScannersCategory = map[string]bool{ - "iac_scan": category["iac"].(bool), + if m.JASEnabled.ValueBool() { + if apiModel.RepoConfig.VulnContextualAnalysis != nil && slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { + vulnContextualAnalysis = types.BoolPointerValue(apiModel.RepoConfig.VulnContextualAnalysis) } - } - return &exposures - } + if apiModel.RepoConfig.Exposures != nil && slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { + scannersCategoryAttrValues := map[string]attr.Value{ + "services": types.BoolNull(), + "secrets": types.BoolNull(), + "iac": types.BoolNull(), + "applications": types.BoolNull(), + } - var unpackRepoConfig = func(_ context.Context, config *schema.Set, xrayVersion, packageType string, jasEnabled bool) *RepoConfiguration { - repoConfig := new(RepoConfiguration) + switch packageType { + case "docker": + scannersCategoryAttrValues["services"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["services_scan"]) + scannersCategoryAttrValues["secrets"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["secrets_scan"]) + scannersCategoryAttrValues["applications"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["applications_scan"]) + case "maven": + scannersCategoryAttrValues["secrets"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["secrets_scan"]) + case "npm", "pypi": + scannersCategoryAttrValues["secrets"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["secrets_scan"]) + scannersCategoryAttrValues["applications"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["applications_scan"]) + case "terraformbackend": + scannersCategoryAttrValues["iac"] = types.BoolValue(apiModel.RepoConfig.Exposures.ScannersCategory["iac_scan"]) + } - if config != nil { - data := config.List()[0].(map[string]interface{}) - repoConfig.RetentionInDays = data["retention_in_days"].(int) + scannersCategory, d := types.ObjectValue( + configExposuresScannersCategoryResourceModelAttributeTypes, + scannersCategoryAttrValues, + ) + if d != nil { + diags.Append(d...) + } - if jasEnabled { - if slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { - vulnContextualAnalysis := data["vuln_contextual_analysis"].(bool) - repoConfig.VulnContextualAnalysis = &vulnContextualAnalysis + scannersCategorySet, d := types.SetValue( + configExposuresScannersCategorySetResourceModelElementTypes, + []attr.Value{scannersCategory}, + ) + if d != nil { + diags.Append(d...) } - repoConfig.Exposures = unpackExposures(data["exposures"].(*schema.Set), xrayVersion, packageType) - } - } - return repoConfig - } + exposure, d := types.ObjectValue( + configExposuresResourceModelAttributeTypes, + map[string]attr.Value{ + "scanners_category": scannersCategorySet, + }, + ) + if d != nil { + diags.Append(d...) + } - var unpackRepositoryConfig = func(ctx context.Context, s *schema.ResourceData, xrayVersion, packageType string) RepositoryConfiguration { - d := &sdk.ResourceData{ResourceData: s} + exposuresSet, d := types.SetValue( + configExposuresSetResourceModelElementTypes, + []attr.Value{exposure}, + ) + if d != nil { + diags.Append(d...) + } - repositoryConfig := RepositoryConfiguration{ - RepoName: d.GetString("repo_name", false), + exposures = exposuresSet + } } - jasEnabled := d.GetBool("jas_enabled", false) - - if v, ok := s.GetOk("config"); ok { - repositoryConfig.RepoConfig = unpackRepoConfig(ctx, v.(*schema.Set), xrayVersion, packageType, jasEnabled) + config, d := types.ObjectValue( + configResourceModelAttributeTypes, + map[string]attr.Value{ + "retention_in_days": types.Int64Value(apiModel.RepoConfig.RetentionInDays), + "vuln_contextual_analysis": vulnContextualAnalysis, + "exposures": exposures, + }, + ) + if d != nil { + diags.Append(d...) } - if v, ok := s.GetOk("paths_config"); ok { - repositoryConfig.RepoPathsConfig = unpackRepoPathConfig(v.(*schema.Set)) + configSet, d := types.SetValue( + configSetResourceModelElementTypes, + []attr.Value{config}, + ) + if d != nil { + diags.Append(d...) } - return repositoryConfig + m.Config = configSet } - var packExposures = func(exposures *Exposures, packageType string) []interface{} { - scannersCategory := map[string]bool{ - "services": false, - "secrets": false, - "iac": false, - "applications": false, - } + m.PathsConfig = types.SetNull(pathsConfigSetResourceModelElementTypes) + + if apiModel.RepoPathsConfig != nil { + patterns := lo.Map( + apiModel.RepoPathsConfig.Patterns, + func(pattern PatternAPIModel, _ int) attr.Value { + p, d := types.ObjectValue( + pathsConfigPatternResourceModelAttributeTypes, + map[string]attr.Value{ + "include": types.StringValue(pattern.Include), + "exclude": types.StringValue(pattern.Exclude), + "index_new_artifacts": types.BoolValue(pattern.IndexNewArtifacts), + "retention_in_days": types.Int64Value(pattern.RetentionInDays), + }, + ) + if d != nil { + diags.Append(d...) + } - switch packageType { - case "docker": - scannersCategory["services"] = exposures.ScannersCategory["services_scan"] - scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] - scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] - case "maven": - scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] - case "npm", "pypi": - scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] - scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] - case "terraformbackend": - scannersCategory["iac"] = exposures.ScannersCategory["iac_scan"] + return p + }, + ) + + patternSet, d := types.SetValue( + pathsConfigPatternResourceModelElementTypes, + patterns, + ) + if d != nil { + diags.Append(d...) } - return []interface{}{ - map[string][]map[string]bool{ - "scanners_category": {scannersCategory}, + allOtherArtifacts, d := types.ObjectValue( + pathsConfigAllOtherArtifactsResourceModelAttributeTypes, + map[string]attr.Value{ + "index_new_artifacts": types.BoolValue(apiModel.RepoPathsConfig.OtherArtifacts.IndexNewArtifacts), + "retention_in_days": types.Int64Value(apiModel.RepoPathsConfig.OtherArtifacts.RetentionInDays), }, + ) + if d != nil { + diags.Append(d...) } - } - var packGeneralRepoConfig = func(repoConfig *RepoConfiguration, xrayVersion, packageType string, jasEnabled bool) []interface{} { - if repoConfig == nil { - return []interface{}{} + allOtherArtifactsSet, d := types.SetValue( + pathsConfigAllOtherArtifactsResourceModelElementTypes, + []attr.Value{allOtherArtifacts}, + ) + if d != nil { + diags.Append(d...) } - m := map[string]interface{}{ - "retention_in_days": repoConfig.RetentionInDays, + pathsConfig, d := types.ObjectValue( + pathsConfigResourceModelAttributeTypes, + map[string]attr.Value{ + "pattern": patternSet, + "all_other_artifacts": allOtherArtifactsSet, + }, + ) + if d != nil { + diags.Append(d...) } - if jasEnabled { - if repoConfig.VulnContextualAnalysis != nil && slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { - m["vuln_contextual_analysis"] = *repoConfig.VulnContextualAnalysis - } - - if repoConfig.Exposures != nil && slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { - m["exposures"] = packExposures(repoConfig.Exposures, packageType) - } + pathsConfigSet, d := types.SetValue( + pathsConfigSetResourceModelElementTypes, + []attr.Value{pathsConfig}, + ) + if d != nil { + diags.Append(d...) } + m.PathsConfig = pathsConfigSet + } - return []interface{}{m} + return diags +} + +type RepositoryConfigurationAPIModel struct { + RepoName string `json:"repo_name"` + // Pointer is used to be able to verify if the RepoConfig or PathsConfiguration struct is nil + RepoConfig *RepoConfigurationAPIModel `json:"repo_config,omitempty"` + RepoPathsConfig *PathsConfigurationAPIModel `json:"repo_paths_config,omitempty"` +} + +type RepoConfigurationAPIModel struct { + // Omitempty is used because 'vuln_contextual_analysis' is not supported by self-hosted Xray installation. + VulnContextualAnalysis *bool `json:"vuln_contextual_analysis,omitempty"` + RetentionInDays int64 `json:"retention_in_days,omitempty"` + Exposures *ExposuresAPIModel `json:"exposures,omitempty"` +} + +type ExposuresAPIModel struct { + ScannersCategory map[string]bool `json:"scanners_category"` +} + +type PathsConfigurationAPIModel struct { + Patterns []PatternAPIModel `json:"patterns,omitempty"` + OtherArtifacts AllOtherArtifactsAPIModel `json:"all_other_artifacts,omitempty"` +} + +type PatternAPIModel struct { + Include string `json:"include"` + Exclude string `json:"exclude"` + IndexNewArtifacts bool `json:"index_new_artifacts"` + RetentionInDays int64 `json:"retention_in_days"` +} + +type AllOtherArtifactsAPIModel struct { + IndexNewArtifacts bool `json:"index_new_artifacts"` + RetentionInDays int64 `json:"retention_in_days"` +} + +func (r *RepoConfigResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "repo_name": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Repository name.", + }, + "jas_enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + Description: "Specified if JFrog Advanced Security is enabled or not. Default to 'false'", + }, + }, + Blocks: map[string]schema.Block{ + "config": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "vuln_contextual_analysis": schema.BoolAttribute{ + Optional: true, + Description: "Only for SaaS instances, will be available after Xray 3.59. Enables vulnerability contextual analysis. Must be set together with `exposures`. Supported for Docker, OCI, and Maven package types.", + }, + "retention_in_days": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(90), + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", + }, + }, + Blocks: map[string]schema.Block{ + "exposures": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "scanners_category": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "services": schema.BoolAttribute{ + Optional: true, + Description: "Detect whether common OSS libraries and services are configured securely, so application can be easily hardened by default.", + }, + "secrets": schema.BoolAttribute{ + Optional: true, + Description: "Detect any secret left exposed in any containers stored in Artifactory to stop any accidental leak of internal tokens or credentials.", + }, + "iac": schema.BoolAttribute{ + Optional: true, + Description: "Scans IaC files stored in Artifactory for early detection of cloud and infrastructure misconfigurations to prevent attacks and data leak. Only supported by Terraform Backend package type.", + }, + "applications": schema.BoolAttribute{ + Optional: true, + Description: "Detect whether common OSS libraries and services are used securely by the application.", + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + Description: "Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type.", + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + setvalidator.AtLeastOneOf(path.MatchRoot("paths_config")), + }, + Description: "Single repository configuration. Only one of 'config' or 'paths_config' can be set.", + }, + "paths_config": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "pattern": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "include": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Description: "Include pattern.", + }, + "exclude": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Description: "Exclude pattern.", + }, + "index_new_artifacts": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", + }, + "retention_in_days": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(90), + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + Description: "Pattern, applied to the repositories.", + }, + "all_other_artifacts": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "index_new_artifacts": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", + }, + "retention_in_days": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(90), + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeBetween(1, 1), + }, + Description: "If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting.", + }, + }, + }, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + setvalidator.AtLeastOneOf(path.MatchRoot("config")), + }, + Description: "Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned", + }, + }, + Description: "Provides an Xray repository config resource. See [Xray Indexing Resources](https://www.jfrog.com/confluence/display/JFROG/Indexing+Xray+Resources#IndexingXrayResources-SetaRetentionPeriod) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-UpdateRepositoriesConfigurations) for more details.", } +} - var packAllOtherArtifacts = func(otherArtifacts AllOtherArtifacts) []interface{} { - m := map[string]interface{}{ - "index_new_artifacts": otherArtifacts.IndexNewArtifacts, - "retention_in_days": otherArtifacts.RetentionInDays, - } +func (r *RepoConfigResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = r.TypeName +} - return []interface{}{m} +func (r *RepoConfigResource) 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) +} - var packPatterns = func(patterns []Pattern) []interface{} { - var ps []interface{} +func (r RepoConfigResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var data RepoConfigResourceModel - for _, pattern := range patterns { - p := map[string]interface{}{ - "include": pattern.Include, - "exclude": pattern.Exclude, - "index_new_artifacts": pattern.IndexNewArtifacts, - "retention_in_days": pattern.RetentionInDays, - } + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } - ps = append(ps, p) - } + // If jas_enabled is not configured, return without warning. + if data.JASEnabled.IsNull() || data.JASEnabled.IsUnknown() { + return + } - return ps + // If config is not configured, return without warning. + if data.Config.IsNull() { + return } - var packRepoPathsConfigList = func(repoPathsConfig *PathsConfiguration) []interface{} { - if repoPathsConfig == nil { - return []interface{}{} + if !data.JASEnabled.ValueBool() { + configs := data.Config.Elements() + if len(configs) == 0 { + return } - m := map[string]interface{}{ - "pattern": packPatterns(repoPathsConfig.Patterns), - "all_other_artifacts": packAllOtherArtifacts(repoPathsConfig.OtherArtifacts), + config := configs[0].(types.Object) + attrs := config.Attributes() + + if v, ok := attrs["vuln_contextual_analysis"]; ok && !v.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("config").AtListIndex(0).AtName("vuln_contextual_analysis"), + "Invalid Attribute Configuration", + "config.vuln_contextual_analysis can not be set when jas_enabled is set to 'true'", + ) + return } - return []interface{}{m} + if v, ok := attrs["exposures"]; ok && !v.IsNull() && len(v.(types.Set).Elements()) > 0 { + resp.Diagnostics.AddAttributeError( + path.Root("config").AtListIndex(0).AtName("exposures"), + "Invalid Attribute Configuration", + "config.exposures can not be set when jas_enabled is set to 'true'", + ) + return + } } +} - var packRepositoryConfig = func(repositoryConfig RepositoryConfiguration, d *schema.ResourceData, xrayVersion, packageType string) diag.Diagnostics { - if err := d.Set("repo_name", repositoryConfig.RepoName); err != nil { - return diag.FromErr(err) - } +func (r *RepoConfigResource) getPackageType(repoName string) (string, error) { - jasEnabled := false - if v, ok := d.GetOk("jas_enabled"); ok { - jasEnabled = v.(bool) - } + type Repository struct { + PackageType string `json:"packageType"` + } - if err := d.Set("config", packGeneralRepoConfig(repositoryConfig.RepoConfig, xrayVersion, packageType, jasEnabled)); err != nil { - return diag.FromErr(err) - } - if err := d.Set("paths_config", packRepoPathsConfigList(repositoryConfig.RepoPathsConfig)); err != nil { - return diag.FromErr(err) - } + var repo Repository - return nil + resp, err := r.ProviderData.Client.R(). + SetResult(&repo). + SetPathParam("repoKey", repoName). + Get("artifactory/api/repositories/{repoKey}") + + if err != nil { + return "", err } - var getPackageType = func(client *resty.Client, repoKey string) (repoType string, err error) { - type Repository struct { - PackageType string `json:"packageType"` - } + if resp.IsError() { + return "", fmt.Errorf(resp.String()) + } - repo := Repository{} + return repo.PackageType, nil +} - _, err = client.R(). - SetResult(&repo). - SetPathParam("repoKey", repoKey). - Get("artifactory/api/repositories/{repoKey}") - if err != nil { - fmt.Printf("err: %v\n", err) - return "", err - } +func (r *RepoConfigResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan RepoConfigResourceModel - return repo.PackageType, nil + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return } - var resourceXrayRepositoryConfigRead = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - repositoryConfig := RepositoryConfiguration{} - repoName := d.Id() + packageType, err := r.getPackageType(plan.RepoName.ValueString()) + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + } - metadata := m.(util.ProviderMetadata) + var repoConfig RepositoryConfigurationAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, r.ProviderData.XrayVersion, packageType, &repoConfig)...) + if resp.Diagnostics.HasError() { + return + } - resp, err := metadata.Client.R(). - SetResult(&repositoryConfig). - SetPathParam("repo_name", repoName). - Get("xray/api/v1/repos_config/{repo_name}") + response, err := r.ProviderData.Client.R(). + SetBody(repoConfig). + Put("xray/api/v1/repos_config") + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, response.String()) + return + } - if err != nil { - return diag.FromErr(err) - } - if resp.IsError() { - d.SetId("") - return diag.Errorf("repo (%s) is either not indexed or does not exist", repoName) - } + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} - packageType, err := getPackageType(metadata.Client, repoName) - if err != nil { - return diag.FromErr(err) - } +func (r *RepoConfigResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - return packRepositoryConfig(repositoryConfig, d, metadata.XrayVersion, packageType) + var state RepoConfigResourceModel + + // Read Terraform state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - var resourceXrayRepositoryConfigCreate = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - metadata := m.(util.ProviderMetadata) - packageType, err := getPackageType(metadata.Client, d.Get("repo_name").(string)) - if err != nil { - return diag.FromErr(err) - } + var repoConfig RepositoryConfigurationAPIModel - repositoryConfig := unpackRepositoryConfig(ctx, d, metadata.XrayVersion, packageType) + repoName := state.RepoName.ValueString() - resp, err := metadata.Client.R().SetBody(&repositoryConfig).Put("xray/api/v1/repos_config") - if err != nil { - return diag.FromErr(err) - } - if resp.IsError() { - return diag.Errorf("%s", resp.String()) - } + response, err := r.ProviderData.Client.R(). + SetPathParam("repo_name", repoName). + SetResult(&repoConfig). + Get("xray/api/v1/repos_config/{repo_name}") - d.SetId(repositoryConfig.RepoName) - return resourceXrayRepositoryConfigRead(ctx, d, m) + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return } - // No delete functionality provided by API. - // Delete function will return a warning and remove the Id from the state. - // The option with restoring a default configuration is not viable, because the default can be changed. - var resourceXrayRepositoryConfigDelete = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Warn(ctx, fmt.Sprintf("There is no delete dunctionality in the API, so the configuration is not "+ - "removed from the Artifactory, but (%s) is removed from the Terraform state", d.Id())) - d.SetId("") + if response.IsError() { + utilfw.UnableToRefreshResourceError(resp, response.String()) + return + } - return diag.Diagnostics{{ - Severity: diag.Warning, - Summary: "No delete functionality provided by API", - Detail: "Delete function will return a warning and remove the Id from the Terraform state. The actual repository configuration will remain unchanged.", - }} + packageType, err := r.getPackageType(repoName) + if err != nil { + resp.Diagnostics.AddError( + "Unable to get repository data", + err.Error(), + ) + return } - var jasEnabledResourceDiff = func(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error { - jasEnabled := false - if v, ok := diff.GetOk("jas_enabled"); ok { - jasEnabled = v.(bool) - } + resp.Diagnostics.Append(state.fromAPIModel(ctx, r.ProviderData.XrayVersion, packageType, repoConfig)...) + if resp.Diagnostics.HasError() { + return + } - if !jasEnabled { - configSet := diff.Get("config").(*schema.Set).List() - if len(configSet) == 0 { - return nil - } + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} - config := configSet[0].(map[string]interface{}) +func (r *RepoConfigResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - if v, ok := config["vuln_contextual_analysis"]; ok && v.(bool) { - return fmt.Errorf("config.vuln_contextual_analysis can not be set when jas_enabled is set to 'true'") - } + var plan RepoConfigResourceModel - tflog.Debug(ctx, "jasEnabledResourceDiff", map[string]any{ - "config": config, - "config['exposures']": config["exposures"], - }) - if v, ok := config["exposures"]; ok && v.(*schema.Set).Len() > 0 { - return fmt.Errorf("config.exposures can not be set when jas_enabled is set to 'true'") - } - } + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + packageType, err := r.getPackageType(plan.RepoName.ValueString()) + if err != nil { + utilfw.UnableToUpdateResourceError(resp, err.Error()) + } + + var repoConfig RepositoryConfigurationAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, r.ProviderData.XrayVersion, packageType, &repoConfig)...) + if resp.Diagnostics.HasError() { + return + } - return nil + response, err := r.ProviderData.Client.R(). + SetBody(repoConfig). + Put("xray/api/v1/repos_config") + if err != nil { + utilfw.UnableToUpdateResourceError(resp, err.Error()) + return + } + if response.IsError() { + utilfw.UnableToUpdateResourceError(resp, response.String()) + return } - return &schema.Resource{ - CreateContext: resourceXrayRepositoryConfigCreate, - ReadContext: resourceXrayRepositoryConfigRead, - UpdateContext: resourceXrayRepositoryConfigCreate, - DeleteContext: resourceXrayRepositoryConfigDelete, + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} - Importer: &schema.ResourceImporter{ - StateContext: func(_ context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - parts := strings.SplitN(d.Id(), ":", 2) +func (r *RepoConfigResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) - if len(parts) == 2 && parts[0] != "" && parts[1] != "" { - d.SetId(parts[0]) - jasEnabled, err := strconv.ParseBool(parts[1]) - if err != nil { - return nil, err - } - d.Set("jas_enabled", jasEnabled) - } + resp.Diagnostics.AddWarning( + "No delete functionality provided by API", + "Delete function will return a warning and remove the Id from the Terraform state. The actual repository configuration will remain unchanged.", + ) - return []*schema.ResourceData{d}, nil - }, - }, + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} - CustomizeDiff: jasEnabledResourceDiff, +// ImportState imports the resource into the Terraform state. +func (r *RepoConfigResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parts := strings.SplitN(req.ID, ":", 2) - Schema: repositoryConfigSchema, - Description: "Provides an Xray repository config resource. See [Xray Indexing Resources](https://www.jfrog.com/confluence/display/JFROG/Indexing+Xray+Resources#IndexingXrayResources-SetaRetentionPeriod) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-UpdateRepositoriesConfigurations) for more details.", + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("repo_name"), parts[0])...) + + jasEnabled, err := strconv.ParseBool(parts[1]) + if err != nil { + resp.Diagnostics.AddError( + "failed to parse import field 'jas_enabled'", + err.Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("jas_enabled"), jasEnabled)...) } } + +var exposuresPackageTypes = func(xrayVersion string) []string { + packageTypes := []string{"docker", "terraformbackend"} + + if ok, err := util.CheckVersion(xrayVersion, "3.78.9"); err == nil && ok { + packageTypes = append(packageTypes, "maven", "npm", "pypi") + } + + return packageTypes +} + +var vulnContextualAnalysisPackageTypes = func(xrayVersion string) []string { + packageTypes := []string{"docker"} + + if ok, err := util.CheckVersion(xrayVersion, "3.77.4"); err == nil && ok { + packageTypes = append(packageTypes, "maven") + } + + return packageTypes +} + +// +// func ResourceXrayRepositoryConfig() *schema.Resource { +// var repositoryConfigSchema = map[string]*schema.Schema{ +// "repo_name": { +// Type: schema.TypeString, +// Required: true, +// Description: "Repository name.", +// ValidateDiagFunc: validator.StringIsNotEmpty, +// }, +// "jas_enabled": { +// Type: schema.TypeBool, +// Optional: true, +// Default: false, +// Description: "Specified if JFrog Advanced Security is enabled or not. Default to 'false'", +// }, +// "config": { +// Type: schema.TypeSet, +// Optional: true, +// MaxItems: 1, +// Description: "Single repository configuration. Only one of 'config' or 'paths_config' can be set.", +// AtLeastOneOf: []string{"paths_config"}, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "vuln_contextual_analysis": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Only for SaaS instances, will be available after Xray 3.59. Enables vulnerability contextual analysis. Must be set together with `exposures`. Supported for Docker, OCI, and Maven package types.", +// }, +// "retention_in_days": { +// Type: schema.TypeInt, +// Optional: true, +// Default: 90, +// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", +// ValidateDiagFunc: validator.IntAtLeast(0), +// }, +// "exposures": { +// Type: schema.TypeSet, +// Optional: true, +// MaxItems: 1, +// Description: "Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type.", +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "scanners_category": { +// Type: schema.TypeSet, +// Required: true, +// MaxItems: 1, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "services": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Detect whether common OSS libraries and services are configured securely, so application can be easily hardened by default.", +// }, +// "secrets": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Detect any secret left exposed in any containers stored in Artifactory to stop any accidental leak of internal tokens or credentials.", +// }, +// "iac": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Scans IaC files stored in Artifactory for early detection of cloud and infrastructure misconfigurations to prevent attacks and data leak. Only supported by Terraform Backend package type.", +// }, +// "applications": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Detect whether common OSS libraries and services are used securely by the application.", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// "paths_config": { +// Type: schema.TypeSet, +// Optional: true, +// MaxItems: 1, +// Description: "Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned", +// AtLeastOneOf: []string{"config"}, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "pattern": { +// Type: schema.TypeList, +// Required: true, +// MinItems: 1, +// Description: "Pattern, applied to the repositories.", +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "include": { +// Type: schema.TypeString, +// Required: true, +// Description: "Include pattern.", +// ValidateDiagFunc: validator.StringIsNotEmpty, +// }, +// "exclude": { +// Type: schema.TypeString, +// Optional: true, +// Description: "Exclude pattern.", +// ValidateDiagFunc: validator.StringIsNotEmpty, +// }, +// "index_new_artifacts": { +// Type: schema.TypeBool, +// Optional: true, +// Default: true, +// Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", +// }, +// "retention_in_days": { +// Type: schema.TypeInt, +// Optional: true, +// Default: 90, +// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", +// ValidateDiagFunc: validator.IntAtLeast(0), +// }, +// }, +// }, +// }, +// "all_other_artifacts": { +// Type: schema.TypeSet, +// Required: true, +// Description: "If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting.", +// MinItems: 1, +// MaxItems: 1, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "index_new_artifacts": { +// Type: schema.TypeBool, +// Optional: true, +// Default: true, +// Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", +// }, +// "retention_in_days": { +// Type: schema.TypeInt, +// Optional: true, +// Default: 90, +// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", +// ValidateDiagFunc: validator.IntAtLeast(0), +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// } +// +// var unpackPattern = func(s []interface{}) []Pattern { +// var patterns []Pattern +// +// for _, raw := range s { +// data := raw.(map[string]interface{}) +// pattern := Pattern{ +// Include: data["include"].(string), +// Exclude: data["exclude"].(string), +// IndexNewArtifacts: data["index_new_artifacts"].(bool), +// RetentionInDays: data["retention_in_days"].(int), +// } +// patterns = append(patterns, pattern) +// } +// +// return patterns +// } +// +// var unpackAllOtherArtifacts = func(config *schema.Set) AllOtherArtifacts { +// allOtherArtifacts := AllOtherArtifacts{} +// +// if config != nil { +// data := config.List()[0].(map[string]interface{}) +// allOtherArtifacts.IndexNewArtifacts = data["index_new_artifacts"].(bool) +// allOtherArtifacts.RetentionInDays = data["retention_in_days"].(int) +// } +// +// return allOtherArtifacts +// } +// +// var unpackRepoPathConfig = func(config *schema.Set) *PathsConfiguration { +// repoPathsConfiguration := new(PathsConfiguration) +// configList := config.List() +// if len(configList) == 0 { +// return nil +// } +// +// m := configList[0].(map[string]interface{}) +// +// otherArtifacts := unpackAllOtherArtifacts(m["all_other_artifacts"].(*schema.Set)) +// repoPathsConfiguration.OtherArtifacts = otherArtifacts +// +// repoPathsConfiguration.Patterns = unpackPattern(m["pattern"].([]interface{})) +// +// return repoPathsConfiguration +// } +// +// var unpackExposures = func(config *schema.Set, xrayVersion, packageType string) *Exposures { +// if !slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { +// return nil +// } +// +// if len(config.List()) == 0 { +// return nil +// } +// +// e := config.List()[0].(map[string]interface{}) +// s := e["scanners_category"].(*schema.Set) +// if len(s.List()) == 0 { +// return nil +// } +// +// category := s.List()[0].(map[string]interface{}) +// +// exposures := Exposures{} +// +// switch packageType { +// case "docker": +// exposures.ScannersCategory = map[string]bool{ +// "services_scan": category["services"].(bool), +// "secrets_scan": category["secrets"].(bool), +// "applications_scan": category["applications"].(bool), +// } +// case "maven": +// exposures.ScannersCategory = map[string]bool{ +// "secrets_scan": category["secrets"].(bool), +// } +// case "npm", "pypi": +// exposures.ScannersCategory = map[string]bool{ +// "secrets_scan": category["secrets"].(bool), +// "applications_scan": category["applications"].(bool), +// } +// case "terraformbackend": +// exposures.ScannersCategory = map[string]bool{ +// "iac_scan": category["iac"].(bool), +// } +// } +// +// return &exposures +// } +// +// var unpackRepoConfig = func(_ context.Context, config *schema.Set, xrayVersion, packageType string, jasEnabled bool) *RepoConfiguration { +// repoConfig := new(RepoConfiguration) +// +// if config != nil { +// data := config.List()[0].(map[string]interface{}) +// repoConfig.RetentionInDays = data["retention_in_days"].(int) +// +// if jasEnabled { +// if slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { +// vulnContextualAnalysis := data["vuln_contextual_analysis"].(bool) +// repoConfig.VulnContextualAnalysis = &vulnContextualAnalysis +// } +// repoConfig.Exposures = unpackExposures(data["exposures"].(*schema.Set), xrayVersion, packageType) +// } +// } +// +// return repoConfig +// } +// +// var unpackRepositoryConfig = func(ctx context.Context, s *schema.ResourceData, xrayVersion, packageType string) RepositoryConfiguration { +// d := &sdk.ResourceData{ResourceData: s} +// +// repositoryConfig := RepositoryConfiguration{ +// RepoName: d.GetString("repo_name", false), +// } +// +// jasEnabled := d.GetBool("jas_enabled", false) +// +// if v, ok := s.GetOk("config"); ok { +// repositoryConfig.RepoConfig = unpackRepoConfig(ctx, v.(*schema.Set), xrayVersion, packageType, jasEnabled) +// } +// +// if v, ok := s.GetOk("paths_config"); ok { +// repositoryConfig.RepoPathsConfig = unpackRepoPathConfig(v.(*schema.Set)) +// } +// return repositoryConfig +// } +// +// var packExposures = func(exposures *Exposures, packageType string) []interface{} { +// scannersCategory := map[string]bool{ +// "services": false, +// "secrets": false, +// "iac": false, +// "applications": false, +// } +// +// switch packageType { +// case "docker": +// scannersCategory["services"] = exposures.ScannersCategory["services_scan"] +// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] +// scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] +// case "maven": +// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] +// case "npm", "pypi": +// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] +// scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] +// case "terraformbackend": +// scannersCategory["iac"] = exposures.ScannersCategory["iac_scan"] +// } +// +// return []interface{}{ +// map[string][]map[string]bool{ +// "scanners_category": {scannersCategory}, +// }, +// } +// } +// +// var packGeneralRepoConfig = func(repoConfig *RepoConfiguration, xrayVersion, packageType string, jasEnabled bool) []interface{} { +// if repoConfig == nil { +// return []interface{}{} +// } +// +// m := map[string]interface{}{ +// "retention_in_days": repoConfig.RetentionInDays, +// } +// +// if jasEnabled { +// if repoConfig.VulnContextualAnalysis != nil && slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { +// m["vuln_contextual_analysis"] = *repoConfig.VulnContextualAnalysis +// } +// +// if repoConfig.Exposures != nil && slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { +// m["exposures"] = packExposures(repoConfig.Exposures, packageType) +// } +// } +// +// return []interface{}{m} +// } +// +// var packAllOtherArtifacts = func(otherArtifacts AllOtherArtifacts) []interface{} { +// m := map[string]interface{}{ +// "index_new_artifacts": otherArtifacts.IndexNewArtifacts, +// "retention_in_days": otherArtifacts.RetentionInDays, +// } +// +// return []interface{}{m} +// } +// +// var packPatterns = func(patterns []Pattern) []interface{} { +// var ps []interface{} +// +// for _, pattern := range patterns { +// p := map[string]interface{}{ +// "include": pattern.Include, +// "exclude": pattern.Exclude, +// "index_new_artifacts": pattern.IndexNewArtifacts, +// "retention_in_days": pattern.RetentionInDays, +// } +// +// ps = append(ps, p) +// } +// +// return ps +// } +// +// var packRepoPathsConfigList = func(repoPathsConfig *PathsConfiguration) []interface{} { +// if repoPathsConfig == nil { +// return []interface{}{} +// } +// +// m := map[string]interface{}{ +// "pattern": packPatterns(repoPathsConfig.Patterns), +// "all_other_artifacts": packAllOtherArtifacts(repoPathsConfig.OtherArtifacts), +// } +// +// return []interface{}{m} +// } +// +// var packRepositoryConfig = func(repositoryConfig RepositoryConfiguration, d *schema.ResourceData, xrayVersion, packageType string) diag.Diagnostics { +// if err := d.Set("repo_name", repositoryConfig.RepoName); err != nil { +// return diag.FromErr(err) +// } +// +// jasEnabled := false +// if v, ok := d.GetOk("jas_enabled"); ok { +// jasEnabled = v.(bool) +// } +// +// if err := d.Set("config", packGeneralRepoConfig(repositoryConfig.RepoConfig, xrayVersion, packageType, jasEnabled)); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("paths_config", packRepoPathsConfigList(repositoryConfig.RepoPathsConfig)); err != nil { +// return diag.FromErr(err) +// } +// +// return nil +// } +// +// var getPackageType = func(client *resty.Client, repoKey string) (repoType string, err error) { +// type Repository struct { +// PackageType string `json:"packageType"` +// } +// +// repo := Repository{} +// +// _, err = client.R(). +// SetResult(&repo). +// SetPathParam("repoKey", repoKey). +// Get("artifactory/api/repositories/{repoKey}") +// if err != nil { +// fmt.Printf("err: %v\n", err) +// return "", err +// } +// +// return repo.PackageType, nil +// } +// +// var resourceXrayRepositoryConfigRead = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// repositoryConfig := RepositoryConfiguration{} +// repoName := d.Id() +// +// metadata := m.(util.ProviderMetadata) +// +// resp, err := metadata.Client.R(). +// SetResult(&repositoryConfig). +// SetPathParam("repo_name", repoName). +// Get("xray/api/v1/repos_config/{repo_name}") +// +// if err != nil { +// return diag.FromErr(err) +// } +// if resp.IsError() { +// d.SetId("") +// return diag.Errorf("repo (%s) is either not indexed or does not exist", repoName) +// } +// +// packageType, err := getPackageType(metadata.Client, repoName) +// if err != nil { +// return diag.FromErr(err) +// } +// +// return packRepositoryConfig(repositoryConfig, d, metadata.XrayVersion, packageType) +// } +// +// var resourceXrayRepositoryConfigCreate = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// metadata := m.(util.ProviderMetadata) +// packageType, err := getPackageType(metadata.Client, d.Get("repo_name").(string)) +// if err != nil { +// return diag.FromErr(err) +// } +// +// repositoryConfig := unpackRepositoryConfig(ctx, d, metadata.XrayVersion, packageType) +// +// resp, err := metadata.Client.R().SetBody(&repositoryConfig).Put("xray/api/v1/repos_config") +// if err != nil { +// return diag.FromErr(err) +// } +// if resp.IsError() { +// return diag.Errorf("%s", resp.String()) +// } +// +// d.SetId(repositoryConfig.RepoName) +// return resourceXrayRepositoryConfigRead(ctx, d, m) +// } +// +// // No delete functionality provided by API. +// // Delete function will return a warning and remove the Id from the state. +// // The option with restoring a default configuration is not viable, because the default can be changed. +// var resourceXrayRepositoryConfigDelete = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// tflog.Warn(ctx, fmt.Sprintf("There is no delete dunctionality in the API, so the configuration is not "+ +// "removed from the Artifactory, but (%s) is removed from the Terraform state", d.Id())) +// d.SetId("") +// +// return diag.Diagnostics{{ +// Severity: diag.Warning, +// Summary: "No delete functionality provided by API", +// Detail: "Delete function will return a warning and remove the Id from the Terraform state. The actual repository configuration will remain unchanged.", +// }} +// } +// +// var jasEnabledResourceDiff = func(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error { +// jasEnabled := false +// if v, ok := diff.GetOk("jas_enabled"); ok { +// jasEnabled = v.(bool) +// } +// +// if !jasEnabled { +// configSet := diff.Get("config").(*schema.Set).List() +// if len(configSet) == 0 { +// return nil +// } +// +// config := configSet[0].(map[string]interface{}) +// +// if v, ok := config["vuln_contextual_analysis"]; ok && v.(bool) { +// return fmt.Errorf("config.vuln_contextual_analysis can not be set when jas_enabled is set to 'true'") +// } +// +// tflog.Debug(ctx, "jasEnabledResourceDiff", map[string]any{ +// "config": config, +// "config['exposures']": config["exposures"], +// }) +// if v, ok := config["exposures"]; ok && v.(*schema.Set).Len() > 0 { +// return fmt.Errorf("config.exposures can not be set when jas_enabled is set to 'true'") +// } +// } +// +// return nil +// } +// +// return &schema.Resource{ +// CreateContext: resourceXrayRepositoryConfigCreate, +// ReadContext: resourceXrayRepositoryConfigRead, +// UpdateContext: resourceXrayRepositoryConfigCreate, +// DeleteContext: resourceXrayRepositoryConfigDelete, +// +// Importer: &schema.ResourceImporter{ +// StateContext: func(_ context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { +// parts := strings.SplitN(d.Id(), ":", 2) +// +// if len(parts) == 2 && parts[0] != "" && parts[1] != "" { +// d.SetId(parts[0]) +// jasEnabled, err := strconv.ParseBool(parts[1]) +// if err != nil { +// return nil, err +// } +// d.Set("jas_enabled", jasEnabled) +// } +// +// return []*schema.ResourceData{d}, nil +// }, +// }, +// +// CustomizeDiff: jasEnabledResourceDiff, +// +// Schema: repositoryConfigSchema, +// Description: "Provides an Xray repository config resource. See [Xray Indexing Resources](https://www.jfrog.com/confluence/display/JFROG/Indexing+Xray+Resources#IndexingXrayResources-SetaRetentionPeriod) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-UpdateRepositoriesConfigurations) for more details.", +// } +// } diff --git a/pkg/xray/resource/resource_xray_repository_config_test.go b/pkg/xray/resource/resource_xray_repository_config_test.go index e5c49d04..e5fd5e85 100644 --- a/pkg/xray/resource/resource_xray_repository_config_test.go +++ b/pkg/xray/resource/resource_xray_repository_config_test.go @@ -14,6 +14,67 @@ import ( "github.com/jfrog/terraform-provider-xray/pkg/acctest" ) +func TestAccRepositoryConfig_UpgradeFromSDKv2(t *testing.T) { + _, fqrn, resourceName := testutil.MkNames("xray-repo-config-", "xray_repository_config") + _, _, repoName := testutil.MkNames("generic-local", "artifactory_local_generic_repository") + + var testData = map[string]string{ + "resource_name": resourceName, + "repo_name": repoName, + "jas_enabled": "false", + "pattern0_include": "core/**", + "pattern0_exclude": "core/external/**", + "pattern0_index_new_artifacts": "true", + "pattern0_retention_in_days": "45", + "pattern1_include": "core/**", + "pattern1_exclude": "core/external/**", + "pattern1_index_new_artifacts": "true", + "pattern1_retention_in_days": "45", + "other_index_new_artifacts": "true", + "other_retention_in_days": "60", + "package_type": "generic", + } + + config := util.ExecuteTemplate(fqrn, TestDataRepoPathsConfigTemplate, testData) + + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + }, + "xray": { + Source: "jfrog/xray", + VersionConstraint: "2.10.0", + }, + }, + Config: config, + Check: resource.ComposeTestCheckFunc(verifyRepositoryConfig(fqrn, testData)), + }, + { + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + }, + }, + ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, + 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(), + }, + }, + }, + }, + }) +} + func TestAccRepositoryConfig_RepoNoConfig(t *testing.T) { _, fqrn, resourceName := testutil.MkNames("xray-repo-config-", "xray_repository_config") _, _, repoName := testutil.MkNames("local-generic", "artifactory_local_generic_repository") @@ -36,7 +97,7 @@ func TestAccRepositoryConfig_RepoNoConfig(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - ExpectError: regexp.MustCompile("Missing required argument"), + ExpectError: regexp.MustCompile(".*Invalid Attribute Combination.*"), }, }, }) @@ -131,7 +192,7 @@ func TestAccRepositoryConfig_JasDisabled_vulnContextualAnalysis_set(t *testing.T Steps: []resource.TestStep{ { Config: config, - ExpectError: regexp.MustCompile(`.*config\.vuln_contextual_analysis can not be set when jas_enabled is set to 'true'.*`), + ExpectError: regexp.MustCompile(`.*config\.vuln_contextual_analysis can not be set when jas_enabled is set to\n.*'true'.*`), }, }, }) @@ -255,10 +316,11 @@ func testAccRepositoryConfigRepoConfigCreate_VulnContextualAnalysis(packageType, ), }, { - ResourceName: fqrn, - ImportState: true, - ImportStateId: fmt.Sprintf("%s:true", testData["repo_name"]), - ImportStateVerify: true, + ResourceName: fqrn, + ImportState: true, + ImportStateId: fmt.Sprintf("%s:true", testData["repo_name"]), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "repo_name", }, }, }) @@ -416,10 +478,11 @@ func testAccRepositoryConfigRepoConfigCreate(packageType, template, validVersion Check: checkFunc(fqrn, testData), }, { - ResourceName: fqrn, - ImportState: true, - ImportStateId: fmt.Sprintf("%s:true", testData["repo_name"]), - ImportStateVerify: true, + ResourceName: fqrn, + ImportState: true, + ImportStateId: fmt.Sprintf("%s:true", testData["repo_name"]), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "repo_name", }, }, }) @@ -459,67 +522,6 @@ func TestAccRepositoryConfig_RepoConfigCreate_InvalidExposures(t *testing.T) { }) } -func TestAccRepositoryConfig_UpgradeFromSDKv2(t *testing.T) { - _, fqrn, resourceName := testutil.MkNames("xray-repo-config-", "xray_repository_config") - _, _, repoName := testutil.MkNames("generic-local", "artifactory_local_generic_repository") - - var testData = map[string]string{ - "resource_name": resourceName, - "repo_name": repoName, - "jas_enabled": "false", - "pattern0_include": "core/**", - "pattern0_exclude": "core/external/**", - "pattern0_index_new_artifacts": "true", - "pattern0_retention_in_days": "45", - "pattern1_include": "core/**", - "pattern1_exclude": "core/external/**", - "pattern1_index_new_artifacts": "true", - "pattern1_retention_in_days": "45", - "other_index_new_artifacts": "true", - "other_retention_in_days": "60", - "package_type": "generic", - } - - config := util.ExecuteTemplate(fqrn, TestDataRepoPathsConfigTemplate, testData) - - resource.Test(t, resource.TestCase{ - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "artifactory": { - Source: "jfrog/artifactory", - }, - "xray": { - Source: "jfrog/xray", - VersionConstraint: "2.4.0", - }, - }, - Config: config, - Check: resource.ComposeTestCheckFunc(verifyRepositoryConfig(fqrn, testData)), - }, - { - ExternalProviders: map[string]resource.ExternalProvider{ - "artifactory": { - Source: "jfrog/artifactory", - }, - }, - ProtoV6ProviderFactories: acctest.ProtoV6MuxProviderFactories, - 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(), - }, - }, - }, - }, - }) -} - func TestAccRepositoryConfig_RepoPathsUpdate(t *testing.T) { _, fqrn, resourceName := testutil.MkNames("xray-repo-config-", "xray_repository_config") _, _, repoName := testutil.MkNames("generic-local", "artifactory_local_generic_repository") @@ -529,13 +531,13 @@ func TestAccRepositoryConfig_RepoPathsUpdate(t *testing.T) { "repo_name": repoName, "jas_enabled": "false", "pattern0_include": "core/**", - "pattern0_exclude": "core/external/**", + "pattern0_exclude": "core/internal/**", "pattern0_index_new_artifacts": "true", "pattern0_retention_in_days": "45", "pattern1_include": "core/**", "pattern1_exclude": "core/external/**", "pattern1_index_new_artifacts": "true", - "pattern1_retention_in_days": "45", + "pattern1_retention_in_days": "55", "other_index_new_artifacts": "true", "other_retention_in_days": "60", "package_type": "generic", @@ -545,7 +547,7 @@ func TestAccRepositoryConfig_RepoPathsUpdate(t *testing.T) { "repo_name": repoName, "jas_enabled": "false", "pattern0_include": "core1/**", - "pattern0_exclude": "core1/external/**", + "pattern0_exclude": "core1/internal/**", "pattern0_index_new_artifacts": "false", "pattern0_retention_in_days": "50", "pattern1_include": "core1/**", @@ -582,14 +584,21 @@ func verifyRepositoryConfig(fqrn string, testData map[string]string) resource.Te return resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(fqrn, "repo_name", testData["repo_name"]), resource.TestCheckResourceAttr(fqrn, "jas_enabled", testData["jas_enabled"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.0.include", testData["pattern0_include"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.0.exclude", testData["pattern0_exclude"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.0.index_new_artifacts", testData["pattern0_index_new_artifacts"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.0.retention_in_days", testData["pattern0_retention_in_days"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.1.include", testData["pattern1_include"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.1.exclude", testData["pattern1_exclude"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.1.index_new_artifacts", testData["pattern1_index_new_artifacts"]), - resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.1.retention_in_days", testData["pattern1_retention_in_days"]), + resource.TestCheckResourceAttr(fqrn, "paths_config.#", "1"), + resource.TestCheckResourceAttr(fqrn, "paths_config.0.pattern.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(fqrn, "paths_config.0.pattern.*", map[string]string{ + "include": testData["pattern0_include"], + "exclude": testData["pattern0_exclude"], + "index_new_artifacts": testData["pattern0_index_new_artifacts"], + "retention_in_days": testData["pattern0_retention_in_days"], + }), + resource.TestCheckTypeSetElemNestedAttrs(fqrn, "paths_config.0.pattern.*", map[string]string{ + "include": testData["pattern1_include"], + "exclude": testData["pattern1_exclude"], + "index_new_artifacts": testData["pattern1_index_new_artifacts"], + "retention_in_days": testData["pattern1_retention_in_days"], + }), + resource.TestCheckResourceAttr(fqrn, "paths_config.0.all_other_artifacts.#", "1"), resource.TestCheckResourceAttr(fqrn, "paths_config.0.all_other_artifacts.0.index_new_artifacts", testData["other_index_new_artifacts"]), resource.TestCheckResourceAttr(fqrn, "paths_config.0.all_other_artifacts.0.retention_in_days", testData["other_retention_in_days"]), ) From 63495fe7ed81558cf84ca938de0e2293456a48ec Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 16 Aug 2024 12:19:55 -0700 Subject: [PATCH 02/11] Update CHANGELOG --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d1132c..addde551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 2.10.0 (August 8 30, 2024). Tested on Artifactory 7.90.6 and Xray 3.101.5 with Terraform 1.9.4 and OpenTofu 1.8.1 +## 2.11.0 (August 19, 2024) + +IMPROVEMENTS: + +* resource/xray_repository_config: Migrate from SDKv2 to Plugin Framework. PR: [#234](https://github.com/jfrog/terraform-provider-xray/pull/234) + +## 2.10.0 (August 8, 2024). Tested on Artifactory 7.90.6 and Xray 3.101.5 with Terraform 1.9.4 and OpenTofu 1.8.1 IMPROVEMENTS: @@ -22,6 +28,8 @@ BUG FIXES: ## 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 +IMPROVEMENTS: + * 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) * resource/xray_watch: Migrate from SDKv2 to Plugin Framework. PR: [#210](https://github.com/jfrog/terraform-provider-xray/pull/210) From 13dbdd06b6d4488073652c9cc422d501e9363624 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 16 Aug 2024 15:40:34 -0700 Subject: [PATCH 03/11] Fix acceptance test Fix incorrect packing of policy exposures Fix makefile not setting correct env vars for Tofu --- GNUmakefile | 2 ++ pkg/xray/resource/policies.go | 6 +++--- pkg/xray/resource/resource_xray_security_policy_test.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 6dd7c91d..96879a10 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -19,6 +19,8 @@ REGISTRY_HOST=registry.terraform.io ifeq ($(TERRAFORM_CLI), tofu) REGISTRY_HOST=registry.opentofu.org +TF_ACC_TERRAFORM_PATH="$(which tofu)" +TF_ACC_PROVIDER_HOST="registry.opentofu.org" endif BUILD_PATH=terraform.d/plugins/${REGISTRY_HOST}/jfrog/${PRODUCT}/${NEXT_VERSION}/${TARGET_ARCH} diff --git a/pkg/xray/resource/policies.go b/pkg/xray/resource/policies.go index 8d2f1295..71e7d618 100644 --- a/pkg/xray/resource/policies.go +++ b/pkg/xray/resource/policies.go @@ -694,9 +694,9 @@ func packExposures(exposures *PolicyExposures) []interface{} { m := map[string]interface{}{ "min_severity": *exposures.MinSeverity, "secrets": *exposures.Secrets, - "applications": *exposures.Secrets, - "services": *exposures.Secrets, - "iac": *exposures.Secrets, + "applications": *exposures.Applications, + "services": *exposures.Services, + "iac": *exposures.Iac, } return []interface{}{m} } diff --git a/pkg/xray/resource/resource_xray_security_policy_test.go b/pkg/xray/resource/resource_xray_security_policy_test.go index 21efbc5f..d8ea00de 100644 --- a/pkg/xray/resource/resource_xray_security_policy_test.go +++ b/pkg/xray/resource/resource_xray_security_policy_test.go @@ -742,7 +742,7 @@ func TestAccSecurityPolicy_exposures(t *testing.T) { testData["resource_name"] = resourceName testData["policy_name"] = fmt.Sprintf("terraform-security-policy-6-%d", testutil.RandomInt()) testData["rule_name"] = fmt.Sprintf("test-security-rule-6-%d", testutil.RandomInt()) - testData["exposures_min_severity"] = "High" + testData["exposures_min_severity"] = "high" testData["exposures_secrets"] = "true" testData["exposures_applications"] = "true" testData["exposures_services"] = "true" From a144d4d59fbdba82abfbe7fdcd0fe5a588b89cce Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 16 Aug 2024 15:54:08 -0700 Subject: [PATCH 04/11] Code tidy up --- go.mod | 2 +- .../resource_xray_repository_config.go | 571 +----------------- 2 files changed, 22 insertions(+), 551 deletions(-) diff --git a/go.mod b/go.mod index df8ac362..8bf496ae 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 github.com/hashicorp/terraform-plugin-go v0.23.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 @@ -55,6 +54,7 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect diff --git a/pkg/xray/resource/resource_xray_repository_config.go b/pkg/xray/resource/resource_xray_repository_config.go index 126332ae..dd1316ec 100644 --- a/pkg/xray/resource/resource_xray_repository_config.go +++ b/pkg/xray/resource/resource_xray_repository_config.go @@ -46,7 +46,7 @@ type RepoConfigResourceModel struct { PathsConfig types.Set `tfsdk:"paths_config"` } -func (m RepoConfigResourceModel) toAPIModel(ctx context.Context, xrayVersion, packageType string, apiModel *RepositoryConfigurationAPIModel) (ds diag.Diagnostics) { +func (m RepoConfigResourceModel) toAPIModel(_ context.Context, xrayVersion, packageType string, apiModel *RepositoryConfigurationAPIModel) (ds diag.Diagnostics) { var repoConfig *RepoConfigurationAPIModel if !m.Config.IsNull() && len(m.Config.Elements()) > 0 { c := m.Config.Elements()[0].(types.Object) @@ -214,6 +214,26 @@ var pathsConfigSetResourceModelElementTypes types.ObjectType = types.ObjectType{ AttrTypes: pathsConfigResourceModelAttributeTypes, } +var exposuresPackageTypes = func(xrayVersion string) []string { + packageTypes := []string{"docker", "terraformbackend"} + + if ok, err := util.CheckVersion(xrayVersion, "3.78.9"); err == nil && ok { + packageTypes = append(packageTypes, "maven", "npm", "pypi") + } + + return packageTypes +} + +var vulnContextualAnalysisPackageTypes = func(xrayVersion string) []string { + packageTypes := []string{"docker"} + + if ok, err := util.CheckVersion(xrayVersion, "3.77.4"); err == nil && ok { + packageTypes = append(packageTypes, "maven") + } + + return packageTypes +} + func (m *RepoConfigResourceModel) fromAPIModel(_ context.Context, xrayVersion, packageType string, apiModel RepositoryConfigurationAPIModel) diag.Diagnostics { diags := diag.Diagnostics{} @@ -819,552 +839,3 @@ func (r *RepoConfigResource) ImportState(ctx context.Context, req resource.Impor resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("jas_enabled"), jasEnabled)...) } } - -var exposuresPackageTypes = func(xrayVersion string) []string { - packageTypes := []string{"docker", "terraformbackend"} - - if ok, err := util.CheckVersion(xrayVersion, "3.78.9"); err == nil && ok { - packageTypes = append(packageTypes, "maven", "npm", "pypi") - } - - return packageTypes -} - -var vulnContextualAnalysisPackageTypes = func(xrayVersion string) []string { - packageTypes := []string{"docker"} - - if ok, err := util.CheckVersion(xrayVersion, "3.77.4"); err == nil && ok { - packageTypes = append(packageTypes, "maven") - } - - return packageTypes -} - -// -// func ResourceXrayRepositoryConfig() *schema.Resource { -// var repositoryConfigSchema = map[string]*schema.Schema{ -// "repo_name": { -// Type: schema.TypeString, -// Required: true, -// Description: "Repository name.", -// ValidateDiagFunc: validator.StringIsNotEmpty, -// }, -// "jas_enabled": { -// Type: schema.TypeBool, -// Optional: true, -// Default: false, -// Description: "Specified if JFrog Advanced Security is enabled or not. Default to 'false'", -// }, -// "config": { -// Type: schema.TypeSet, -// Optional: true, -// MaxItems: 1, -// Description: "Single repository configuration. Only one of 'config' or 'paths_config' can be set.", -// AtLeastOneOf: []string{"paths_config"}, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "vuln_contextual_analysis": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Only for SaaS instances, will be available after Xray 3.59. Enables vulnerability contextual analysis. Must be set together with `exposures`. Supported for Docker, OCI, and Maven package types.", -// }, -// "retention_in_days": { -// Type: schema.TypeInt, -// Optional: true, -// Default: 90, -// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", -// ValidateDiagFunc: validator.IntAtLeast(0), -// }, -// "exposures": { -// Type: schema.TypeSet, -// Optional: true, -// MaxItems: 1, -// Description: "Enables Xray to perform scans for multiple categories that cover security issues in your configurations and the usage of open source libraries in your code. Available only to CLOUD (SaaS)/SELF HOSTED for ENTERPRISE X and ENTERPRISE+ with Advanced DevSecOps. Must be set together with `vuln_contextual_analysis`. Supported for Docker, Maven, NPM, PyPi, and Terraform Backend package type.", -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "scanners_category": { -// Type: schema.TypeSet, -// Required: true, -// MaxItems: 1, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "services": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Detect whether common OSS libraries and services are configured securely, so application can be easily hardened by default.", -// }, -// "secrets": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Detect any secret left exposed in any containers stored in Artifactory to stop any accidental leak of internal tokens or credentials.", -// }, -// "iac": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Scans IaC files stored in Artifactory for early detection of cloud and infrastructure misconfigurations to prevent attacks and data leak. Only supported by Terraform Backend package type.", -// }, -// "applications": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Detect whether common OSS libraries and services are used securely by the application.", -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// "paths_config": { -// Type: schema.TypeSet, -// Optional: true, -// MaxItems: 1, -// Description: "Enables you to set a more granular retention period. It enables you to scan future artifacts within the specific path, and set a retention period for the historical data of artifacts after they are scanned", -// AtLeastOneOf: []string{"config"}, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "pattern": { -// Type: schema.TypeList, -// Required: true, -// MinItems: 1, -// Description: "Pattern, applied to the repositories.", -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "include": { -// Type: schema.TypeString, -// Required: true, -// Description: "Include pattern.", -// ValidateDiagFunc: validator.StringIsNotEmpty, -// }, -// "exclude": { -// Type: schema.TypeString, -// Optional: true, -// Description: "Exclude pattern.", -// ValidateDiagFunc: validator.StringIsNotEmpty, -// }, -// "index_new_artifacts": { -// Type: schema.TypeBool, -// Optional: true, -// Default: true, -// Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", -// }, -// "retention_in_days": { -// Type: schema.TypeInt, -// Optional: true, -// Default: 90, -// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", -// ValidateDiagFunc: validator.IntAtLeast(0), -// }, -// }, -// }, -// }, -// "all_other_artifacts": { -// Type: schema.TypeSet, -// Required: true, -// Description: "If you select by pattern, you must define a retention period for all other artifacts in the repository in the All Other Artifacts setting.", -// MinItems: 1, -// MaxItems: 1, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "index_new_artifacts": { -// Type: schema.TypeBool, -// Optional: true, -// Default: true, -// Description: "If checked, Xray will scan newly added artifacts in the path. Note that existing artifacts will not be scanned. If the folder contains existing artifacts that have been scanned, and you do not want to index new artifacts in that folder, you can choose not to index that folder.", -// }, -// "retention_in_days": { -// Type: schema.TypeInt, -// Optional: true, -// Default: 90, -// Description: "The artifact will be retained for the number of days you set here, after the artifact is scanned. This will apply to all artifacts in the repository.", -// ValidateDiagFunc: validator.IntAtLeast(0), -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// } -// -// var unpackPattern = func(s []interface{}) []Pattern { -// var patterns []Pattern -// -// for _, raw := range s { -// data := raw.(map[string]interface{}) -// pattern := Pattern{ -// Include: data["include"].(string), -// Exclude: data["exclude"].(string), -// IndexNewArtifacts: data["index_new_artifacts"].(bool), -// RetentionInDays: data["retention_in_days"].(int), -// } -// patterns = append(patterns, pattern) -// } -// -// return patterns -// } -// -// var unpackAllOtherArtifacts = func(config *schema.Set) AllOtherArtifacts { -// allOtherArtifacts := AllOtherArtifacts{} -// -// if config != nil { -// data := config.List()[0].(map[string]interface{}) -// allOtherArtifacts.IndexNewArtifacts = data["index_new_artifacts"].(bool) -// allOtherArtifacts.RetentionInDays = data["retention_in_days"].(int) -// } -// -// return allOtherArtifacts -// } -// -// var unpackRepoPathConfig = func(config *schema.Set) *PathsConfiguration { -// repoPathsConfiguration := new(PathsConfiguration) -// configList := config.List() -// if len(configList) == 0 { -// return nil -// } -// -// m := configList[0].(map[string]interface{}) -// -// otherArtifacts := unpackAllOtherArtifacts(m["all_other_artifacts"].(*schema.Set)) -// repoPathsConfiguration.OtherArtifacts = otherArtifacts -// -// repoPathsConfiguration.Patterns = unpackPattern(m["pattern"].([]interface{})) -// -// return repoPathsConfiguration -// } -// -// var unpackExposures = func(config *schema.Set, xrayVersion, packageType string) *Exposures { -// if !slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { -// return nil -// } -// -// if len(config.List()) == 0 { -// return nil -// } -// -// e := config.List()[0].(map[string]interface{}) -// s := e["scanners_category"].(*schema.Set) -// if len(s.List()) == 0 { -// return nil -// } -// -// category := s.List()[0].(map[string]interface{}) -// -// exposures := Exposures{} -// -// switch packageType { -// case "docker": -// exposures.ScannersCategory = map[string]bool{ -// "services_scan": category["services"].(bool), -// "secrets_scan": category["secrets"].(bool), -// "applications_scan": category["applications"].(bool), -// } -// case "maven": -// exposures.ScannersCategory = map[string]bool{ -// "secrets_scan": category["secrets"].(bool), -// } -// case "npm", "pypi": -// exposures.ScannersCategory = map[string]bool{ -// "secrets_scan": category["secrets"].(bool), -// "applications_scan": category["applications"].(bool), -// } -// case "terraformbackend": -// exposures.ScannersCategory = map[string]bool{ -// "iac_scan": category["iac"].(bool), -// } -// } -// -// return &exposures -// } -// -// var unpackRepoConfig = func(_ context.Context, config *schema.Set, xrayVersion, packageType string, jasEnabled bool) *RepoConfiguration { -// repoConfig := new(RepoConfiguration) -// -// if config != nil { -// data := config.List()[0].(map[string]interface{}) -// repoConfig.RetentionInDays = data["retention_in_days"].(int) -// -// if jasEnabled { -// if slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { -// vulnContextualAnalysis := data["vuln_contextual_analysis"].(bool) -// repoConfig.VulnContextualAnalysis = &vulnContextualAnalysis -// } -// repoConfig.Exposures = unpackExposures(data["exposures"].(*schema.Set), xrayVersion, packageType) -// } -// } -// -// return repoConfig -// } -// -// var unpackRepositoryConfig = func(ctx context.Context, s *schema.ResourceData, xrayVersion, packageType string) RepositoryConfiguration { -// d := &sdk.ResourceData{ResourceData: s} -// -// repositoryConfig := RepositoryConfiguration{ -// RepoName: d.GetString("repo_name", false), -// } -// -// jasEnabled := d.GetBool("jas_enabled", false) -// -// if v, ok := s.GetOk("config"); ok { -// repositoryConfig.RepoConfig = unpackRepoConfig(ctx, v.(*schema.Set), xrayVersion, packageType, jasEnabled) -// } -// -// if v, ok := s.GetOk("paths_config"); ok { -// repositoryConfig.RepoPathsConfig = unpackRepoPathConfig(v.(*schema.Set)) -// } -// return repositoryConfig -// } -// -// var packExposures = func(exposures *Exposures, packageType string) []interface{} { -// scannersCategory := map[string]bool{ -// "services": false, -// "secrets": false, -// "iac": false, -// "applications": false, -// } -// -// switch packageType { -// case "docker": -// scannersCategory["services"] = exposures.ScannersCategory["services_scan"] -// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] -// scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] -// case "maven": -// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] -// case "npm", "pypi": -// scannersCategory["secrets"] = exposures.ScannersCategory["secrets_scan"] -// scannersCategory["applications"] = exposures.ScannersCategory["applications_scan"] -// case "terraformbackend": -// scannersCategory["iac"] = exposures.ScannersCategory["iac_scan"] -// } -// -// return []interface{}{ -// map[string][]map[string]bool{ -// "scanners_category": {scannersCategory}, -// }, -// } -// } -// -// var packGeneralRepoConfig = func(repoConfig *RepoConfiguration, xrayVersion, packageType string, jasEnabled bool) []interface{} { -// if repoConfig == nil { -// return []interface{}{} -// } -// -// m := map[string]interface{}{ -// "retention_in_days": repoConfig.RetentionInDays, -// } -// -// if jasEnabled { -// if repoConfig.VulnContextualAnalysis != nil && slices.Contains(vulnContextualAnalysisPackageTypes(xrayVersion), packageType) { -// m["vuln_contextual_analysis"] = *repoConfig.VulnContextualAnalysis -// } -// -// if repoConfig.Exposures != nil && slices.Contains(exposuresPackageTypes(xrayVersion), packageType) { -// m["exposures"] = packExposures(repoConfig.Exposures, packageType) -// } -// } -// -// return []interface{}{m} -// } -// -// var packAllOtherArtifacts = func(otherArtifacts AllOtherArtifacts) []interface{} { -// m := map[string]interface{}{ -// "index_new_artifacts": otherArtifacts.IndexNewArtifacts, -// "retention_in_days": otherArtifacts.RetentionInDays, -// } -// -// return []interface{}{m} -// } -// -// var packPatterns = func(patterns []Pattern) []interface{} { -// var ps []interface{} -// -// for _, pattern := range patterns { -// p := map[string]interface{}{ -// "include": pattern.Include, -// "exclude": pattern.Exclude, -// "index_new_artifacts": pattern.IndexNewArtifacts, -// "retention_in_days": pattern.RetentionInDays, -// } -// -// ps = append(ps, p) -// } -// -// return ps -// } -// -// var packRepoPathsConfigList = func(repoPathsConfig *PathsConfiguration) []interface{} { -// if repoPathsConfig == nil { -// return []interface{}{} -// } -// -// m := map[string]interface{}{ -// "pattern": packPatterns(repoPathsConfig.Patterns), -// "all_other_artifacts": packAllOtherArtifacts(repoPathsConfig.OtherArtifacts), -// } -// -// return []interface{}{m} -// } -// -// var packRepositoryConfig = func(repositoryConfig RepositoryConfiguration, d *schema.ResourceData, xrayVersion, packageType string) diag.Diagnostics { -// if err := d.Set("repo_name", repositoryConfig.RepoName); err != nil { -// return diag.FromErr(err) -// } -// -// jasEnabled := false -// if v, ok := d.GetOk("jas_enabled"); ok { -// jasEnabled = v.(bool) -// } -// -// if err := d.Set("config", packGeneralRepoConfig(repositoryConfig.RepoConfig, xrayVersion, packageType, jasEnabled)); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("paths_config", packRepoPathsConfigList(repositoryConfig.RepoPathsConfig)); err != nil { -// return diag.FromErr(err) -// } -// -// return nil -// } -// -// var getPackageType = func(client *resty.Client, repoKey string) (repoType string, err error) { -// type Repository struct { -// PackageType string `json:"packageType"` -// } -// -// repo := Repository{} -// -// _, err = client.R(). -// SetResult(&repo). -// SetPathParam("repoKey", repoKey). -// Get("artifactory/api/repositories/{repoKey}") -// if err != nil { -// fmt.Printf("err: %v\n", err) -// return "", err -// } -// -// return repo.PackageType, nil -// } -// -// var resourceXrayRepositoryConfigRead = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// repositoryConfig := RepositoryConfiguration{} -// repoName := d.Id() -// -// metadata := m.(util.ProviderMetadata) -// -// resp, err := metadata.Client.R(). -// SetResult(&repositoryConfig). -// SetPathParam("repo_name", repoName). -// Get("xray/api/v1/repos_config/{repo_name}") -// -// if err != nil { -// return diag.FromErr(err) -// } -// if resp.IsError() { -// d.SetId("") -// return diag.Errorf("repo (%s) is either not indexed or does not exist", repoName) -// } -// -// packageType, err := getPackageType(metadata.Client, repoName) -// if err != nil { -// return diag.FromErr(err) -// } -// -// return packRepositoryConfig(repositoryConfig, d, metadata.XrayVersion, packageType) -// } -// -// var resourceXrayRepositoryConfigCreate = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// metadata := m.(util.ProviderMetadata) -// packageType, err := getPackageType(metadata.Client, d.Get("repo_name").(string)) -// if err != nil { -// return diag.FromErr(err) -// } -// -// repositoryConfig := unpackRepositoryConfig(ctx, d, metadata.XrayVersion, packageType) -// -// resp, err := metadata.Client.R().SetBody(&repositoryConfig).Put("xray/api/v1/repos_config") -// if err != nil { -// return diag.FromErr(err) -// } -// if resp.IsError() { -// return diag.Errorf("%s", resp.String()) -// } -// -// d.SetId(repositoryConfig.RepoName) -// return resourceXrayRepositoryConfigRead(ctx, d, m) -// } -// -// // No delete functionality provided by API. -// // Delete function will return a warning and remove the Id from the state. -// // The option with restoring a default configuration is not viable, because the default can be changed. -// var resourceXrayRepositoryConfigDelete = func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// tflog.Warn(ctx, fmt.Sprintf("There is no delete dunctionality in the API, so the configuration is not "+ -// "removed from the Artifactory, but (%s) is removed from the Terraform state", d.Id())) -// d.SetId("") -// -// return diag.Diagnostics{{ -// Severity: diag.Warning, -// Summary: "No delete functionality provided by API", -// Detail: "Delete function will return a warning and remove the Id from the Terraform state. The actual repository configuration will remain unchanged.", -// }} -// } -// -// var jasEnabledResourceDiff = func(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error { -// jasEnabled := false -// if v, ok := diff.GetOk("jas_enabled"); ok { -// jasEnabled = v.(bool) -// } -// -// if !jasEnabled { -// configSet := diff.Get("config").(*schema.Set).List() -// if len(configSet) == 0 { -// return nil -// } -// -// config := configSet[0].(map[string]interface{}) -// -// if v, ok := config["vuln_contextual_analysis"]; ok && v.(bool) { -// return fmt.Errorf("config.vuln_contextual_analysis can not be set when jas_enabled is set to 'true'") -// } -// -// tflog.Debug(ctx, "jasEnabledResourceDiff", map[string]any{ -// "config": config, -// "config['exposures']": config["exposures"], -// }) -// if v, ok := config["exposures"]; ok && v.(*schema.Set).Len() > 0 { -// return fmt.Errorf("config.exposures can not be set when jas_enabled is set to 'true'") -// } -// } -// -// return nil -// } -// -// return &schema.Resource{ -// CreateContext: resourceXrayRepositoryConfigCreate, -// ReadContext: resourceXrayRepositoryConfigRead, -// UpdateContext: resourceXrayRepositoryConfigCreate, -// DeleteContext: resourceXrayRepositoryConfigDelete, -// -// Importer: &schema.ResourceImporter{ -// StateContext: func(_ context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { -// parts := strings.SplitN(d.Id(), ":", 2) -// -// if len(parts) == 2 && parts[0] != "" && parts[1] != "" { -// d.SetId(parts[0]) -// jasEnabled, err := strconv.ParseBool(parts[1]) -// if err != nil { -// return nil, err -// } -// d.Set("jas_enabled", jasEnabled) -// } -// -// return []*schema.ResourceData{d}, nil -// }, -// }, -// -// CustomizeDiff: jasEnabledResourceDiff, -// -// Schema: repositoryConfigSchema, -// Description: "Provides an Xray repository config resource. See [Xray Indexing Resources](https://www.jfrog.com/confluence/display/JFROG/Indexing+Xray+Resources#IndexingXrayResources-SetaRetentionPeriod) and [REST API](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-UpdateRepositoriesConfigurations) for more details.", -// } -// } From 86c21e5bb13d89d28869cf6c68d9bb71dc509f74 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 16 Aug 2024 15:56:40 -0700 Subject: [PATCH 05/11] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index addde551..5286c229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ IMPROVEMENTS: -* resource/xray_repository_config: Migrate from SDKv2 to Plugin Framework. PR: [#234](https://github.com/jfrog/terraform-provider-xray/pull/234) +* resource/xray_repository_config: Migrate from SDKv2 to Plugin Framework. + +BUG FIXES: + +* resource/xray_\*\_policy: Fix incorrect value being set from API in `exposures` attributes. + +PR: [#234](https://github.com/jfrog/terraform-provider-xray/pull/234) ## 2.10.0 (August 8, 2024). Tested on Artifactory 7.90.6 and Xray 3.101.5 with Terraform 1.9.4 and OpenTofu 1.8.1 From 5d8ba4e934fb16f36338dff7c5df1403716faae9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 19 Aug 2024 11:35:47 -0700 Subject: [PATCH 06/11] Add support to override XRAY_VERSION in workflow This allows acceptance tests to pass with Xray version up to 3.100.3 --- .github/workflows/acceptance-tests.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 725ddb93..532e8fd2 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -32,14 +32,17 @@ jobs: uses: azure/setup-helm@v4.2.0 - name: Get Artifactory and Xray versions id: get_versions + env: + XRAY_VERSION: ${{ vars.XRAY_VERSION }} run: | helm repo add jfrog https://charts.jfrog.io/ helm repo update - RT_HELM_CHART_VERSION=$(helm search repo | grep "artifactory " | awk '{$1=$1};1' | cut -f2 -d " ") - ARTIFACTORY_VERSION=$(helm search repo | grep "artifactory " | awk '{$1=$1};1' | cut -f3 -d " ") + RT_HELM_CHART_VERSION=$(helm search repo | grep "jfrog/artifactory " | awk '{$1=$1};1' | cut -f2 -d " ") + ARTIFACTORY_VERSION=$(helm search repo | grep "jfrog/artifactory " | awk '{$1=$1};1' | cut -f3 -d " ") echo "rt_version=$ARTIFACTORY_VERSION" >> "$GITHUB_OUTPUT" - XRAY_HELM_CHART_VERSION=$(helm search repo | grep "/xray" | awk '{$1=$1};1' | cut -f2 -d " ") - XRAY_VERSION=$(helm search repo | grep "/xray" | awk '{$1=$1};1' | cut -f3 -d " ") + XRAY_HELM_CHART_VERSION=$(helm search repo | grep "jfrog/xray" | awk '{$1=$1};1' | cut -f2 -d " ") + XRAY_VERSION=${XRAY_VERSION:=$(helm search repo | grep "jfrog/xray" | awk '{$1=$1};1' | cut -f3 -d " ")} + echo "XRAY_VERSION=$XRAY_VERSION" >> "$GITHUB_ENV" echo "xray_version=$XRAY_VERSION" >> "$GITHUB_OUTPUT" - name: Authenticate with Google Cloud uses: google-github-actions/auth@v2 @@ -125,6 +128,7 @@ jobs: id: install_xray run: | helm upgrade --install xray jfrog/xray \ + --version $XRAY_VERSION --set postgresql.persistence.size=200Gi \ --set xray.jfrogUrl=http://artifactory-artifactory-nginx \ --set xray.masterKey=$MASTER_KEY \ From 05530dce92a490e160a8e99ebbf88cf9a3998a4a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 19 Aug 2024 11:51:03 -0700 Subject: [PATCH 07/11] Use Helm chart version to override --- .github/workflows/acceptance-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 532e8fd2..b1ee1b05 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -40,9 +40,9 @@ jobs: RT_HELM_CHART_VERSION=$(helm search repo | grep "jfrog/artifactory " | awk '{$1=$1};1' | cut -f2 -d " ") ARTIFACTORY_VERSION=$(helm search repo | grep "jfrog/artifactory " | awk '{$1=$1};1' | cut -f3 -d " ") echo "rt_version=$ARTIFACTORY_VERSION" >> "$GITHUB_OUTPUT" - XRAY_HELM_CHART_VERSION=$(helm search repo | grep "jfrog/xray" | awk '{$1=$1};1' | cut -f2 -d " ") + XRAY_HELM_CHART_VERSION=${XRAY_HELM_CHART_VERSION:=$(helm search repo | grep "jfrog/xray" | awk '{$1=$1};1' | cut -f2 -d " ")} XRAY_VERSION=${XRAY_VERSION:=$(helm search repo | grep "jfrog/xray" | awk '{$1=$1};1' | cut -f3 -d " ")} - echo "XRAY_VERSION=$XRAY_VERSION" >> "$GITHUB_ENV" + echo "XRAY_HELM_CHART_VERSION=$XRAY_HELM_CHART_VERSION" >> "$GITHUB_ENV" echo "xray_version=$XRAY_VERSION" >> "$GITHUB_OUTPUT" - name: Authenticate with Google Cloud uses: google-github-actions/auth@v2 @@ -128,7 +128,7 @@ jobs: id: install_xray run: | helm upgrade --install xray jfrog/xray \ - --version $XRAY_VERSION + --version $XRAY_HELM_CHART_VERSION --set postgresql.persistence.size=200Gi \ --set xray.jfrogUrl=http://artifactory-artifactory-nginx \ --set xray.masterKey=$MASTER_KEY \ From 0bc703677d24f5e802c15bea60029c3febd72659 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 19 Aug 2024 12:42:38 -0700 Subject: [PATCH 08/11] Fix syntax error --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index b1ee1b05..bbbbbee9 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -128,7 +128,7 @@ jobs: id: install_xray run: | helm upgrade --install xray jfrog/xray \ - --version $XRAY_HELM_CHART_VERSION + --version $XRAY_HELM_CHART_VERSION \ --set postgresql.persistence.size=200Gi \ --set xray.jfrogUrl=http://artifactory-artifactory-nginx \ --set xray.masterKey=$MASTER_KEY \ From 1372f713f1dd85988ea344432b80809b1880a35c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 19 Aug 2024 13:02:43 -0700 Subject: [PATCH 09/11] Add workflow env var override --- .github/workflows/acceptance-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index bbbbbee9..3040fed3 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -33,6 +33,7 @@ jobs: - name: Get Artifactory and Xray versions id: get_versions env: + XRAY_HELM_CHART_VERSION: ${{ vars.XRAY_HELM_CHART_VERSION }} XRAY_VERSION: ${{ vars.XRAY_VERSION }} run: | helm repo add jfrog https://charts.jfrog.io/ From 6b9b696075c0fb7cfc3e5de17f8fb5865a9821ba Mon Sep 17 00:00:00 2001 From: JFrog CI Date: Mon, 19 Aug 2024 20:32:19 +0000 Subject: [PATCH 10/11] 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 5286c229..1c7b3e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.11.0 (August 19, 2024) +## 2.11.0 (August 19, 2024). Tested on Artifactory 7.90.8 and Xray 3.100.3 with Terraform 1.9.4 and OpenTofu 1.8.1 IMPROVEMENTS: From 65f7c8b608a233494f6021cd465d03f621a61f79 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 19 Aug 2024 14:19:40 -0700 Subject: [PATCH 11/11] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7b3e3c..f1ff3a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.11.0 (August 19, 2024). Tested on Artifactory 7.90.8 and Xray 3.100.3 with Terraform 1.9.4 and OpenTofu 1.8.1 +## 2.11.0 (August 19, 2024). Tested on Artifactory 7.90.8 and Xray 3.101.5 with Terraform 1.9.4 and OpenTofu 1.8.1 IMPROVEMENTS: