Skip to content

Commit 9adeccf

Browse files
cashwinichourasiaHarness
authored andcommitted
feat:[AH-2554]: Support for allowed blocked patterns per registry mapping in CLI (#148)
* 19bbf2 Added support for allowed blocked patterns per registry mapping
1 parent c2705fc commit 9adeccf

File tree

5 files changed

+182
-4
lines changed

5 files changed

+182
-4
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/BurntSushi/toml v1.5.0
77
github.com/MakeNowJust/heredoc v1.0.0
88
github.com/getkin/kin-openapi v0.132.0
9+
github.com/gobwas/glob v0.2.3
910
github.com/google/go-containerregistry v0.20.6
1011
github.com/google/uuid v1.6.0
1112
github.com/hashicorp/go-retryablehttp v0.7.8

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
116116
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
117117
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
118118
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
119+
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
120+
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
119121
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
120122
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
121123
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=

migrate_ex_config.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ mappings:
2929
destinationRegistry: dst
3030
- artifactType: MAVEN
3131
sourceRegistry: src
32+
excludePatterns:
33+
- com/*
34+
- foo/*
3235
destinationRegistry: dst
3336
- artifactType: GENERIC
3437
sourceRegistry: src
@@ -47,4 +50,9 @@ mappings:
4750
destinationRegistry: dst
4851
- artifactType: COMPOSER
4952
sourceRegistry: src
50-
destinationRegistry: dst
53+
destinationRegistry: dst
54+
- artifactType: DART
55+
sourceRegistry: src
56+
destinationRegistry: dst
57+
includePatterns:
58+
- packages/sample_dart_pkg/**

module/ar/migrate/migratable/registry.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"fmt"
66
"time"
77

8+
"github.com/google/uuid"
89
"github.com/harness/harness-cli/module/ar/migrate/adapter"
910
"github.com/harness/harness-cli/module/ar/migrate/engine"
1011
"github.com/harness/harness-cli/module/ar/migrate/tree"
1112
"github.com/harness/harness-cli/module/ar/migrate/types"
12-
13-
"github.com/google/uuid"
13+
"github.com/harness/harness-cli/module/ar/migrate/util"
1414
"github.com/rs/zerolog"
1515
"github.com/rs/zerolog/log"
1616
)
@@ -81,7 +81,6 @@ func (r *Registry) Pre(ctx context.Context) error {
8181
logger.Info().Msg("Starting registry pre-migration step")
8282

8383
startTime := time.Now()
84-
8584
registry, err := r.destAdapter.GetRegistry(ctx, r.destRegistry)
8685
if err != nil {
8786
log.Error().Err(err).Msgf("Failed to get registry %q", r.destRegistry)
@@ -107,13 +106,31 @@ func (r *Registry) Migrate(ctx context.Context) error {
107106

108107
logger.Info().Msg("Starting registry migration step")
109108

109+
if len(r.mapping.IncludePatterns) > 0 && len(r.mapping.ExcludePatterns) > 0 {
110+
logger.Error().Msgf("Either include or Exclude Pattern is suppoted at a time for %s", r.artifactType)
111+
return fmt.Errorf("failed in validating config file for %s ", r.artifactType)
112+
}
113+
110114
startTime := time.Now()
111115

112116
files, err2 := r.srcAdapter.GetFiles(r.srcRegistry)
113117
if err2 != nil {
114118
logger.Error().Msgf("Failed to get files from registry %s", r.srcRegistry)
115119
return fmt.Errorf("get files from registry %s failed: %w", r.srcRegistry, err2)
116120
}
121+
122+
// Filter files based on include/exclude patterns
123+
currArtifactType := r.artifactType
124+
if util.IsFileLevelFilterableArtifact(currArtifactType) {
125+
if len(r.mapping.IncludePatterns) > 0 || len(r.mapping.ExcludePatterns) > 0 {
126+
originalCount := len(files)
127+
filteredFiles := util.FilterFilesByPatterns(files, r.mapping.IncludePatterns, r.mapping.ExcludePatterns)
128+
files = filteredFiles
129+
logger.Info().Msgf("Filtered files: %d -> %d (includePatterns: %v, excludePatterns: %v)",
130+
originalCount, len(files), r.mapping.IncludePatterns, r.mapping.ExcludePatterns)
131+
}
132+
}
133+
117134
root := tree.TransformToTree(files)
118135

119136
pkgs, err := r.srcAdapter.GetPackages(r.srcRegistry, r.artifactType, root)
@@ -122,6 +139,17 @@ func (r *Registry) Migrate(ctx context.Context) error {
122139
return fmt.Errorf("get packages failed: %w", err)
123140
}
124141

142+
// applying package level filter
143+
if util.IsPackageLevelFilterableArtifact(currArtifactType) {
144+
if len(r.mapping.IncludePatterns) > 0 || len(r.mapping.ExcludePatterns) > 0 {
145+
originalCount := len(pkgs)
146+
filteredPackages := util.FilterFilesByPatternsPackageName(pkgs, r.mapping.IncludePatterns, r.mapping.ExcludePatterns)
147+
pkgs = filteredPackages
148+
logger.Info().Msgf("Filtered packages: %d -> %d (includePatterns: %v, excludePatterns: %v)",
149+
originalCount, len(pkgs), r.mapping.IncludePatterns, r.mapping.ExcludePatterns)
150+
}
151+
}
152+
125153
var jobs []engine.Job
126154
for _, pkg := range pkgs {
127155
treeNode, err2 := tree.GetNodeForPath(root, pkg.Path)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/gobwas/glob"
8+
"github.com/harness/harness-cli/module/ar/migrate/types"
9+
)
10+
11+
/* Patterns support * and ** wildcards:
12+
- * matches files in the current directory only (single level)
13+
- ** matches files in all subdirectories recursively (multi-level)
14+
*/
15+
16+
func MatchesPattern(filePath string, patterns []string) bool {
17+
if len(patterns) == 0 {
18+
return true
19+
}
20+
21+
// removing leading slashes
22+
normalizedPath := strings.TrimPrefix(filePath, "/")
23+
24+
for _, pattern := range patterns {
25+
normalizedPattern := strings.TrimPrefix(pattern, "/")
26+
27+
// Validate pattern - only * and ** wildcards are supported
28+
if containsUnsupportedWildcards(normalizedPattern) {
29+
fmt.Printf("WARNING: Pattern '%s' contains unsupported wildcard characters. Only * and ** are supported.\n", pattern)
30+
continue
31+
}
32+
33+
// The library handles * and ** natively
34+
g, err := glob.Compile(normalizedPattern, '/')
35+
if err != nil {
36+
// If pattern compilation fails, skip this pattern
37+
continue
38+
}
39+
40+
// Check if the path matches the pattern
41+
if g.Match(normalizedPath) {
42+
return true
43+
}
44+
}
45+
46+
return false
47+
}
48+
49+
// containsUnsupportedWildcards checks if pattern contains unsupported wildcard characters
50+
// Only * and ** are supported. Characters like ?, [, ], {, } are not supported.
51+
func containsUnsupportedWildcards(pattern string) bool {
52+
unsupportedChars := []rune{'?', '[', ']', '{', '}'}
53+
54+
for _, char := range unsupportedChars {
55+
if strings.ContainsRune(pattern, char) {
56+
return true
57+
}
58+
}
59+
60+
return false
61+
}
62+
63+
// FilterFilesByPatterns filters a list of files based on include and exclude patterns.
64+
// Include patterns are applied first (if any), then exclude patterns are applied.
65+
// If no include patterns are specified, all files are included by default.
66+
67+
func FilterFilesByPatterns(files []types.File, includePatterns, excludePatterns []string) []types.File {
68+
if len(includePatterns) == 0 && len(excludePatterns) == 0 {
69+
return files
70+
}
71+
72+
var filtered []types.File
73+
for _, file := range files {
74+
// Skipping folders
75+
if file.Folder {
76+
filtered = append(filtered, file)
77+
continue
78+
}
79+
80+
if len(includePatterns) > 0 {
81+
if !MatchesPattern(file.Uri, includePatterns) {
82+
// File doesn't match any include pattern, skip it
83+
continue
84+
}
85+
} else if len(excludePatterns) > 0 {
86+
if MatchesPattern(file.Uri, excludePatterns) {
87+
// File matches an exclude pattern, skip it
88+
continue
89+
}
90+
}
91+
//appending passed files
92+
filtered = append(filtered, file)
93+
}
94+
95+
return filtered
96+
}
97+
98+
// This is to filter based on package name
99+
func FilterFilesByPatternsPackageName(packages []types.Package, includePatterns, excludePatterns []string) []types.Package {
100+
if len(includePatterns) == 0 && len(excludePatterns) == 0 {
101+
return packages
102+
}
103+
104+
var filteredPackages []types.Package
105+
for _, pkg := range packages {
106+
107+
if len(includePatterns) > 0 {
108+
if !MatchesPattern(pkg.Name, includePatterns) {
109+
continue
110+
}
111+
} else if len(excludePatterns) > 0 {
112+
if MatchesPattern(pkg.Name, excludePatterns) {
113+
continue
114+
}
115+
}
116+
filteredPackages = append(filteredPackages, pkg)
117+
}
118+
119+
return filteredPackages
120+
}
121+
122+
func IsFileLevelFilterableArtifact(artifactType types.ArtifactType) bool {
123+
switch artifactType {
124+
case types.GENERIC, types.PYTHON, types.MAVEN, types.NUGET, types.NPM, types.DART, types.GO:
125+
return true
126+
default:
127+
return false
128+
}
129+
}
130+
131+
func IsPackageLevelFilterableArtifact(artifactType types.ArtifactType) bool {
132+
133+
switch artifactType {
134+
case types.DOCKER, types.HELM, types.HELM_LEGACY, types.RPM, types.CONDA, types.COMPOSER:
135+
return true
136+
default:
137+
return false
138+
}
139+
}

0 commit comments

Comments
 (0)