@@ -19,6 +19,8 @@ import (
19
19
"strings"
20
20
"time"
21
21
22
+ gitInternal "github.com/kimdre/doco-cd/internal/git"
23
+
22
24
"github.com/compose-spec/compose-go/v2/cli"
23
25
"github.com/compose-spec/compose-go/v2/types"
24
26
"github.com/docker/cli/cli/command"
@@ -210,7 +212,6 @@ func LoadCompose(ctx context.Context, workingDir, projectName string, composeFil
210
212
cli .WithWorkingDirectory (workingDir ),
211
213
cli .WithInterpolation (true ),
212
214
cli .WithResolvedPaths (true ),
213
- cli .WithDotEnv ,
214
215
)
215
216
if err != nil {
216
217
return nil , fmt .Errorf ("failed to create project options: %w" , err )
@@ -323,7 +324,7 @@ func DeployCompose(ctx context.Context, dockerCli command.Cli, project *types.Pr
323
324
func DeployStack (
324
325
jobLog * slog.Logger , internalRepoPath , externalRepoPath string , ctx * context.Context ,
325
326
dockerCli * command.Cli , payload * webhook.ParsedPayload , deployConfig * config.DeployConfig ,
326
- latestCommit , appVersion string , forceDeploy bool ,
327
+ changedFiles []gitInternal. ChangedFile , latestCommit , appVersion string , forceDeploy bool ,
327
328
) error {
328
329
startTime := time .Now ()
329
330
@@ -437,8 +438,6 @@ func DeployStack(
437
438
}
438
439
439
440
stackLog .Debug ("file decrypted successfully" , slog .String ("file" , path ))
440
- } else {
441
- stackLog .Debug ("file is not encrypted" , slog .String ("file" , path ))
442
441
}
443
442
444
443
return nil
@@ -450,13 +449,25 @@ func DeployStack(
450
449
project , err := LoadCompose (* ctx , externalWorkingDir , deployConfig .Name , deployConfig .ComposeFiles )
451
450
if err != nil {
452
451
errMsg := "failed to load compose config"
453
- stackLog .Error (errMsg ,
454
- logger .ErrAttr (err ),
455
- slog .Group ("compose_files" , slog .Any ("files" , deployConfig .ComposeFiles )))
452
+ stackLog .Error (errMsg , logger .ErrAttr (err ), slog .Group ("compose_files" , slog .Any ("files" , deployConfig .ComposeFiles )))
453
+
454
+ return fmt .Errorf ("%s: %w" , errMsg , err )
455
+ }
456
+
457
+ hasChanges , err := MountedFilesHaveChanges (changedFiles , project )
458
+ if err != nil {
459
+ errMsg := "failed to check for changed project files"
460
+ stackLog .Error (errMsg , logger .ErrAttr (err ), slog .Group ("compose_files" , slog .Any ("files" , deployConfig .ComposeFiles )))
456
461
457
462
return fmt .Errorf ("%s: %w" , errMsg , err )
458
463
}
459
464
465
+ if hasChanges {
466
+ stackLog .Info ("mounted files have changed, forcing recreation of the stack" )
467
+
468
+ deployConfig .ForceRecreate = true
469
+ }
470
+
460
471
stackLog .Info ("deploying stack" )
461
472
462
473
done := make (chan struct {})
@@ -527,3 +538,168 @@ func DestroyStack(
527
538
528
539
return nil
529
540
}
541
+
542
+ // HasChangedConfigs checks if any files used in docker compose `configs:` definitions have changed using the Git status.
543
+ func HasChangedConfigs (changedFiles []gitInternal.ChangedFile , project * types.Project ) (bool , error ) {
544
+ for _ , c := range project .Configs {
545
+ configPath := c .File
546
+ if configPath == "" {
547
+ continue
548
+ }
549
+
550
+ if ! path .IsAbs (configPath ) {
551
+ configPath = filepath .Join (project .WorkingDir , configPath )
552
+ }
553
+
554
+ for _ , f := range changedFiles {
555
+ var paths []string
556
+
557
+ if f .From != nil {
558
+ fromPath := f .From .Path ()
559
+ if ! path .IsAbs (fromPath ) {
560
+ fromPath = filepath .Join (project .WorkingDir , fromPath )
561
+ }
562
+
563
+ paths = append (paths , fromPath )
564
+ }
565
+
566
+ if f .To != nil {
567
+ toPath := f .To .Path ()
568
+ if ! path .IsAbs (toPath ) {
569
+ toPath = filepath .Join (project .WorkingDir , toPath )
570
+ }
571
+
572
+ paths = append (paths , toPath )
573
+ }
574
+
575
+ for _ , p := range paths {
576
+ if p == configPath {
577
+ return true , nil
578
+ }
579
+ }
580
+ }
581
+ }
582
+
583
+ return false , nil
584
+ }
585
+
586
+ // HasChangedSecrets checks if any files used in docker compose `secrets:` definitions have changed using the Git status.
587
+ func HasChangedSecrets (changedFiles []gitInternal.ChangedFile , project * types.Project ) (bool , error ) {
588
+ for _ , s := range project .Secrets {
589
+ secretPath := s .File
590
+ if secretPath == "" {
591
+ continue
592
+ }
593
+
594
+ if ! path .IsAbs (secretPath ) {
595
+ secretPath = filepath .Join (project .WorkingDir , secretPath )
596
+ }
597
+
598
+ for _ , f := range changedFiles {
599
+ var paths []string
600
+
601
+ if f .From != nil {
602
+ fromPath := f .From .Path ()
603
+ if ! path .IsAbs (fromPath ) {
604
+ fromPath = filepath .Join (project .WorkingDir , fromPath )
605
+ }
606
+
607
+ paths = append (paths , fromPath )
608
+ }
609
+
610
+ if f .To != nil {
611
+ toPath := f .To .Path ()
612
+ if ! path .IsAbs (toPath ) {
613
+ toPath = filepath .Join (project .WorkingDir , toPath )
614
+ }
615
+
616
+ paths = append (paths , toPath )
617
+ }
618
+
619
+ for _ , p := range paths {
620
+ if p == secretPath {
621
+ return true , nil
622
+ }
623
+ }
624
+ }
625
+ }
626
+
627
+ return false , nil
628
+ }
629
+
630
+ // HasChangedBindMounts checks if any files used in docker compose `volumes:` definitions with type `bind` have changed using the Git status.
631
+ func HasChangedBindMounts (changedFiles []gitInternal.ChangedFile , project * types.Project ) (bool , error ) {
632
+ for _ , s := range project .Services {
633
+ for _ , v := range s .Volumes {
634
+ if v .Type == "bind" && v .Source != "" {
635
+ bindPath := v .Source
636
+ if ! path .IsAbs (bindPath ) {
637
+ bindPath = filepath .Join (project .WorkingDir , bindPath )
638
+ }
639
+
640
+ for _ , f := range changedFiles {
641
+ var paths []string
642
+
643
+ if f .From != nil {
644
+ fromPath := f .From .Path ()
645
+ if ! path .IsAbs (fromPath ) {
646
+ fromPath = filepath .Join (project .WorkingDir , fromPath )
647
+ }
648
+
649
+ paths = append (paths , fromPath )
650
+ }
651
+
652
+ if f .To != nil {
653
+ toPath := f .To .Path ()
654
+ if ! path .IsAbs (toPath ) {
655
+ toPath = filepath .Join (project .WorkingDir , toPath )
656
+ }
657
+
658
+ paths = append (paths , toPath )
659
+ }
660
+
661
+ for _ , p := range paths {
662
+ // Check if bindPath is in the changed file path
663
+ if strings .HasPrefix (p , bindPath ) {
664
+ return true , nil
665
+ }
666
+ }
667
+ }
668
+ }
669
+ }
670
+ }
671
+
672
+ return false , nil
673
+ }
674
+
675
+ // MountedFilesHaveChanges checks if any files from config, secret or bind mounts have changed in the project.
676
+ func MountedFilesHaveChanges (changedFiles []gitInternal.ChangedFile , project * types.Project ) (bool , error ) {
677
+ changedConfigs , err := HasChangedConfigs (changedFiles , project )
678
+ if err != nil {
679
+ return false , fmt .Errorf ("failed to check changed configs: %w" , err )
680
+ }
681
+
682
+ if changedConfigs {
683
+ return true , nil
684
+ }
685
+
686
+ changedSecrets , err := HasChangedSecrets (changedFiles , project )
687
+ if err != nil {
688
+ return false , fmt .Errorf ("failed to check changed secrets: %w" , err )
689
+ }
690
+
691
+ if changedSecrets {
692
+ return true , nil
693
+ }
694
+
695
+ changedBindMounts , err := HasChangedBindMounts (changedFiles , project )
696
+ if err != nil {
697
+ return false , fmt .Errorf ("failed to check changed bind mounts: %w" , err )
698
+ }
699
+
700
+ if changedBindMounts {
701
+ return true , nil
702
+ }
703
+
704
+ return false , nil
705
+ }
0 commit comments