Skip to content

Commit ee218fa

Browse files
committed
Deprecation: config: remove support for old ~/.dockercfg
The `~/.dockercfg` file was replaced by `~/.docker/config.json` in 2015 (github.com/docker/docker/commit/18c9b6c6455f116ae59cde8544413b3d7d294a5e), but the CLI still falls back to checking if this file exists if no current (`~/.docker/config.json`) file was found. Given that no version of the CLI since Docker v1.7.0 has created this file, and if such a file exists, it means someone hasn't re-authenticated for 5 years, it's probably safe to remove this fallback. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 59449a5 commit ee218fa

4 files changed

Lines changed: 12 additions & 224 deletions

File tree

cli/config/config.go

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const (
1919
// ConfigFileName is the name of config file
2020
ConfigFileName = "config.json"
2121
configFileDir = ".docker"
22-
oldConfigfile = ".dockercfg"
22+
oldConfigfile = ".dockercfg" // Deprecated: remove once we stop printing deprecation warning
2323
contextsDir = "contexts"
2424
)
2525

@@ -84,16 +84,6 @@ func Path(p ...string) (string, error) {
8484
return path, nil
8585
}
8686

87-
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
88-
// a non-nested reader
89-
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
90-
configFile := configfile.ConfigFile{
91-
AuthConfigs: make(map[string]types.AuthConfig),
92-
}
93-
err := configFile.LegacyLoadFromReader(configData)
94-
return &configFile, err
95-
}
96-
9787
// LoadFromReader is a convenience function that creates a ConfigFile object from
9888
// a reader
9989
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
@@ -140,12 +130,8 @@ func load(configDir string) (*configfile.ConfigFile, bool, error) {
140130

141131
// Can't find latest config file so check for the old one
142132
filename = filepath.Join(getHomeDir(), oldConfigfile)
143-
if file, err := os.Open(filename); err == nil {
133+
if _, err := os.Stat(filename); err == nil {
144134
printLegacyFileWarning = true
145-
defer file.Close()
146-
if err := configFile.LegacyLoadFromReader(file); err != nil {
147-
return configFile, printLegacyFileWarning, errors.Wrap(err, filename)
148-
}
149135
}
150136
return configFile, printLegacyFileWarning, nil
151137
}
@@ -158,7 +144,7 @@ func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
158144
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
159145
}
160146
if printLegacyFileWarning {
161-
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release")
147+
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored")
162148
}
163149
if !configFile.ContainsAuth() {
164150
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)

cli/config/config_test.go

Lines changed: 2 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -96,115 +96,6 @@ func TestEmptyJSON(t *testing.T) {
9696
saveConfigAndValidateNewFormat(t, config, tmpHome)
9797
}
9898

