@@ -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
@@ -711,6 +712,11 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
711712 // Sanitize the environment of any opts!
712713 options .UnsetEnv ()
713714
715+ // Remove the Docker config secret file!
716+ if err := cleanupDockerConfigOverride (); err != nil {
717+ return err
718+ }
719+
714720 // Set the environment from /etc/environment first, so it can be
715721 // overridden by the image and devcontainer settings.
716722 err = setEnvFromEtcEnvironment (opts .Logger )
@@ -770,11 +776,6 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
770776 exportEnvFile .Close ()
771777 }
772778
773- // Remove the Docker config secret file!
774- if err := cleanupDockerConfigJSON (); err != nil {
775- return err
776- }
777-
778779 if runtimeData .ContainerUser == "" {
779780 opts .Logger (log .LevelWarn , "#%d: no user specified, using root" , stageNumber )
780781 }
@@ -978,13 +979,13 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
978979
979980 opts .Logger (log .LevelInfo , "%s %s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ), buildinfo .Version ())
980981
981- cleanupDockerConfigJSON , err := initDockerConfigJSON ( opts .Logger , workingDir , opts .DockerConfigBase64 )
982+ cleanupDockerConfigOverride , err := initDockerConfigOverride ( opts . Filesystem , opts .Logger , workingDir , opts .DockerConfigBase64 )
982983 if err != nil {
983984 return nil , err
984985 }
985986 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 )
988989 }
989990 }() // best effort
990991
@@ -1315,7 +1316,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
13151316 options .UnsetEnv ()
13161317
13171318 // Remove the Docker config secret file!
1318- if err := cleanupDockerConfigJSON (); err != nil {
1319+ if err := cleanupDockerConfigOverride (); err != nil {
13191320 return nil , err
13201321 }
13211322
@@ -1567,8 +1568,22 @@ func maybeDeleteFilesystem(logger log.Func, force bool) error {
15671568}
15681569
15691570func 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
15721587}
15731588
15741589func 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 {
15951610 return nil
15961611}
15971612
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+
15981628func writeMagicImageFile (fs billy.Filesystem , path string , v any ) error {
15991629 file , err := fs .OpenFile (path , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0o644 )
16001630 if err != nil {
@@ -1627,55 +1657,161 @@ func parseMagicImageFile(fs billy.Filesystem, path string, v any) error {
16271657 return nil
16281658}
16291659
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 )
16351728 }
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 )
16381750 if err != nil {
1639- return noop , fmt .Errorf ("decode docker config: %w" , err )
1751+ return nil , fmt .Errorf ("set docker config override : %w" , err )
16401752 }
1641- var configFile DockerConfig
1642- decoded , err = hujson . Standardize ( decoded )
1753+
1754+ err = writeFile ( bfs , newDockerConfigFile , dockerConfigJSON , 0o600 )
16431755 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 )
16451758 }
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 ))
16471775 if err != nil {
1648- return noop , fmt .Errorf ("parse docker config: %w" , err )
1776+ return fmt .Errorf ("load docker config: %w" , err )
16491777 }
1650- for k := range configFile .AuthConfigs {
1778+ for k := range dc .AuthConfigs {
16511779 logf (log .LevelInfo , "Docker config contains auth for registry %q" , k )
16521780 }
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 )
16541787 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 )
16741796 }
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 ()
16751812 })
1676- return cleanupErr
1813+ return err
16771814 }
1678- return cleanup , err
16791815}
16801816
16811817// Allows quick testing of layer caching using a local directory!
0 commit comments