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