@@ -41,6 +41,7 @@ import (
4141 "github.com/distribution/distribution/v3/configuration"
4242 "github.com/distribution/distribution/v3/registry/handlers"
4343 _ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
44+ dockerconfig "github.com/docker/cli/cli/config"
4445 "github.com/docker/cli/cli/config/configfile"
4546 "github.com/fatih/color"
4647 v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -56,7 +57,7 @@ import (
5657var ErrNoFallbackImage = errors .New ("no fallback image has been specified" )
5758
5859// DockerConfig represents the Docker configuration file.
59- type DockerConfig configfile.ConfigFile
60+ type DockerConfig = configfile.ConfigFile
6061
6162type runtimeDataStore struct {
6263 // Runtime data.
@@ -154,13 +155,13 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
154155
155156 opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
156157
157- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
158+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
158159 if err != nil {
159160 return err
160161 }
161162 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 )
164165 }
165166 }() // best effort
166167
@@ -717,6 +718,11 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
717718 // Sanitize the environment of any opts!
718719 options .UnsetEnv ()
719720
721+ // Remove the Docker config secret file!
722+ if err := cleanupDockerConfigOverride (); err != nil {
723+ return err
724+ }
725+
720726 // Set the environment from /etc/environment first, so it can be
721727 // overridden by the image and devcontainer settings.
722728 err = setEnvFromEtcEnvironment (opts .Logger )
@@ -776,11 +782,6 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
776782 exportEnvFile .Close ()
777783 }
778784
779- // Remove the Docker config secret file!
780- if err := cleanupDockerConfigJSON (); err != nil {
781- return err
782- }
783-
784785 if runtimeData .ContainerUser == "" {
785786 opts .Logger (log .LevelWarn , "#%d: no user specified, using root" , stageNumber )
786787 }
@@ -984,13 +985,13 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
984985
985986 opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
986987
987- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
988+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
988989 if err != nil {
989990 return nil , err
990991 }
991992 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 )
994995 }
995996 }() // best effort
996997
@@ -1321,7 +1322,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
13211322 options .UnsetEnv ()
13221323
13231324 // Remove the Docker config secret file!
1324- if err := cleanupDockerConfigJSON (); err != nil {
1325+ if err := cleanupDockerConfigOverride (); err != nil {
13251326 return nil , err
13261327 }
13271328
@@ -1573,8 +1574,22 @@ func maybeDeleteFilesystem(logger log.Func, force bool) error {
15731574}
15741575
15751576func 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
15781593}
15791594
15801595func 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 {
16011616 return nil
16021617}
16031618
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+
16041634func writeMagicImageFile (fs billy.Filesystem , path string , v any ) error {
16051635 file , err := fs .OpenFile (path , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0o644 )
16061636 if err != nil {
@@ -1633,55 +1663,161 @@ func parseMagicImageFile(fs billy.Filesystem, path string, v any) error {
16331663 return nil
16341664}
16351665
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 )
16411734 }
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 )
16441756 if err != nil {
1645- return noop , fmt .Errorf ("decode docker config: %w" , err )
1757+ return nil , fmt .Errorf ("set docker config override : %w" , err )
16461758 }
1647- var configFile DockerConfig
1648- decoded , err = hujson . Standardize ( decoded )
1759+
1760+ err = writeFile ( bfs , newDockerConfigFile , dockerConfigJSON , 0o600 )
16491761 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 )
16511764 }
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 ))
16531781 if err != nil {
1654- return noop , fmt .Errorf ("parse docker config: %w" , err )
1782+ return fmt .Errorf ("load docker config: %w" , err )
16551783 }
1656- for k := range configFile .AuthConfigs {
1784+ for k := range dc .AuthConfigs {
16571785 logf (log .LevelInfo , "Docker config contains auth for registry %q" , k )
16581786 }
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 )
16601793 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 )
16801802 }
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 ()
16811818 })
1682- return cleanupErr
1819+ return err
16831820 }
1684- return cleanup , err
16851821}
16861822
16871823// Allows quick testing of layer caching using a local directory!
0 commit comments