77 "log"
88 "os"
99 "os/exec"
10-
1110 "path/filepath"
1211 "strings"
12+ "sync"
1313 "time"
1414
1515 "github.com/chime/mani-diffy/pkg/helm"
@@ -44,9 +44,33 @@ type Walker struct {
4444 ignoreSuffix string
4545}
4646
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+
4771// Walk walks a directory tree looking for Argo applications and renders them
4872func (w * Walker ) Walk (inputPath , outputPath string , maxDepth int , hashes HashStore ) error {
49- visited := make ( map [ string ] bool )
73+ visited := NewVisitedMap ( )
5074
5175 if err := w .walk (inputPath , outputPath , 0 , maxDepth , visited , hashes ); err != nil {
5276 return err
@@ -63,7 +87,7 @@ func (w *Walker) Walk(inputPath, outputPath string, maxDepth int, hashes HashSto
6387 return nil
6488}
6589
66- func pruneUnvisited (visited map [ string ] bool , outputPath string ) error {
90+ func pruneUnvisited (visited * VisitedMap , outputPath string ) error {
6791 files , err := os .ReadDir (outputPath )
6892 if err != nil {
6993 return err
@@ -75,7 +99,7 @@ func pruneUnvisited(visited map[string]bool, outputPath string) error {
7599 }
76100
77101 path := filepath .Join (outputPath , f .Name ())
78- if visited [ path ] {
102+ if visited . Get ( path ) {
79103 continue
80104 }
81105 if err := os .RemoveAll (path ); err != nil {
@@ -86,7 +110,7 @@ func pruneUnvisited(visited map[string]bool, outputPath string) error {
86110 return nil
87111}
88112
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 {
90114 if maxDepth != InfiniteDepth {
91115 // If we've reached the max depth, stop walking
92116 if depth > maxDepth {
@@ -100,65 +124,97 @@ func (w *Walker) walk(inputPath, outputPath string, depth, maxDepth int, visited
100124 if err != nil {
101125 return err
102126 }
127+
128+ var wg sync.WaitGroup
129+ errChan := make (chan error , len (fi ))
130+ semaphore := make (chan struct {}, 10 ) // Limit concurrent goroutines
131+
103132 for _ , file := range fi {
104133 if ! strings .Contains (file .Name (), ".yaml" ) {
105134 continue
106135 }
107136
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
120139
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
123143
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 ()))
126145 if err != nil {
127- return err
146+ errChan <- err
147+ return
128148 }
129149
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" {
133152 continue
134153 }
135- return err
136- }
137154
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 )
142161
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 {
146170 if errors .Is (err , kustomize .ErrNotSupported ) {
147171 continue
148172 }
149- return err
173+ errChan <- err
174+ return
150175 }
151176
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
154181 }
155- }
156182
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+ }
159203 }
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
160215 }
161216 }
217+
162218 return nil
163219}
164220
0 commit comments