99-
func TestOldInvalidsAuth(t *testing.T) {
100-
invalids := map[string]string{
101-
`username = test`: "The Auth config file is empty",
102-
`username
103-
password`: "Invalid Auth config file",
104-
`username = test
105-
email`: "Invalid auth configuration file",
106-
}
107-
108-
resetHomeDir()
109-
tmpHome := t.TempDir()
110-
defer env.Patch(t, homeKey, tmpHome)()
111-
112-
for content, expectedError := range invalids {
113-
fn := filepath.Join(tmpHome, oldConfigfile)
114-
err := os.WriteFile(fn, []byte(content), 0600)
115-
assert.NilError(t, err)
116-
117-
_, err = Load(tmpHome)
118-
assert.ErrorContains(t, err, expectedError)
119-
}
120-
}
121-
122-
func TestOldValidAuth(t *testing.T) {
123-
resetHomeDir()
124-
tmpHome := t.TempDir()
125-
defer env.Patch(t, homeKey, tmpHome)()
126-
127-
fn := filepath.Join(tmpHome, oldConfigfile)
128-
js := `username = am9lam9lOmhlbGxv
129-
130-
err := os.WriteFile(fn, []byte(js), 0600)
131-
assert.NilError(t, err)
132-
133-
config, err := Load(tmpHome)
134-
assert.NilError(t, err)
135-
136-
// defaultIndexserver is https://index.docker.io/v1/
137-
ac := config.AuthConfigs["https://index.docker.io/v1/"]
138-
assert.Equal(t, ac.Username, "joejoe")
139-
assert.Equal(t, ac.Password, "hello")
140-
141-
// Now save it and make sure it shows up in new form
142-
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
143-
144-
expConfStr := `{
145-
"auths": {
146-
"https://index.docker.io/v1/": {
147-
"auth": "am9lam9lOmhlbGxv"
148-
}
149-
}
150-
}`
151-
152-
assert.Check(t, is.Equal(expConfStr, configStr))
153-
}
154-
155-
func TestOldJSONInvalid(t *testing.T) {
156-
resetHomeDir()
157-
tmpHome := t.TempDir()
158-
defer env.Patch(t, homeKey, tmpHome)()
159-
160-
fn := filepath.Join(tmpHome, oldConfigfile)
161-
js := `{"https://index.docker.io/v1/":{"auth":"test","email":"[email protected]"}}`
162-
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
163-
t.Fatal(err)
164-
}
165-
166-
config, err := Load(tmpHome)
167-
// Use Contains instead of == since the file name will change each time
168-
if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
169-
t.Fatalf("Expected an error got : %v, %v", config, err)
170-
}
171-
}
172-
173-
func TestOldJSON(t *testing.T) {
174-
resetHomeDir()
175-
tmpHome := t.TempDir()
176-
defer env.Patch(t, homeKey, tmpHome)()
177-
178-
fn := filepath.Join(tmpHome, oldConfigfile)
179-
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
180-
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
181-
t.Fatal(err)
182-
}
183-
184-
config, err := Load(tmpHome)
185-
assert.NilError(t, err)
186-
187-
ac := config.AuthConfigs["https://index.docker.io/v1/"]
188-
assert.Equal(t, ac.Username, "joejoe")
189-
assert.Equal(t, ac.Password, "hello")
190-
191-
// Now save it and make sure it shows up in new form
192-
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
193-
194-
expConfStr := `{
195-
"auths": {
196-
"https://index.docker.io/v1/": {
197-
"auth": "am9lam9lOmhlbGxv",
198-
"email": "[email protected]"
199-
}
200-
}
201-
}`
202-
203-
if configStr != expConfStr {
204-
t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
205-
}
206-
}
207-
20899
func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
209100
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
210101
tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
@@ -218,15 +109,8 @@ func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
218109
buffer := new(bytes.Buffer)
219110
configFile := LoadDefaultConfigFile(buffer)
220111
expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
221-
expected.AuthConfigs = map[string]types.AuthConfig{
222-
"https://index.docker.io/v1/": {
223-
Username: "joejoe",
224-
Password: "hello",
225-
226-
ServerAddress: "https://index.docker.io/v1/",
227-
},
228-
}
229-
assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release"))
112+
expected.AuthConfigs = map[string]types.AuthConfig{}
113+
assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored"))
230114
assert.Check(t, is.DeepEqual(expected, configFile))
231115
}
232116

@@ -418,17 +302,6 @@ func TestJSONReaderNoFile(t *testing.T) {
418302
assert.Equal(t, ac.Password, "hello")
419303
}
420304

421-
func TestOldJSONReaderNoFile(t *testing.T) {
422-
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
423-
424-
config, err := LegacyLoadFromReader(strings.NewReader(js))
425-
assert.NilError(t, err)
426-
427-
ac := config.AuthConfigs["https://index.docker.io/v1/"]
428-
assert.Equal(t, ac.Username, "joejoe")
429-
assert.Equal(t, ac.Password, "hello")
430-
}
431-
432305
func TestJSONWithPsFormatNoFile(t *testing.T) {
433306
js := `{
434307
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } },
@@ -474,36 +347,6 @@ func TestJSONSaveWithNoFile(t *testing.T) {
474347
}
475348
}
476349

