@@ -41,6 +41,7 @@ import (
41
41
"github.com/distribution/distribution/v3/configuration"
42
42
"github.com/distribution/distribution/v3/registry/handlers"
43
43
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
44
+ dockerconfig "github.com/docker/cli/cli/config"
44
45
"github.com/docker/cli/cli/config/configfile"
45
46
"github.com/fatih/color"
46
47
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -56,7 +57,7 @@ import (
56
57
var ErrNoFallbackImage = errors .New ("no fallback image has been specified" )
57
58
58
59
// DockerConfig represents the Docker configuration file.
59
- type DockerConfig configfile.ConfigFile
60
+ type DockerConfig = configfile.ConfigFile
60
61
61
62
type runtimeDataStore struct {
62
63
// Runtime data.
@@ -154,13 +155,13 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
154
155
155
156
opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
156
157
157
- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
158
+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
158
159
if err != nil {
159
160
return err
160
161
}
161
162
defer func () {
162
- if err := cleanupDockerConfigJSON (); err != nil {
163
- opts .Logger (log .LevelError , "failed to cleanup docker config JSON : %w" , err )
163
+ if err := cleanupDockerConfigOverride (); err != nil {
164
+ opts .Logger (log .LevelError , "failed to cleanup docker config override : %w" , err )
164
165
}
165
166
}() // best effort
166
167
@@ -711,6 +712,11 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
711
712
// Sanitize the environment of any opts!
712
713
options .UnsetEnv ()
713
714
715
+ // Remove the Docker config secret file!
716
+ if err := cleanupDockerConfigOverride (); err != nil {
717
+ return err
718
+ }
719
+
714
720
// Set the environment from /etc/environment first, so it can be
715
721
// overridden by the image and devcontainer settings.
716
722
err = setEnvFromEtcEnvironment (opts .Logger )
@@ -770,11 +776,6 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
770
776
exportEnvFile .Close ()
771
777
}
772
778
773
- // Remove the Docker config secret file!
774
- if err := cleanupDockerConfigJSON (); err != nil {
775
- return err
776
- }
777
-
778
779
if runtimeData .ContainerUser == "" {
779
780
opts .Logger (log .LevelWarn , "#%d: no user specified, using root" , stageNumber )
780
781
}
@@ -978,13 +979,13 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
978
979
979
980
opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
980
981
981
- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
982
+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
982
983
if err != nil {
983
984
return nil , err
984
985
}
985
986
defer func () {
986
- if err := cleanupDockerConfigJSON (); err != nil {
987
- opts .Logger (log .LevelError , "failed to cleanup docker config JSON : %w" , err )
987
+ if err := cleanupDockerConfigOverride (); err != nil {
988
+ opts .Logger (log .LevelError , "failed to cleanup docker config override : %w" , err )
988
989
}
989
990
}() // best effort
990
991
@@ -1315,7 +1316,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
1315
1316
options .UnsetEnv ()
1316
1317
1317
1318
// Remove the Docker config secret file!
1318
- if err := cleanupDockerConfigJSON (); err != nil {
1319
+ if err := cleanupDockerConfigOverride (); err != nil {
1319
1320
return nil , err
1320
1321
}
1321
1322
@@ -1567,8 +1568,22 @@ func maybeDeleteFilesystem(logger log.Func, force bool) error {
1567
1568
}
1568
1569
1569
1570
func fileExists (fs billy.Filesystem , path string ) bool {
1570
- _ , err := fs .Stat (path )
1571
- return err == nil
1571
+ fi , err := fs .Stat (path )
1572
+ return err == nil && ! fi .IsDir ()
1573
+ }
1574
+
1575
+ func readFile (fs billy.Filesystem , name string ) ([]byte , error ) {
1576
+ f , err := fs .Open (name )
1577
+ if err != nil {
1578
+ return nil , fmt .Errorf ("open file: %w" , err )
1579
+ }
1580
+ defer f .Close ()
1581
+
1582
+ b , err := io .ReadAll (f )
1583
+ if err != nil {
1584
+ return nil , fmt .Errorf ("read file: %w" , err )
1585
+ }
1586
+ return b , nil
1572
1587
}
1573
1588
1574
1589
func copyFile (fs billy.Filesystem , src , dst string , mode fs.FileMode ) error {
@@ -1595,6 +1610,21 @@ func copyFile(fs billy.Filesystem, src, dst string, mode fs.FileMode) error {
1595
1610
return nil
1596
1611
}
1597
1612
1613
+ func writeFile (fs billy.Filesystem , name string , data []byte , perm fs.FileMode ) error {
1614
+ f , err := fs .OpenFile (name , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , perm )
1615
+ if err != nil {
1616
+ return fmt .Errorf ("create file: %w" , err )
1617
+ }
1618
+ _ , err = f .Write (data )
1619
+ if err != nil {
1620
+ err = fmt .Errorf ("write file: %w" , err )
1621
+ }
1622
+ if err2 := f .Close (); err2 != nil && err == nil {
1623
+ err = fmt .Errorf ("close file: %w" , err2 )
1624
+ }
1625
+ return err
1626
+ }
1627
+
1598
1628
func writeMagicImageFile (fs billy.Filesystem , path string , v any ) error {
1599
1629
file , err := fs .OpenFile (path , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0o644 )
1600
1630
if err != nil {
@@ -1627,55 +1657,161 @@ func parseMagicImageFile(fs billy.Filesystem, path string, v any) error {
1627
1657
return nil
1628
1658
}
1629
1659
1630
- func initDockerConfigJSON (logf log.Func , workingDir workingdir.WorkingDir , dockerConfigBase64 string ) (func () error , error ) {
1631
- var cleanupOnce sync.Once
1632
- noop := func () error { return nil }
1633
- if dockerConfigBase64 == "" {
1634
- return noop , nil
1660
+ const (
1661
+ dockerConfigFile = dockerconfig .ConfigFileName
1662
+ dockerConfigEnvKey = dockerconfig .EnvOverrideConfigDir
1663
+ )
1664
+
1665
+ // initDockerConfigOverride sets the DOCKER_CONFIG environment variable
1666
+ // to a path within the working directory. If a base64 encoded Docker
1667
+ // config is provided, it is written to the path/config.json and the
1668
+ // DOCKER_CONFIG environment variable is set to the path. If no base64
1669
+ // encoded Docker config is provided, the following paths are checked in
1670
+ // order:
1671
+ //
1672
+ // 1. $DOCKER_CONFIG/config.json
1673
+ // 2. $DOCKER_CONFIG
1674
+ // 3. /.envbuilder/config.json
1675
+ //
1676
+ // If a Docker config file is found, its path is set as DOCKER_CONFIG.
1677
+ func initDockerConfigOverride (bfs billy.Filesystem , logf log.Func , workingDir workingdir.WorkingDir , dockerConfigBase64 string ) (func () error , error ) {
1678
+ // If dockerConfigBase64 is set, it will have priority over file
1679
+ // detection.
1680
+ var dockerConfigJSON []byte
1681
+ var err error
1682
+ if dockerConfigBase64 != "" {
1683
+ logf (log .LevelInfo , "Using base64 encoded Docker config" )
1684
+
1685
+ dockerConfigJSON , err = base64 .StdEncoding .DecodeString (dockerConfigBase64 )
1686
+ if err != nil {
1687
+ return nil , fmt .Errorf ("decode docker config: %w" , err )
1688
+ }
1689
+ }
1690
+
1691
+ oldDockerConfig := os .Getenv (dockerConfigEnvKey )
1692
+ var oldDockerConfigFile string
1693
+ if oldDockerConfig != "" {
1694
+ oldDockerConfigFile = filepath .Join (oldDockerConfig , dockerConfigFile )
1695
+ }
1696
+ for _ , path := range []string {
1697
+ oldDockerConfigFile , // $DOCKER_CONFIG/config.json
1698
+ oldDockerConfig , // $DOCKER_CONFIG
1699
+ workingDir .Join (dockerConfigFile ), // /.envbuilder/config.json
1700
+ } {
1701
+ if path == "" || ! fileExists (bfs , path ) {
1702
+ continue
1703
+ }
1704
+
1705
+ logf (log .LevelWarn , "Found Docker config at %s, this file will remain after the build" , path )
1706
+
1707
+ if dockerConfigJSON == nil {
1708
+ logf (log .LevelInfo , "Using Docker config at %s" , path )
1709
+
1710
+ dockerConfigJSON , err = readFile (bfs , path )
1711
+ if err != nil {
1712
+ return nil , fmt .Errorf ("read docker config: %w" , err )
1713
+ }
1714
+ } else {
1715
+ logf (log .LevelWarn , "Ignoring Docker config at %s, using base64 encoded Docker config instead" , path )
1716
+ }
1717
+ break
1718
+ }
1719
+
1720
+ if dockerConfigJSON == nil {
1721
+ // No user-provided config available.
1722
+ return func () error { return nil }, nil
1723
+ }
1724
+
1725
+ dockerConfigJSON , err = hujson .Standardize (dockerConfigJSON )
1726
+ if err != nil {
1727
+ return nil , fmt .Errorf ("humanize json for docker config: %w" , err )
1635
1728
}
1636
- cfgPath := workingDir .Join ("config.json" )
1637
- decoded , err := base64 .StdEncoding .DecodeString (dockerConfigBase64 )
1729
+
1730
+ if err = logDockerAuthConfigs (logf , dockerConfigJSON ); err != nil {
1731
+ return nil , fmt .Errorf ("log docker auth configs: %w" , err )
1732
+ }
1733
+
1734
+ // We're going to set the DOCKER_CONFIG environment variable to a
1735
+ // path within the working directory so that Kaniko can pick it up.
1736
+ // A user should not mount a file directly to this path as we will
1737
+ // write to the file.
1738
+ newDockerConfig := workingDir .Join (".docker" )
1739
+ newDockerConfigFile := filepath .Join (newDockerConfig , dockerConfigFile )
1740
+ err = bfs .MkdirAll (newDockerConfig , 0o700 )
1741
+ if err != nil {
1742
+ return nil , fmt .Errorf ("create docker config dir: %w" , err )
1743
+ }
1744
+
1745
+ if fileExists (bfs , newDockerConfigFile ) {
1746
+ return nil , fmt .Errorf ("unable to write Docker config file, file already exists: %s" , newDockerConfigFile )
1747
+ }
1748
+
1749
+ restoreEnv , err := setAndRestoreEnv (logf , dockerConfigEnvKey , newDockerConfig )
1638
1750
if err != nil {
1639
- return noop , fmt .Errorf ("decode docker config: %w" , err )
1751
+ return nil , fmt .Errorf ("set docker config override : %w" , err )
1640
1752
}
1641
- var configFile DockerConfig
1642
- decoded , err = hujson . Standardize ( decoded )
1753
+
1754
+ err = writeFile ( bfs , newDockerConfigFile , dockerConfigJSON , 0o600 )
1643
1755
if err != nil {
1644
- return noop , fmt .Errorf ("humanize json for docker config: %w" , err )
1756
+ _ = restoreEnv () // Best effort.
1757
+ return nil , fmt .Errorf ("write docker config: %w" , err )
1645
1758
}
1646
- err = json .Unmarshal (decoded , & configFile )
1759
+ logf (log .LevelInfo , "Wrote Docker config JSON to %s" , newDockerConfigFile )
1760
+
1761
+ cleanupFile := onceErrFunc (func () error {
1762
+ // Remove the Docker config secret file!
1763
+ if err := bfs .Remove (newDockerConfigFile ); err != nil {
1764
+ logf (log .LevelError , "Failed to remove the Docker config secret file: %s" , err )
1765
+ return fmt .Errorf ("remove docker config: %w" , err )
1766
+ }
1767
+ return nil
1768
+ })
1769
+ return func () error { return errors .Join (cleanupFile (), restoreEnv ()) }, nil
1770
+ }
1771
+
1772
+ func logDockerAuthConfigs (logf log.Func , dockerConfigJSON []byte ) error {
1773
+ dc := new (DockerConfig )
1774
+ err := dc .LoadFromReader (bytes .NewReader (dockerConfigJSON ))
1647
1775
if err != nil {
1648
- return noop , fmt .Errorf ("parse docker config: %w" , err )
1776
+ return fmt .Errorf ("load docker config: %w" , err )
1649
1777
}
1650
- for k := range configFile .AuthConfigs {
1778
+ for k := range dc .AuthConfigs {
1651
1779
logf (log .LevelInfo , "Docker config contains auth for registry %q" , k )
1652
1780
}
1653
- err = os .WriteFile (cfgPath , decoded , 0o644 )
1781
+ return nil
1782
+ }
1783
+
1784
+ func setAndRestoreEnv (logf log.Func , key , value string ) (restore func () error , err error ) {
1785
+ old := os .Getenv (key )
1786
+ err = os .Setenv (key , value )
1654
1787
if err != nil {
1655
- return noop , fmt .Errorf ("write docker config: %w" , err )
1656
- }
1657
- logf (log .LevelInfo , "Wrote Docker config JSON to %s" , cfgPath )
1658
- oldDockerConfig := os .Getenv ("DOCKER_CONFIG" )
1659
- _ = os .Setenv ("DOCKER_CONFIG" , workingDir .Path ())
1660
- newDockerConfig := os .Getenv ("DOCKER_CONFIG" )
1661
- logf (log .LevelInfo , "Set DOCKER_CONFIG to %s" , newDockerConfig )
1662
- cleanup := func () error {
1663
- var cleanupErr error
1664
- cleanupOnce .Do (func () {
1665
- // Restore the old DOCKER_CONFIG value.
1666
- os .Setenv ("DOCKER_CONFIG" , oldDockerConfig )
1667
- logf (log .LevelInfo , "Restored DOCKER_CONFIG to %s" , oldDockerConfig )
1668
- // Remove the Docker config secret file!
1669
- if cleanupErr = os .Remove (cfgPath ); err != nil {
1670
- if ! errors .Is (err , fs .ErrNotExist ) {
1671
- cleanupErr = fmt .Errorf ("remove docker config: %w" , cleanupErr )
1672
- }
1673
- logf (log .LevelError , "Failed to remove the Docker config secret file: %s" , cleanupErr )
1788
+ logf (log .LevelError , "Failed to set %s: %s" , key , err )
1789
+ return nil , fmt .Errorf ("set %s: %w" , key , err )
1790
+ }
1791
+ logf (log .LevelInfo , "Set %s to %s" , key , value )
1792
+ return onceErrFunc (func () error {
1793
+ if err := func () error {
1794
+ if old == "" {
1795
+ return os .Unsetenv (key )
1674
1796
}
1797
+ return os .Setenv (key , old )
1798
+ }(); err != nil {
1799
+ return fmt .Errorf ("restore %s: %w" , key , err )
1800
+ }
1801
+ logf (log .LevelInfo , "Restored %s to %s" , key , old )
1802
+ return nil
1803
+ }), nil
1804
+ }
1805
+
1806
+ func onceErrFunc (f func () error ) func () error {
1807
+ var once sync.Once
1808
+ return func () error {
1809
+ var err error
1810
+ once .Do (func () {
1811
+ err = f ()
1675
1812
})
1676
- return cleanupErr
1813
+ return err
1677
1814
}
1678
- return cleanup , err
1679
1815
}
1680
1816
1681
1817
// Allows quick testing of layer caching using a local directory!
0 commit comments