@@ -8,9 +8,31 @@ import (
8
8
9
9
"github.com/charmbracelet/huh"
10
10
"github.com/charmbracelet/lipgloss"
11
+ log "github.com/charmbracelet/log"
11
12
"github.com/cloudposse/atmos/pkg/schema"
12
13
"github.com/cloudposse/atmos/pkg/ui/theme"
13
14
u "github.com/cloudposse/atmos/pkg/utils"
15
+ "github.com/pkg/errors"
16
+ )
17
+
18
+ var (
19
+ ErrParseStacks = errors .New ("could not parse stacks" )
20
+ ErrParseComponents = errors .New ("could not parse components" )
21
+ ErrParseTerraformComponents = errors .New ("could not parse Terraform components" )
22
+ ErrParseComponentsAttributes = errors .New ("could not parse component attributes" )
23
+ ErrDescribeStack = errors .New ("error describe stacks" )
24
+ ErrEmptyPath = errors .New ("path cannot be empty" )
25
+ ErrPathNotExist = errors .New ("path not exist" )
26
+ ErrFileStat = errors .New ("error get file stat" )
27
+ ErrMatchPattern = errors .New ("error matching pattern" )
28
+ ErrReadDir = errors .New ("error reading directory" )
29
+ ErrFailedFoundStack = errors .New ("failed to find stack folders" )
30
+ ErrCollectFiles = errors .New ("failed to collect files" )
31
+ ErrEmptyEnvDir = errors .New ("ENV TF_DATA_DIR is empty" )
32
+ ErrResolveEnvDir = errors .New ("error resolving TF_DATA_DIR path" )
33
+ ErrRefusingToDeleteDir = errors .New ("refusing to delete root directory" )
34
+ ErrRefusingToDelete = errors .New ("refusing to delete directory containing" )
35
+ ErrRootPath = errors .New ("root path cannot be empty" )
14
36
)
15
37
16
38
type ObjectInfo struct {
@@ -32,12 +54,12 @@ type Directory struct {
32
54
func findFoldersNamesWithPrefix (root , prefix string , atmosConfig schema.AtmosConfiguration ) ([]string , error ) {
33
55
var folderNames []string
34
56
if root == "" {
35
- return nil , fmt . Errorf ( "root path cannot be empty" )
57
+ return nil , ErrRootPath
36
58
}
37
59
// First, read the directories at the root level (level 1)
38
60
level1Dirs , err := os .ReadDir (root )
39
61
if err != nil {
40
- return nil , fmt .Errorf ("error reading root directory %s: %w" , root , err )
62
+ return nil , fmt .Errorf ("%w path %s: error %v" , ErrReadDir , root , err )
41
63
}
42
64
43
65
for _ , dir := range level1Dirs {
@@ -51,7 +73,7 @@ func findFoldersNamesWithPrefix(root, prefix string, atmosConfig schema.AtmosCon
51
73
level2Path := filepath .Join (root , dir .Name ())
52
74
level2Dirs , err := os .ReadDir (level2Path )
53
75
if err != nil {
54
- u . LogWarning ( fmt . Sprintf ( "Error reading subdirectory %s: %v" , level2Path , err ) )
76
+ log . Debug ( "Error reading subdirectory" , "directory" , level2Path , "error" , err )
55
77
continue
56
78
}
57
79
@@ -68,24 +90,24 @@ func findFoldersNamesWithPrefix(root, prefix string, atmosConfig schema.AtmosCon
68
90
69
91
func CollectDirectoryObjects (basePath string , patterns []string ) ([]Directory , error ) {
70
92
if basePath == "" {
71
- return nil , fmt . Errorf ( "path cannot be empty" )
93
+ return nil , ErrEmptyPath
72
94
}
73
95
if _ , err := os .Stat (basePath ); os .IsNotExist (err ) {
74
- return nil , fmt .Errorf ("path %s does not exist" , basePath )
96
+ return nil , fmt .Errorf ("%w %s" , ErrPathNotExist , basePath )
75
97
}
76
98
var folders []Directory
77
99
78
100
// Helper function to add file information if it exists
79
101
addFileInfo := func (filePath string ) (* ObjectInfo , error ) {
80
102
relativePath , err := filepath .Rel (basePath , filePath )
81
103
if err != nil {
82
- return nil , fmt .Errorf ("error determining relative path for %s: %v" , filePath , err )
104
+ return nil , fmt .Errorf ("%w %s: %v" , ErrRelPath , filePath , err )
83
105
}
84
106
info , err := os .Stat (filePath )
85
107
if os .IsNotExist (err ) {
86
108
return nil , nil // Skip if the file doesn't exist
87
109
} else if err != nil {
88
- return nil , fmt .Errorf ("error stating file %s: %v" , filePath , err )
110
+ return nil , fmt .Errorf ("%w,path %s error %v" , ErrFileStat , filePath , err )
89
111
}
90
112
91
113
return & ObjectInfo {
@@ -100,7 +122,7 @@ func CollectDirectoryObjects(basePath string, patterns []string) ([]Directory, e
100
122
createFolder := func (folderPath string , folderName string ) (* Directory , error ) {
101
123
relativePath , err := filepath .Rel (basePath , folderPath )
102
124
if err != nil {
103
- return nil , fmt .Errorf ("error determining relative path for folder %s: %v" , folderPath , err )
125
+ return nil , fmt .Errorf ("%w %s: %v" , ErrRelPath , folderPath , err )
104
126
}
105
127
106
128
return & Directory {
@@ -116,7 +138,7 @@ func CollectDirectoryObjects(basePath string, patterns []string) ([]Directory, e
116
138
for _ , pat := range patterns {
117
139
matchedFiles , err := filepath .Glob (filepath .Join (folderPath , pat ))
118
140
if err != nil {
119
- return fmt .Errorf ("error matching pattern %s in folder %s: %v" , pat , folderPath , err )
141
+ return fmt .Errorf ("%w %s in folder %s: %v" , ErrMatchPattern , pat , folderPath , err )
120
142
}
121
143
122
144
// Add matched files to folder
@@ -184,7 +206,7 @@ func getStackTerraformStateFolder(componentPath string, stack string, atmosConfi
184
206
tfStateFolderPath := filepath .Join (componentPath , "terraform.tfstate.d" )
185
207
tfStateFolderNames , err := findFoldersNamesWithPrefix (tfStateFolderPath , stack , atmosConfig )
186
208
if err != nil {
187
- return nil , fmt .Errorf ("failed to find stack folders : %w" , err )
209
+ return nil , fmt .Errorf ("%w : %v" , ErrFailedFoundStack , err )
188
210
}
189
211
var stackTfStateFolders []Directory
190
212
for _ , folderName := range tfStateFolderNames {
@@ -195,7 +217,7 @@ func getStackTerraformStateFolder(componentPath string, stack string, atmosConfi
195
217
}
196
218
directories , err := CollectDirectoryObjects (tfStateFolderPath , []string {"*.tfstate" , "*.tfstate.backup" })
197
219
if err != nil {
198
- return nil , fmt .Errorf ("failed to collect files in %s: %w" , tfStateFolderPath , err )
220
+ return nil , fmt .Errorf ("%w in %s: %v" , ErrCollectFiles , tfStateFolderPath , err )
199
221
}
200
222
for i := range directories {
201
223
if directories [i ].Files != nil {
@@ -286,7 +308,7 @@ func confirmDeletion(atmosConfig schema.AtmosConfiguration) (bool, error) {
286
308
return false , err
287
309
}
288
310
if ! confirm {
289
- u . LogWarning ("Mission aborted." )
311
+ log . Warn ("Mission aborted." )
290
312
return false , nil
291
313
}
292
314
return true , nil
@@ -297,29 +319,33 @@ func deleteFolders(folders []Directory, relativePath string, atmosConfig schema.
297
319
var errors []error
298
320
for _ , folder := range folders {
299
321
for _ , file := range folder .Files {
300
- path := filepath .ToSlash (filepath .Join (relativePath , file .Name ))
322
+ fileRel , err := getRelativePath (atmosConfig .BasePath , file .FullPath )
323
+ if err != nil {
324
+ log .Debug ("failed to get relative path" , "path" , file .FullPath , "error" , err )
325
+ fileRel = filepath .Join (relativePath , file .Name )
326
+ }
301
327
if file .IsDir {
302
- if err := DeletePathTerraform (file .FullPath , path + "/" ); err != nil {
303
- errors = append (errors , fmt .Errorf ("failed to delete %s: %w" , path , err ))
328
+ if err := DeletePathTerraform (file .FullPath , fileRel + "/" ); err != nil {
329
+ errors = append (errors , fmt .Errorf ("failed to delete %s: %w" , fileRel , err ))
304
330
}
305
331
} else {
306
- if err := DeletePathTerraform (file .FullPath , path ); err != nil {
307
- errors = append (errors , fmt .Errorf ("failed to delete %s: %w" , path , err ))
332
+ if err := DeletePathTerraform (file .FullPath , fileRel ); err != nil {
333
+ errors = append (errors , fmt .Errorf ("failed to delete %s: %w" , fileRel , err ))
308
334
}
309
335
}
310
336
}
311
337
}
312
338
if len (errors ) > 0 {
313
339
for _ , err := range errors {
314
- u . LogWarning (err . Error () )
340
+ log . Debug (err )
315
341
}
316
342
}
317
343
// check if the folder is empty by using the os.ReadDir function
318
344
for _ , folder := range folders {
319
345
entries , err := os .ReadDir (folder .FullPath )
320
346
if err == nil && len (entries ) == 0 {
321
347
if err := os .Remove (folder .FullPath ); err != nil {
322
- u . LogWarning ( fmt . Sprintf ( "Error removing directory %s: %v" , folder .FullPath , err ) )
348
+ log . Debug ( "Error removing directory" , "path" , folder .FullPath , "error" , err )
323
349
}
324
350
}
325
351
}
@@ -332,15 +358,15 @@ func handleTFDataDir(componentPath string, relativePath string, atmosConfig sche
332
358
return
333
359
}
334
360
if err := IsValidDataDir (tfDataDir ); err != nil {
335
- u . LogWarning ( err . Error () )
361
+ log . Debug ( "error validating TF_DATA_DIR" , "error" , err )
336
362
return
337
363
}
338
364
if _ , err := os .Stat (filepath .Join (componentPath , tfDataDir )); os .IsNotExist (err ) {
339
- u . LogWarning ( fmt . Sprintf ( "TF_DATA_DIR '%s' does not exist" , tfDataDir ) )
365
+ log . Debug ( "TF_DATA_DIR does not exist" , "TF_DATA_DIR" , tfDataDir , "error" , err )
340
366
return
341
367
}
342
368
if err := DeletePathTerraform (filepath .Join (componentPath , tfDataDir ), filepath .Join (relativePath , tfDataDir )); err != nil {
343
- u . LogWarning ( err . Error () )
369
+ log . Debug ( "error deleting TF_DATA_DIR" , "TF_DATA_DIR" , tfDataDir , "error" , err )
344
370
}
345
371
}
346
372
@@ -365,17 +391,25 @@ func initializeFilesToClear(info schema.ConfigAndStacksInfo, atmosConfig schema.
365
391
366
392
func IsValidDataDir (tfDataDir string ) error {
367
393
if tfDataDir == "" {
368
- return fmt . Errorf ( "ENV TF_DATA_DIR is empty" )
394
+ return ErrEmptyEnvDir
369
395
}
370
396
absTFDataDir , err := filepath .Abs (tfDataDir )
371
397
if err != nil {
372
- return fmt .Errorf ("error resolving TF_DATA_DIR path : %v" , err )
398
+ return fmt .Errorf ("%w : %v" , ErrResolveEnvDir , err )
373
399
}
400
+
401
+ // Check for root path on both Unix and Windows systems
374
402
if absTFDataDir == "/" || absTFDataDir == filepath .Clean ("/" ) {
375
- return fmt .Errorf ("refusing to delete root directory '/'" )
403
+ return fmt .Errorf ("%w: %s" , ErrRefusingToDeleteDir , absTFDataDir )
404
+ }
405
+
406
+ // Windows-specific root path check (like C:\ or D:\)
407
+ if len (absTFDataDir ) == 3 && absTFDataDir [1 :] == ":\\ " {
408
+ return fmt .Errorf ("%w: %s" , ErrRefusingToDeleteDir , absTFDataDir )
376
409
}
410
+
377
411
if strings .Contains (absTFDataDir , ".." ) {
378
- return fmt .Errorf ("refusing to delete directory containing '..' " )
412
+ return fmt .Errorf ("%w: %s" , ErrRefusingToDelete , ".. " )
379
413
}
380
414
return nil
381
415
}
@@ -385,6 +419,7 @@ func handleCleanSubCommand(info schema.ConfigAndStacksInfo, componentPath string
385
419
if info .SubCommand != "clean" {
386
420
return nil
387
421
}
422
+
388
423
cleanPath := componentPath
389
424
if info .ComponentFromArg != "" && info .StackFromArg == "" {
390
425
if info .Context .BaseComponent == "" {
@@ -406,17 +441,27 @@ func handleCleanSubCommand(info schema.ConfigAndStacksInfo, componentPath string
406
441
407
442
force := u .SliceContainsString (info .AdditionalArgsAndFlags , forceFlag )
408
443
filesToClear := initializeFilesToClear (info , atmosConfig )
409
- folders , err := CollectDirectoryObjects (cleanPath , filesToClear )
444
+ var FilterComponents []string
445
+ if info .ComponentFromArg != "" {
446
+ FilterComponents = append (FilterComponents , info .ComponentFromArg )
447
+ }
448
+ stacksMap , err := ExecuteDescribeStacks (
449
+ atmosConfig , info .StackFromArg ,
450
+ FilterComponents ,
451
+ nil , nil , false , false , false , false , nil )
410
452
if err != nil {
411
- u .LogTrace (fmt .Errorf ("error collecting folders and files: %v" , err ).Error ())
453
+ return fmt .Errorf ("%w: %v" , ErrDescribeStack , err )
454
+ }
455
+ allComponentsRelativePaths := getAllStacksComponentsPaths (stacksMap )
456
+ folders , err := CollectComponentsDirectoryObjects (atmosConfig .TerraformDirAbsolutePath , allComponentsRelativePaths , filesToClear )
457
+ if err != nil {
458
+ log .Debug ("error collecting folders and files" , "error" , err )
412
459
return err
413
460
}
414
-
415
461
if info .Component != "" && info .Stack != "" {
416
462
stackFolders , err := getStackTerraformStateFolder (cleanPath , info .Stack , atmosConfig )
417
463
if err != nil {
418
- errMsg := fmt .Errorf ("error getting stack terraform state folders: %v" , err )
419
- u .LogTrace (errMsg .Error ())
464
+ log .Debug ("error getting stack terraform state folders" , "error" , err )
420
465
}
421
466
if stackFolders != nil {
422
467
folders = append (folders , stackFolders ... )
@@ -427,11 +472,11 @@ func handleCleanSubCommand(info schema.ConfigAndStacksInfo, componentPath string
427
472
var tfDataDirFolders []Directory
428
473
if tfDataDir != "" {
429
474
if err := IsValidDataDir (tfDataDir ); err != nil {
430
- u . LogTrace ( err . Error () )
475
+ log . Debug ( "error validating TF_DATA_DIR" , "error" , err )
431
476
} else {
432
477
tfDataDirFolders , err = CollectDirectoryObjects (cleanPath , []string {tfDataDir })
433
478
if err != nil {
434
- u . LogTrace ( fmt . Errorf ( "error collecting folder of ENV TF_DATA_DIR: %v" , err ). Error () )
479
+ log . Debug ( "error collecting folder of ENV TF_DATA_DIR" , "error" , err )
435
480
}
436
481
}
437
482
}
0 commit comments