diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc7c362e..4e1e50a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.8.0 (October 18, 2022). Tested on Artifactory 7.46.6 and Xray 3.59.4
+
+IMPROVEMENTS:
+
+* resource/xray_watch: add functionality to apply `path_ant_filter` for `repository` and `all-repos` `watch_resource.type`.
+ PR [#82](https://github.com/jfrog/terraform-provider-xray/pull/82)
+
## 1.7.0 (October 6, 2022). Tested on Artifactory 7.41.13 and Xray 3.57.6
NEW FEATURE:
diff --git a/docs/resources/watch.md b/docs/resources/watch.md
index 50ef2abe..689f55a1 100644
--- a/docs/resources/watch.md
+++ b/docs/resources/watch.md
@@ -85,6 +85,49 @@ resource "xray_watch" "repository" {
watch_recipients = ["test@email.com", "test1@email.com"]
}
+resource "xray_watch" "repository-ant-filter" {
+ name = "repository-watch"
+ description = "Watch a single repo or a list of repositories, using ant pattern"
+ active = true
+ project_key = "testproj"
+
+ watch_resource {
+ type = "repository"
+ bin_mgr_id = "default"
+ name = "your-repository-name"
+ repo_type = "local"
+
+ path_ant_filter {
+ exclude_patterns = ["**/*.md"]
+ include_patterns = ["**/*.js"]
+ }
+ }
+
+ watch_resource {
+ type = "repository"
+ bin_mgr_id = "default"
+ name = "your-other-repository-name"
+ repo_type = "remote"
+
+ filter {
+ type = "regex"
+ value = ".*"
+ }
+ }
+
+ assigned_policy {
+ name = xray_security_policy.min_severity.name
+ type = "security"
+ }
+
+ assigned_policy {
+ name = xray_license_policy.cvss_range.name
+ type = "license"
+ }
+
+ watch_recipients = ["test@email.com", "test1@email.com"]
+}
+
resource "xray_watch" "all-builds-with-filters" {
name = "build-watch"
description = "Watch all builds with Ant patterns filter"
@@ -269,6 +312,7 @@ Optional:
- `bin_mgr_id` (String) The ID number of a binary manager resource. Default value is `default`. To check the list of available binary managers, use the API call `${JFROG_URL}/xray/api/v1/binMgr` as an admin user, use `binMgrId` value. More info [here](https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-GetBinaryManager)
- `filter` (Block Set) Filter for `regex` and `package-type` type. Works only with `all-repos` watch_resource.type. (see [below for nested schema](#nestedblock--watch_resource--filter))
- `name` (String) The name of the build, repository or project. Xray indexing must be enabled on the repository or build
+- `path_ant_filter` (Block Set) `path-ant-patterns` filter for `repository` and `all-repos` watch_resource.type (see [below for nested schema](#nestedblock--watch_resource--path_ant_filter))
- `repo_type` (String) Type of repository. Only applicable when `type` is `repository`. Options: `local` or `remote`.
@@ -286,4 +330,13 @@ Required:
Required:
- `type` (String) The type of filter, such as `regex` or `package-type`
-- `value` (String) The value of the filter, such as the text of the regex or name of the package type.
\ No newline at end of file
+- `value` (String) The value of the filter, such as the text of the regex or name of the package type.
+
+
+
+### Nested Schema for `watch_resource.path_ant_filter`
+
+Required:
+
+- `exclude_patterns` (List of String) List of Ant patterns.
+- `include_patterns` (List of String) List of Ant patterns.
\ No newline at end of file
diff --git a/examples/resources/xray_watch/resource.tf b/examples/resources/xray_watch/resource.tf
index 92866810..afee0ddc 100644
--- a/examples/resources/xray_watch/resource.tf
+++ b/examples/resources/xray_watch/resource.tf
@@ -69,6 +69,49 @@ resource "xray_watch" "repository" {
watch_recipients = ["test@email.com", "test1@email.com"]
}
+resource "xray_watch" "repository-ant-filter" {
+ name = "repository-watch"
+ description = "Watch a single repo or a list of repositories, using ant pattern"
+ active = true
+ project_key = "testproj"
+
+ watch_resource {
+ type = "repository"
+ bin_mgr_id = "default"
+ name = "your-repository-name"
+ repo_type = "local"
+
+ path_ant_filter {
+ exclude_patterns = ["**/*.md"]
+ include_patterns = ["**/*.js"]
+ }
+ }
+
+ watch_resource {
+ type = "repository"
+ bin_mgr_id = "default"
+ name = "your-other-repository-name"
+ repo_type = "remote"
+
+ filter {
+ type = "regex"
+ value = ".*"
+ }
+ }
+
+ assigned_policy {
+ name = xray_security_policy.min_severity.name
+ type = "security"
+ }
+
+ assigned_policy {
+ name = xray_license_policy.cvss_range.name
+ type = "license"
+ }
+
+ watch_recipients = ["test@email.com", "test1@email.com"]
+}
+
resource "xray_watch" "all-builds-with-filters" {
name = "build-watch"
description = "Watch all builds with Ant patterns filter"
diff --git a/pkg/xray/resource_xray_watch.go b/pkg/xray/resource_xray_watch.go
index 6f16b47b..a06b30b1 100644
--- a/pkg/xray/resource_xray_watch.go
+++ b/pkg/xray/resource_xray_watch.go
@@ -119,6 +119,34 @@ func resourceXrayWatch() *schema.Resource {
},
},
},
+ "path_ant_filter": {
+ Type: schema.TypeSet,
+ Optional: true,
+ MinItems: 1,
+ Description: "`path-ant-patterns` filter for `repository` and `all-repos` watch_resource.type",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "include_patterns": {
+ Type: schema.TypeList,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Required: true,
+ MinItems: 1,
+ Description: "List of Ant patterns.",
+ },
+ "exclude_patterns": {
+ Type: schema.TypeList,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Required: true,
+ MinItems: 1,
+ Description: "List of Ant patterns.",
+ },
+ },
+ },
+ },
},
},
},
diff --git a/pkg/xray/resource_xray_watch_test.go b/pkg/xray/resource_xray_watch_test.go
index 4af886ae..1f1ba3bb 100644
--- a/pkg/xray/resource_xray_watch_test.go
+++ b/pkg/xray/resource_xray_watch_test.go
@@ -2,14 +2,14 @@ package xray
import (
"fmt"
- "github.com/jfrog/terraform-provider-shared/test"
- "github.com/jfrog/terraform-provider-shared/util"
"regexp"
"testing"
"github.com/go-resty/resty/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/jfrog/terraform-provider-shared/client"
+ "github.com/jfrog/terraform-provider-shared/test"
+ "github.com/jfrog/terraform-provider-shared/util"
)
var testDataWatch = map[string]string{
@@ -39,7 +39,7 @@ func TestAccWatch_allReposSinglePolicy(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
- testCheckPolicyDeleted("xray_security_policy.security", t, request)
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
resp, err := testCheckWatch(id, request)
return resp, err
}),
@@ -53,6 +53,33 @@ func TestAccWatch_allReposSinglePolicy(t *testing.T) {
})
}
+func TestAccWatch_allReposPathAntFilter(t *testing.T) {
+ _, fqrn, resourceName := test.MkNames("watch-", "xray_watch")
+ testData := util.MergeMaps(testDataWatch)
+
+ testData["resource_name"] = resourceName
+ testData["watch_name"] = fmt.Sprintf("xray-watch-%d", test.RandomInt())
+ testData["policy_name_0"] = fmt.Sprintf("xray-policy-%d", test.RandomInt())
+ testData["exclude_patterns0"] = "**/*.md"
+ testData["include_patterns0"] = "**/*.js"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
+ resp, err := testCheckWatch(id, request)
+ return resp, err
+ }),
+ ProviderFactories: testAccProviders(),
+ Steps: []resource.TestStep{
+ {
+ Config: util.ExecuteTemplate(fqrn, allReposPathAntFilterWatchTemplate, testData),
+ Check: verifyXrayWatch(fqrn, testData),
+ },
+ },
+ })
+}
+
func TestAccWatch_allReposWithProjectKey(t *testing.T) {
_, fqrn, resourceName := test.MkNames("watch-", "xray_watch")
projectKey := fmt.Sprintf("testproj%d", test.RandSelect(1, 2, 3, 4, 5))
@@ -156,8 +183,8 @@ func TestAccWatch_allReposMultiplePolicies(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
- testCheckPolicyDeleted("xray_security_policy.security", t, request)
- testCheckPolicyDeleted("xray_license_policy.license", t, request)
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
+ testCheckPolicyDeleted(testData["policy_name_1"], t, request)
resp, err := testCheckWatch(id, request)
return resp, err
}),
@@ -206,7 +233,7 @@ func makeSingleRepositoryTestCase(repoType string, t *testing.T) (*testing.T, re
},
CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
testAccDeleteRepo(t, testData["repo0"])
- testCheckPolicyDeleted("xray_security_policy.security", t, request)
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
resp, err := testCheckWatch(id, request)
return resp, err
}),
@@ -343,7 +370,7 @@ func TestAccWatch_repositoryMissingRepoType(t *testing.T) {
},
CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
testAccDeleteRepo(t, testData["repo0"])
- testCheckPolicyDeleted("xray_security_policy.security", t, request)
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
resp, err := testCheckWatch(id, request)
return resp, err
}),
@@ -379,7 +406,7 @@ func TestAccWatch_multipleRepositories(t *testing.T) {
CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
testAccDeleteRepo(t, testData["repo0"])
testAccDeleteRepo(t, testData["repo1"])
- testCheckPolicyDeleted("xray_security_policy.security", t, request)
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
resp, err := testCheckWatch(id, request)
return resp, err
}),
@@ -393,6 +420,93 @@ func TestAccWatch_multipleRepositories(t *testing.T) {
})
}
+func TestAccWatch_multipleRepositoriesPathAntPatterns(t *testing.T) {
+ _, fqrn, resourceName := test.MkNames("watch-", "xray_watch")
+ testData := util.MergeMaps(testDataWatch)
+
+ testData["resource_name"] = resourceName
+ testData["watch_name"] = fmt.Sprintf("xray-watch-%d", test.RandomInt())
+ testData["policy_name_0"] = fmt.Sprintf("xray-policy-%d", test.RandomInt())
+ testData["watch_type"] = "repository"
+ testData["repo_type"] = "local"
+ testData["repo0"] = fmt.Sprintf("libs-release-local-0-%d", test.RandomInt())
+ testData["repo1"] = fmt.Sprintf("libs-release-local-1-%d", test.RandomInt())
+ testData["exclude_patterns0"] = "**/*.md"
+ testData["include_patterns0"] = "**/*.js"
+ testData["exclude_patterns1"] = "**/*.txt"
+ testData["include_patterns1"] = "**/*.jar"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ testAccCreateRepos(t, testData["repo0"], "local", "")
+ testAccCreateRepos(t, testData["repo1"], "local", "")
+ },
+ CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
+ testAccDeleteRepo(t, testData["repo0"])
+ testAccDeleteRepo(t, testData["repo1"])
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
+ resp, err := testCheckWatch(id, request)
+ return resp, err
+ }),
+ ProviderFactories: testAccProviders(),
+ Steps: []resource.TestStep{
+ {
+ Config: util.ExecuteTemplate(fqrn, pathAntPatterns, testData),
+ Check: resource.ComposeTestCheckFunc(
+ verifyXrayWatch(fqrn, testData),
+ resource.TestCheckResourceAttr(fqrn, "watch_resource.0.type", testData["watch_type"]),
+ resource.TestCheckResourceAttr(fqrn, "assigned_policy.0.name", testData["policy_name_0"]),
+ resource.TestCheckResourceAttr(fqrn, "assigned_policy.0.type", "security"),
+ resource.TestCheckResourceAttr(fqrn, "watch_resource.0.path_ant_filter.0.exclude_patterns.0", testData["exclude_patterns0"]),
+ resource.TestCheckResourceAttr(fqrn, "watch_resource.0.path_ant_filter.0.include_patterns.0", testData["include_patterns0"]),
+ resource.TestCheckResourceAttr(fqrn, "watch_resource.1.path_ant_filter.0.exclude_patterns.0", testData["exclude_patterns1"]),
+ resource.TestCheckResourceAttr(fqrn, "watch_resource.1.path_ant_filter.0.include_patterns.0", testData["include_patterns1"]),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccWatch_PathAntPatternsError(t *testing.T) {
+ _, fqrn, resourceName := test.MkNames("watch-", "xray_watch")
+ testData := util.MergeMaps(testDataWatch)
+
+ testData["resource_name"] = resourceName
+ testData["watch_name"] = fmt.Sprintf("xray-watch-%d", test.RandomInt())
+ testData["policy_name_0"] = fmt.Sprintf("xray-policy-%d", test.RandomInt())
+ testData["watch_type"] = "build"
+ testData["repo_type"] = "local"
+ testData["repo0"] = fmt.Sprintf("libs-release-local-0-%d", test.RandomInt())
+ testData["repo1"] = fmt.Sprintf("libs-release-local-1-%d", test.RandomInt())
+ testData["exclude_patterns0"] = "**/*.md"
+ testData["include_patterns0"] = "**/*.js"
+ testData["exclude_patterns1"] = "**/*.md"
+ testData["include_patterns1"] = "**/*.js"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ testAccCreateRepos(t, testData["repo0"], "local", "")
+ testAccCreateRepos(t, testData["repo1"], "local", "")
+ },
+ CheckDestroy: verifyDeleted(fqrn, func(id string, request *resty.Request) (*resty.Response, error) {
+ testAccDeleteRepo(t, testData["repo0"])
+ testAccDeleteRepo(t, testData["repo1"])
+ testCheckPolicyDeleted(testData["policy_name_0"], t, request)
+ resp, err := testCheckWatch(id, request)
+ return resp, err
+ }),
+ ProviderFactories: testAccProviders(),
+ Steps: []resource.TestStep{
+ {
+ Config: util.ExecuteTemplate(fqrn, pathAntPatterns, testData),
+ ExpectError: regexp.MustCompile("attribute 'path_ant_filter' is set when 'watch_resource.type' is not set to 'repository'"),
+ },
+ },
+ })
+}
+
func TestAccWatch_build(t *testing.T) {
_, fqrn, resourceName := test.MkNames("watch-", "xray_watch")
testData := util.MergeMaps(testDataWatch)
@@ -829,6 +943,53 @@ resource "xray_watch" "{{ .resource_name }}" {
watch_recipients = ["{{ .watch_recipient_0 }}", "{{ .watch_recipient_1 }}"]
}`
+const allReposPathAntFilterWatchTemplate = `resource "xray_security_policy" "security" {
+ name = "{{ .policy_name_0 }}"
+ description = "Security policy description"
+ type = "security"
+ rule {
+ name = "rule-name-severity"
+ priority = 1
+ criteria {
+ min_severity = "High"
+ }
+ actions {
+ webhooks = []
+ mails = ["test@email.com"]
+ block_download {
+ unscanned = true
+ active = true
+ }
+ block_release_bundle_distribution = true
+ fail_build = true
+ notify_watch_recipients = true
+ notify_deployer = true
+ create_ticket_enabled = false
+ build_failure_grace_period_in_days = 5
+ }
+ }
+}
+
+resource "xray_watch" "{{ .resource_name }}" {
+ name = "{{ .watch_name }}"
+ description = "{{ .description }}"
+ active = {{ .active }}
+
+ watch_resource {
+ type = "{{ .watch_type }}"
+ path_ant_filter {
+ exclude_patterns = ["{{ .exclude_patterns0 }}"]
+ include_patterns = ["{{ .include_patterns0 }}"]
+ }
+ }
+ assigned_policy {
+ name = xray_security_policy.security.name
+ type = "security"
+ }
+
+ watch_recipients = ["{{ .watch_recipient_0 }}", "{{ .watch_recipient_1 }}"]
+}`
+
const allReposMultiplePoliciesWatchTemplate = `resource "xray_security_policy" "security" {
name = "{{ .policy_name_0 }}"
description = "Security policy description"
@@ -1070,6 +1231,65 @@ resource "xray_watch" "{{ .resource_name }}" {
watch_recipients = ["{{ .watch_recipient_0 }}", "{{ .watch_recipient_1 }}"]
}`
+const pathAntPatterns = `resource "xray_security_policy" "security" {
+ name = "{{ .policy_name_0 }}"
+ description = "Security policy description"
+ type = "security"
+ rule {
+ name = "rule-name-severity"
+ priority = 1
+ criteria {
+ min_severity = "High"
+ }
+ actions {
+ webhooks = []
+ mails = ["test@email.com"]
+ block_download {
+ unscanned = true
+ active = true
+ }
+ block_release_bundle_distribution = true
+ fail_build = true
+ notify_watch_recipients = true
+ notify_deployer = true
+ create_ticket_enabled = false
+ build_failure_grace_period_in_days = 5
+ }
+ }
+}
+
+resource "xray_watch" "{{ .resource_name }}" {
+ name = "{{ .watch_name }}"
+ description = "{{ .description }}"
+ active = {{ .active }}
+
+ watch_resource {
+ type = "{{ .watch_type }}"
+ bin_mgr_id = "default"
+ name = "{{ .repo0 }}"
+ repo_type = "{{ .repo_type }}"
+ path_ant_filter {
+ exclude_patterns = ["{{ .exclude_patterns0 }}"]
+ include_patterns = ["{{ .include_patterns0 }}"]
+ }
+ }
+ watch_resource {
+ type = "{{ .watch_type }}"
+ bin_mgr_id = "default"
+ name = "{{ .repo1 }}"
+ repo_type = "{{ .repo_type }}"
+ path_ant_filter {
+ exclude_patterns = ["{{ .exclude_patterns1 }}"]
+ include_patterns = ["{{ .include_patterns1 }}"]
+ }
+}
+ assigned_policy {
+ name = xray_security_policy.security.name
+ type = "security"
+}
+ watch_recipients = ["{{ .watch_recipient_0 }}", "{{ .watch_recipient_1 }}"]
+}`
+
const buildWatchTemplate = `resource "xray_security_policy" "security" {
name = "{{ .policy_name_0 }}"
description = "Security policy description"
diff --git a/pkg/xray/watches.go b/pkg/xray/watches.go
index 5105bb74..c2a74bb8 100644
--- a/pkg/xray/watches.go
+++ b/pkg/xray/watches.go
@@ -130,7 +130,12 @@ func unpackProjectResource(rawCfg interface{}) WatchProjectResource {
}
if v, ok := cfg["ant_filter"]; ok {
- antFilters := unpackAntFilters(v.(*schema.Set))
+ antFilters := unpackAntFilters(v.(*schema.Set), "ant-patterns")
+ resource.Filters = append(resource.Filters, antFilters...)
+ }
+
+ if v, ok := cfg["path_ant_filter"]; ok {
+ antFilters := unpackAntFilters(v.(*schema.Set), "path-ant-patterns")
resource.Filters = append(resource.Filters, antFilters...)
}
@@ -154,7 +159,7 @@ func unpackFilters(d *schema.Set) []WatchFilter {
return filters
}
-func unpackAntFilters(d *schema.Set) []WatchFilter {
+func unpackAntFilters(d *schema.Set, filterType string) []WatchFilter {
tfFilters := d.List()
var filters []WatchFilter
@@ -173,7 +178,7 @@ func unpackAntFilters(d *schema.Set) []WatchFilter {
)
filter := WatchFilter{
- Type: "ant-patterns",
+ Type: filterType,
Value: json.RawMessage(filterJsonString),
}
filters = append(filters, filter)
@@ -263,11 +268,16 @@ var packFilterMap = map[string]map[string]interface{}{
"func": packAntFilter,
"attributeName": "ant_filter",
},
+ "path-ant-patterns": {
+ "func": packAntFilter,
+ "attributeName": "path_ant_filter",
+ },
}
func packFilters(filters []WatchFilter, resources map[string]interface{}) (map[string]interface{}, []error) {
resources["filter"] = []map[string]interface{}{}
resources["ant_filter"] = []map[string]interface{}{}
+ resources["path_ant_filter"] = []map[string]interface{}{}
var errors []error
for _, filter := range filters {
@@ -440,6 +450,11 @@ func watchResourceDiff(_ context.Context, diff *schema.ResourceDiff, v interface
if !slices.Contains(antPatternsResourceTypes, resourceType) && len(antFilters) > 0 {
return fmt.Errorf("attribute 'ant_filter' is set when 'watch_resource.type' is not set to 'all-builds' or 'all-projects'")
}
+ pathAntFilters := r["path_ant_filter"].(*schema.Set).List()
+ pathAntPatternsResourceTypes := []string{"repository", "all-repos"}
+ if !slices.Contains(pathAntPatternsResourceTypes, resourceType) && len(pathAntFilters) > 0 {
+ return fmt.Errorf("attribute 'path_ant_filter' is set when 'watch_resource.type' is not set to 'repository' or 'all-repos'")
+ }
}
return nil
}