@@ -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
@@ -719,6 +720,11 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
719
720
// Sanitize the environment of any opts!
720
721
options .UnsetEnv ()
721
722
723
+ // Remove the Docker config secret file!
724
+ if err := cleanupDockerConfigOverride (); err != nil {
725
+ return err
726
+ }
727
+
722
728
// Set the environment from /etc/environment first, so it can be
723
729
// overridden by the image and devcontainer settings.
724
730
err = setEnvFromEtcEnvironment (opts .Logger )
@@ -778,11 +784,6 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
778
784
exportEnvFile .Close ()
779
785
}
780
786
781
- // Remove the Docker config secret file!
782
- if err := cleanupDockerConfigJSON (); err != nil {
783
- return err
784
- }
785
-
786
787
if runtimeData .ContainerUser == "" {
787
788
opts .Logger (log .LevelWarn , "#%d: no user specified, using root" , stageNumber )
788
789
}
@@ -986,13 +987,13 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
986
987
987
988
opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
988
989
989
- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
990
+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
990
991
if err != nil {
991
992
return nil , err
992
993
}
993
994
defer func () {
994
- if err := cleanupDockerConfigJSON (); err != nil {
995
- opts .Logger (log .LevelError , "failed to cleanup docker config JSON : %w" , err )
995
+ if err := cleanupDockerConfigOverride (); err != nil {
996
+ opts .Logger (log .LevelError , "failed to cleanup docker config override : %w" , err )
996
997
}
997
998
}() // best effort
998
999
@@ -1323,7 +1324,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
1323
1324
options .UnsetEnv ()
1324
1325
1325
1326
// Remove the Docker config secret file!
1326
- if err := cleanupDockerConfigJSON (); err != nil {
1327
+ if err := cleanupDockerConfigOverride (); err != nil {
1327
1328
return nil , err
1328
1329
}
1329
1330
@@ -1575,8 +1576,22 @@ func maybeDeleteFilesystem(logger log.Func, force bool) error {
1575
1576
}
1576
1577
1577
1578
func fileExists (fs billy.Filesystem , path string ) bool {
1578
- _ , err := fs .Stat (path )
1579
- return err == nil
1579
+ fi , err := fs .Stat (path )
1580
+ return err == nil && ! fi .IsDir ()
1581
+ }
1582
+
1583
+ func readFile (fs billy.Filesystem , name string ) ([]byte , error ) {
1584
+ f , err := fs .Open (name )
1585
+ if err != nil {
1586
+ return nil , fmt .Errorf ("open file: %w" , err )
1587
+ }
1588
+ defer f .Close ()
1589
+
1590
+ b , err := io .ReadAll (f )
1591
+ if err != nil {
1592
+ return nil , fmt .Errorf ("read file: %w" , err )
1593
+ }
1594
+ return b , nil
1580
1595
}
1581
1596
1582
1597
func copyFile (fs billy.Filesystem , src , dst string , mode fs.FileMode ) error {
@@ -1603,6 +1618,21 @@ func copyFile(fs billy.Filesystem, src, dst string, mode fs.FileMode) error {
1603
1618
return nil
1604
1619
}
1605
1620
1621
+ func writeFile (fs billy.Filesystem , name string , data []byte , perm fs.FileMode ) error {
1622
+ f , err := fs .OpenFile (name , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , perm )
1623
+ if err != nil {
1624
+ return fmt .Errorf ("create file: %w" , err )
1625
+ }
1626
+ _ , err = f .Write (data )
1627
+ if err != nil {
1628
+ err = fmt .Errorf ("write file: %w" , err )
1629
+ }
1630
+ if err2 := f .Close (); err2 != nil && err == nil {
1631
+ err = fmt .Errorf ("close file: %w" , err2 )
1632
+ }
1633
+ return err
1634
+ }
1635
+
1606
1636
func writeMagicImageFile (fs billy.Filesystem , path string , v any ) error {
1607
1637
file , err := fs .OpenFile (path , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0o644 )
1608
1638
if err != nil {
@@ -1635,55 +1665,161 @@ func parseMagicImageFile(fs billy.Filesystem, path string, v any) error {
1635
1665
return nil
1636
1666
}
1637
1667
1638
- func initDockerConfigJSON (logf log.Func , workingDir workingdir.WorkingDir , dockerConfigBase64 string ) (func () error , error ) {
1639
- var cleanupOnce sync.Once
1640
- noop := func () error { return nil }
1641
- if dockerConfigBase64 == "" {
1642
- return noop , nil
1668
+ const (
1669
+ dockerConfigFile = dockerconfig .ConfigFileName
1670
+ dockerConfigEnvKey = dockerconfig .EnvOverrideConfigDir
1671
+ )
1672
+
1673
+ // initDockerConfigOverride sets the DOCKER_CONFIG environment variable
1674
+ // to a path within the working directory. If a base64 encoded Docker
1675
+ // config is provided, it is written to the path/config.json and the
1676
+ // DOCKER_CONFIG environment variable is set to the path. If no base64
1677
+ // encoded Docker config is provided, the following paths are checked in
1678
+ // order:
1679
+ //
1680
+ // 1. $DOCKER_CONFIG/config.json
1681
+ // 2. $DOCKER_CONFIG
1682
+ // 3. /.envbuilder/config.json
1683
+ //
1684
+ // If a Docker config file is found, its path is set as DOCKER_CONFIG.
1685
+ func initDockerConfigOverride (bfs billy.Filesystem , logf log.Func , workingDir workingdir.WorkingDir , dockerConfigBase64 string ) (func () error , error ) {
1686
+ // If dockerConfigBase64 is set, it will have priority over file
1687
+ // detection.
1688
+ var dockerConfigJSON []byte
1689
+ var err error
1690
+ if dockerConfigBase64 != "" {
1691
+ logf (log .LevelInfo , "Using base64 encoded Docker config" )
1692
+
1693
+ dockerConfigJSON , err = base64 .StdEncoding .DecodeString (dockerConfigBase64 )
1694
+ if err != nil {
1695
+ return nil , fmt .Errorf ("decode docker config: %w" , err )
1696
+ }
1697
+ }
1698
+
1699
+ oldDockerConfig := os .Getenv (dockerConfigEnvKey )
1700
+ var oldDockerConfigFile string
1701
+ if oldDockerConfig != "" {
1702
+ oldDockerConfigFile = filepath .Join (oldDockerConfig , dockerConfigFile )
1703
+ }
1704
+ for _ , path := range []string {
1705
+ oldDockerConfigFile , // $DOCKER_CONFIG/config.json
1706
+ oldDockerConfig , // $DOCKER_CONFIG
1707
+ workingDir .Join (dockerConfigFile ), // /.envbuilder/config.json
1708
+ } {
1709
+ if path == "" || ! fileExists (bfs , path ) {
1710
+ continue
1711
+ }
1712
+
1713
+ logf (log .LevelWarn , "Found Docker config at %s, this file will remain after the build" , path )
1714
+
1715
+ if dockerConfigJSON == nil {
1716
+ logf (log .LevelInfo , "Using Docker config at %s" , path )
1717
+
1718
+ dockerConfigJSON , err = readFile (bfs , path )
1719
+ if err != nil {
1720
+ return nil , fmt .Errorf ("read docker config: %w" , err )
1721
+ }
1722
+ } else {
1723
+ logf (log .LevelWarn , "Ignoring Docker config at %s, using base64 encoded Docker config instead" , path )
1724
+ }
1725
+ break
1726
+ }
1727
+
1728
+ if dockerConfigJSON == nil {
1729
+ // No user-provided config available.
1730
+ return func () error { return nil }, nil
1731
+ }
1732
+
1733
+ dockerConfigJSON , err = hujson .Standardize (dockerConfigJSON )
1734
+ if err != nil {
1735
+ return nil , fmt .Errorf ("humanize json for docker config: %w" , err )
1643
1736
}
1644
- cfgPath := workingDir .Join ("config.json" )
1645
- decoded , err := base64 .StdEncoding .DecodeString (dockerConfigBase64 )
1737
+
1738
+ if err = logDockerAuthConfigs (logf , dockerConfigJSON ); err != nil {
1739
+ return nil , fmt .Errorf ("log docker auth configs: %w" , err )
1740
+ }
1741
+
1742
+ // We're going to set the DOCKER_CONFIG environment variable to a
1743
+ // path within the working directory so that Kaniko can pick it up.
1744
+ // A user should not mount a file directly to this path as we will
1745
+ // write to the file.
1746
+ newDockerConfig := workingDir .Join (".docker" )
1747
+ newDockerConfigFile := filepath .Join (newDockerConfig , dockerConfigFile )
1748
+ err = bfs .MkdirAll (newDockerConfig , 0o700 )
1749
+ if err != nil {
1750
+ return nil , fmt .Errorf ("create docker config dir: %w" , err )
1751
+ }
1752
+
1753
+ if fileExists (bfs , newDockerConfigFile ) {
1754
+ return nil , fmt .Errorf ("unable to write Docker config file, file already exists: %s" , newDockerConfigFile )
1755
+ }
1756
+
1757
+ restoreEnv , err := setAndRestoreEnv (logf , dockerConfigEnvKey , newDockerConfig )
1646
1758
if err != nil {
1647
- return noop , fmt .Errorf ("decode docker config: %w" , err )
1759
+ return nil , fmt .Errorf ("set docker config override : %w" , err )
1648
1760
}
1649
- var configFile DockerConfig
1650
- decoded , err = hujson . Standardize ( decoded )
1761
+
1762
+ err = writeFile ( bfs , newDockerConfigFile , dockerConfigJSON , 0o600 )
1651
1763
if err != nil {
1652
- return noop , fmt .Errorf ("humanize json for docker config: %w" , err )
1764
+ _ = restoreEnv () // Best effort.
1765
+ return nil , fmt .Errorf ("write docker config: %w" , err )
1653
1766
}
1654
- err = json .Unmarshal (decoded , & configFile )
1767
+ logf (log .LevelInfo , "Wrote Docker config JSON to %s" , newDockerConfigFile )
1768
+
1769
+ cleanupFile := onceErrFunc (func () error {
1770
+ // Remove the Docker config secret file!
1771
+ if err := bfs .Remove (newDockerConfigFile ); err != nil {
1772
+ logf (log .LevelError , "Failed to remove the Docker config secret file: %s" , err )
1773
+ return fmt .Errorf ("remove docker config: %w" , err )
1774
+ }
1775
+ return nil
1776
+ })
1777
+ return func () error { return errors .Join (cleanupFile (), restoreEnv ()) }, nil
1778
+ }
1779
+
1780
+ func logDockerAuthConfigs (logf log.Func , dockerConfigJSON []byte ) error {
1781
+ dc := new (DockerConfig )
1782
+ err := dc .LoadFromReader (bytes .NewReader (dockerConfigJSON ))
1655
1783
if err != nil {
1656
- return noop , fmt .Errorf ("parse docker config: %w" , err )
1784
+ return fmt .Errorf ("load docker config: %w" , err )
1657
1785
}
1658
- for k := range configFile .AuthConfigs {
1786
+ for k := range dc .AuthConfigs {
1659
1787
logf (log .LevelInfo , "Docker config contains auth for registry %q" , k )
1660
1788
}
1661
- err = os .WriteFile (cfgPath , decoded , 0o644 )
1789
+ return nil
1790
+ }
1791
+
1792
+ func setAndRestoreEnv (logf log.Func , key , value string ) (restore func () error , err error ) {
1793
+ old := os .Getenv (key )
1794
+ err = os .Setenv (key , value )
1662
1795
if err != nil {
1663
- return noop , fmt .Errorf ("write docker config: %w" , err )
1664
- }
1665
- logf (log .LevelInfo , "Wrote Docker config JSON to %s" , cfgPath )
1666
- oldDockerConfig := os .Getenv ("DOCKER_CONFIG" )
1667
- _ = os .Setenv ("DOCKER_CONFIG" , workingDir .Path ())
1668
- newDockerConfig := os .Getenv ("DOCKER_CONFIG" )
1669
- logf (log .LevelInfo , "Set DOCKER_CONFIG to %s" , newDockerConfig )
1670
- cleanup := func () error {
1671
- var cleanupErr error
1672
- cleanupOnce .Do (func () {
1673
- // Restore the old DOCKER_CONFIG value.
1674
- os .Setenv ("DOCKER_CONFIG" , oldDockerConfig )
1675
- logf (log .LevelInfo , "Restored DOCKER_CONFIG to %s" , oldDockerConfig )
1676
- // Remove the Docker config secret file!
1677
- if cleanupErr = os .Remove (cfgPath ); err != nil {
1678
- if ! errors .Is (err , fs .ErrNotExist ) {
1679
- cleanupErr = fmt .Errorf ("remove docker config: %w" , cleanupErr )
1680
- }
1681
- logf (log .LevelError , "Failed to remove the Docker config secret file: %s" , cleanupErr )
1796
+ logf (log .LevelError , "Failed to set %s: %s" , key , err )
1797
+ return nil , fmt .Errorf ("set %s: %w" , key , err )
1798
+ }
1799
+ logf (log .LevelInfo , "Set %s to %s" , key , value )
1800
+ return onceErrFunc (func () error {
1801
+ if err := func () error {
1802
+ if old == "" {
1803
+ return os .Unsetenv (key )
1682
1804
}
1805
+ return os .Setenv (key , old )
1806
+ }(); err != nil {
1807
+ return fmt .Errorf ("restore %s: %w" , key , err )
1808
+ }
1809
+ logf (log .LevelInfo , "Restored %s to %s" , key , old )
1810
+ return nil
1811
+ }), nil
1812
+ }
1813
+
1814
+ func onceErrFunc (f func () error ) func () error {
1815
+ var once sync.Once
1816
+ return func () error {
1817
+ var err error
1818
+ once .Do (func () {
1819
+ err = f ()
1683
1820
})
1684
- return cleanupErr
1821
+ return err
1685
1822
}
1686
- return cleanup , err
1687
1823
}
1688
1824
1689
1825
// Allows quick testing of layer caching using a local directory!
0 commit comments