7
7
"log"
8
8
"os"
9
9
"os/exec"
10
-
11
10
"path/filepath"
12
11
"strings"
12
+ "sync"
13
13
"time"
14
14
15
15
"github.com/chime/mani-diffy/pkg/helm"
@@ -44,9 +44,33 @@ type Walker struct {
44
44
ignoreSuffix string
45
45
}
46
46
47
+ // Thread-safe visited map
48
+ type VisitedMap struct {
49
+ sync.RWMutex
50
+ visited map [string ]bool
51
+ }
52
+
53
+ func NewVisitedMap () * VisitedMap {
54
+ return & VisitedMap {
55
+ visited : make (map [string ]bool ),
56
+ }
57
+ }
58
+
59
+ func (vm * VisitedMap ) Set (path string ) {
60
+ vm .Lock ()
61
+ defer vm .Unlock ()
62
+ vm .visited [path ] = true
63
+ }
64
+
65
+ func (vm * VisitedMap ) Get (path string ) bool {
66
+ vm .RLock ()
67
+ defer vm .RUnlock ()
68
+ return vm .visited [path ]
69
+ }
70
+
47
71
// Walk walks a directory tree looking for Argo applications and renders them
48
72
func (w * Walker ) Walk (inputPath , outputPath string , maxDepth int , hashes HashStore ) error {
49
- visited := make ( map [ string ] bool )
73
+ visited := NewVisitedMap ( )
50
74
51
75
if err := w .walk (inputPath , outputPath , 0 , maxDepth , visited , hashes ); err != nil {
52
76
return err
@@ -63,7 +87,7 @@ func (w *Walker) Walk(inputPath, outputPath string, maxDepth int, hashes HashSto
63
87
return nil
64
88
}
65
89
66
- func pruneUnvisited (visited map [ string ] bool , outputPath string ) error {
90
+ func pruneUnvisited (visited * VisitedMap , outputPath string ) error {
67
91
files , err := os .ReadDir (outputPath )
68
92
if err != nil {
69
93
return err
@@ -75,7 +99,7 @@ func pruneUnvisited(visited map[string]bool, outputPath string) error {
75
99
}
76
100
77
101
path := filepath .Join (outputPath , f .Name ())
78
- if visited [ path ] {
102
+ if visited . Get ( path ) {
79
103
continue
80
104
}
81
105
if err := os .RemoveAll (path ); err != nil {
@@ -86,7 +110,7 @@ func pruneUnvisited(visited map[string]bool, outputPath string) error {
86
110
return nil
87
111
}
88
112
89
- func (w * Walker ) walk (inputPath , outputPath string , depth , maxDepth int , visited map [ string ] bool , hashes HashStore ) error {
113
+ func (w * Walker ) walk (inputPath , outputPath string , depth , maxDepth int , visited * VisitedMap , hashes HashStore ) error {
90
114
if maxDepth != InfiniteDepth {
91
115
// If we've reached the max depth, stop walking
92
116
if depth > maxDepth {
@@ -100,65 +124,97 @@ func (w *Walker) walk(inputPath, outputPath string, depth, maxDepth int, visited
100
124
if err != nil {
101
125
return err
102
126
}
127
+
128
+ var wg sync.WaitGroup
129
+ errChan := make (chan error , len (fi ))
130
+ semaphore := make (chan struct {}, 10 ) // Limit concurrent goroutines
131
+
103
132
for _ , file := range fi {
104
133
if ! strings .Contains (file .Name (), ".yaml" ) {
105
134
continue
106
135
}
107
136
108
- crds , err := helm .Read (filepath .Join (inputPath , file .Name ()))
109
- if err != nil {
110
- return err
111
- }
112
- for _ , crd := range crds {
113
- if crd .Kind != "Application" {
114
- continue
115
- }
116
-
117
- if strings .HasSuffix (crd .ObjectMeta .Name , w .ignoreSuffix ) {
118
- continue
119
- }
137
+ wg .Add (1 )
138
+ semaphore <- struct {}{} // Acquire semaphore
120
139
121
- path := filepath .Join (outputPath , crd .ObjectMeta .Name )
122
- visited [path ] = true
140
+ go func (file os.DirEntry ) {
141
+ defer wg .Done ()
142
+ defer func () { <- semaphore }() // Release semaphore
123
143
124
- hash , err := hashes .Get (crd .ObjectMeta .Name )
125
- // COMPARE HASHES HERE. STEP INTO RENDER IF NO MATCH
144
+ crds , err := helm .Read (filepath .Join (inputPath , file .Name ()))
126
145
if err != nil {
127
- return err
146
+ errChan <- err
147
+ return
128
148
}
129
149
130
- hashGenerated , err := w .GenerateHash (crd )
131
- if err != nil {
132
- if errors .Is (err , kustomize .ErrNotSupported ) {
150
+ for _ , crd := range crds {
151
+ if crd .Kind != "Application" {
133
152
continue
134
153
}
135
- return err
136
- }
137
154
138
- emptyManifest , err := helm .EmptyManifest (filepath .Join (path , "manifest.yaml" ))
139
- if err != nil {
140
- return err
141
- }
155
+ if strings .HasSuffix (crd .ObjectMeta .Name , w .ignoreSuffix ) {
156
+ continue
157
+ }
158
+
159
+ path := filepath .Join (outputPath , crd .ObjectMeta .Name )
160
+ visited .Set (path )
142
161
143
- if hashGenerated != hash || emptyManifest {
144
- log .Printf ("No match detected. Render: %s\n " , crd .ObjectMeta .Name )
145
- if err := w .Render (crd , path ); err != nil {
162
+ hash , err := hashes .Get (crd .ObjectMeta .Name )
163
+ if err != nil {
164
+ errChan <- err
165
+ return
166
+ }
167
+
168
+ hashGenerated , err := w .GenerateHash (crd )
169
+ if err != nil {
146
170
if errors .Is (err , kustomize .ErrNotSupported ) {
147
171
continue
148
172
}
149
- return err
173
+ errChan <- err
174
+ return
150
175
}
151
176
152
- if err := hashes .Add (crd .ObjectMeta .Name , hashGenerated ); err != nil {
153
- return err
177
+ emptyManifest , err := helm .EmptyManifest (filepath .Join (path , "manifest.yaml" ))
178
+ if err != nil {
179
+ errChan <- err
180
+ return
154
181
}
155
- }
156
182
157
- if err := w .walk (path , outputPath , depth + 1 , maxDepth , visited , hashes ); err != nil {
158
- return err
183
+ if hashGenerated != hash || emptyManifest {
184
+ log .Printf ("No match detected. Render: %s\n " , crd .ObjectMeta .Name )
185
+ if err := w .Render (crd , path ); err != nil {
186
+ if errors .Is (err , kustomize .ErrNotSupported ) {
187
+ continue
188
+ }
189
+ errChan <- err
190
+ return
191
+ }
192
+
193
+ if err := hashes .Add (crd .ObjectMeta .Name , hashGenerated ); err != nil {
194
+ errChan <- err
195
+ return
196
+ }
197
+ }
198
+
199
+ if err := w .walk (path , outputPath , depth + 1 , maxDepth , visited , hashes ); err != nil {
200
+ errChan <- err
201
+ return
202
+ }
159
203
}
204
+ }(file )
205
+ }
206
+
207
+ // Wait for all goroutines to complete
208
+ wg .Wait ()
209
+ close (errChan )
210
+
211
+ // Check for any errors
212
+ for err := range errChan {
213
+ if err != nil {
214
+ return err
160
215
}
161
216
}
217
+
162
218
return nil
163
219
}
164
220
0 commit comments