477-
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
478-
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
479-
config, err := LegacyLoadFromReader(strings.NewReader(js))
480-
assert.NilError(t, err)
481-
err = config.Save()
482-
assert.ErrorContains(t, err, "with empty filename")
483-
484-
tmpHome := t.TempDir()
485-
fn := filepath.Join(tmpHome, ConfigFileName)
486-
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
487-
defer f.Close()
488-
489-
assert.NilError(t, config.SaveToWriter(f))
490-
buf, err := os.ReadFile(filepath.Join(tmpHome, ConfigFileName))
491-
assert.NilError(t, err)
492-
493-
expConfStr := `{
494-
"auths": {
495-
"https://index.docker.io/v1/": {
496-
"auth": "am9lam9lOmhlbGxv",
497-
"email": "[email protected]"
498-
}
499-
}
500-
}`
501-
502-
if string(buf) != expConfStr {
503-
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
504-
}
505-
}
506-
507350
func TestLoadDefaultConfigFile(t *testing.T) {
508351
dir := setupConfigDir(t)
509352
buffer := new(bytes.Buffer)

cli/config/configfile/file.go

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ import (
1414
"github.com/sirupsen/logrus"
1515
)
1616

17-
const (
18-
// This constant is only used for really old config files when the
19-
// URL wasn't saved as part of the config file and it was just
20-
// assumed to be this value.
21-
defaultIndexServer = "https://index.docker.io/v1/"
22-
)
23-
2417
// ConfigFile ~/.docker/config.json file info
2518
type ConfigFile struct {
2619
AuthConfigs map[string]types.AuthConfig `json:"auths"`
@@ -71,44 +64,6 @@ func New(fn string) *ConfigFile {
7164
}
7265
}
7366

74-
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
75-
// auth config information with given directory and populates the receiver object
76-
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
77-
b, err := io.ReadAll(configData)
78-
if err != nil {
79-
return err
80-
}
81-
82-
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
83-
arr := strings.Split(string(b), "\n")
84-
if len(arr) < 2 {
85-
return errors.Errorf("The Auth config file is empty")
86-
}
87-
authConfig := types.AuthConfig{}
88-
origAuth := strings.Split(arr[0], " = ")
89-
if len(origAuth) != 2 {
90-
return errors.Errorf("Invalid Auth config file")
91-
}
92-
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
93-
if err != nil {
94-
return err
95-
}
96-
authConfig.ServerAddress = defaultIndexServer
97-
configFile.AuthConfigs[defaultIndexServer] = authConfig
98-
} else {
99-
for k, authConfig := range configFile.AuthConfigs {
100-
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
101-
if err != nil {
102-
return err
103-
}
104-
authConfig.Auth = ""
105-
authConfig.ServerAddress = k
106-
configFile.AuthConfigs[k] = authConfig
107-
}
108-
}
109-
return nil
110-
}
111-
11267
// LoadFromReader reads the configuration data given and sets up the auth config
11368
// information with given directory and populates the receiver object
11469
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {

docs/deprecated.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Removed | [Linux containers on Windows (LCOW)](#linux-containers-on-windows-l
5959
Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options-with-cgroups-v1) | v20.10 | -
6060
Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | -
6161
Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | -
62-
Deprecated | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | -
62+
Removed | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | v21.xx
6363
Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | -
6464
Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | -
6565
Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10
@@ -298,6 +298,7 @@ deprecated, and will be disabled or removed in a future release.
298298
### Support for legacy `~/.dockercfg` configuration files
299299

300300
**Deprecated in Release: v20.10**
301+
**Removed in Release: v21.xx**
301302

302303
The docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials
303304
after authenticating to a registry (`docker login`). Docker v1.7.0 replaced this
@@ -307,8 +308,11 @@ as a fall-back, to assist existing users with migrating to the new file.
307308

308309
Given that the old file format encourages insecure storage of credentials
309310
(credentials are stored unencrypted), and that no version of the CLI since
310-
Docker v1.7.0 has created this file, the file is marked deprecated, and support
311-
for this file will be removed in a future release.
311+
Docker v1.7.0 has created this file, support for this file, and its format has
312+
been removed.
313+
314+
A warning is printed in situations where the CLI would fall back to the old file,
315+
notifying the user that the legacy file is present, but ignored.
312316

313317
### Configuration options for experimental CLI features
314318

0 commit comments

Comments
 (0)