Skip to content

Commit 0af0d51

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 9207469 commit 0af0d51

4 files changed

Lines changed: 12 additions & 235 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) {
@@ -141,14 +131,10 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
141131

142132
// Can't find latest config file so check for the old one
143133
filename = filepath.Join(getHomeDir(), oldConfigfile)
144-
if file, err := os.Open(filename); err == nil {
134+
if _, err := os.Stat(filename); err == nil {
145135
mutex.Lock()
146136
printLegacyFileWarning = true
147137
mutex.Unlock()
148-
defer file.Close()
149-
if err := configFile.LegacyLoadFromReader(file); err != nil {
150-
return configFile, errors.Wrap(err, filename)
151-
}
152138
}
153139
return configFile, nil
154140
}
@@ -163,7 +149,7 @@ func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
163149
mutex.RLock()
164150
defer mutex.RUnlock()
165151
if printLegacyFileWarning {
166-
_, _ = fmt.Fprintln(stderr, "WARNING: The legacy ~/.dockercfg configuration file and file-format are deprecated and will be removed in an upcoming release")
152+
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format have been removed. The configuration file will be ignored.")
167153
}
168154
if !configFile.ContainsAuth() {
169155
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)

cli/config/config_test.go

Lines changed: 2 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -108,123 +108,6 @@ func TestEmptyJSON(t *testing.T) {
108108
saveConfigAndValidateNewFormat(t, config, tmpHome)
109109
}
110110

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

@@ -450,17 +326,6 @@ func TestJSONReaderNoFile(t *testing.T) {
450326
assert.Equal(t, ac.Password, "hello")
451327
}
452328

453-
func TestOldJSONReaderNoFile(t *testing.T) {
454-
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
455-
456-
config, err := LegacyLoadFromReader(strings.NewReader(js))
457-
assert.NilError(t, err)
458-
459-
ac := config.AuthConfigs["https://index.docker.io/v1/"]
460-
assert.Equal(t, ac.Username, "joejoe")
461-
assert.Equal(t, ac.Password, "hello")
462-
}
463-
464329
func TestJSONWithPsFormatNoFile(t *testing.T) {
465330
js := `{
466331
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } },
@@ -508,39 +373,6 @@ func TestJSONSaveWithNoFile(t *testing.T) {
508373
}
509374
}
510375

511-
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
512-
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
513-
config, err := LegacyLoadFromReader(strings.NewReader(js))
514-
assert.NilError(t, err)
515-
err = config.Save()
516-
assert.ErrorContains(t, err, "with empty filename")
517-
518-
tmpHome, err := ioutil.TempDir("", "config-test")
519-
assert.NilError(t, err)
520-
defer os.RemoveAll(tmpHome)
521-
522-
fn := filepath.Join(tmpHome, ConfigFileName)
523-
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
524-
defer f.Close()
525-
526-
assert.NilError(t, config.SaveToWriter(f))
527-
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
528-
assert.NilError(t, err)
529-
530-
expConfStr := `{
531-
"auths": {
532-
"https://index.docker.io/v1/": {
533-
"auth": "am9lam9lOmhlbGxv",
534-
"email": "[email protected]"
535-
}
536-
}
537-
}`
538-
539-
if string(buf) != expConfStr {
540-
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
541-
}
542-
}
543-
544376
func TestLoadDefaultConfigFile(t *testing.T) {
545377
dir, cleanup := setupConfigDir(t)
546378
defer cleanup()

cli/config/configfile/file.go

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

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

81-
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
82-
// auth config information with given directory and populates the receiver object
83-
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
84-
b, err := ioutil.ReadAll(configData)
85-
if err != nil {
86-
return err
87-
}
88-
89-
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
90-
arr := strings.Split(string(b), "\n")
91-
if len(arr) < 2 {
92-
return errors.Errorf("The Auth config file is empty")
93-
}
94-
authConfig := types.AuthConfig{}
95-
origAuth := strings.Split(arr[0], " = ")
96-
if len(origAuth) != 2 {
97-
return errors.Errorf("Invalid Auth config file")
98-
}
99-
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
100-
if err != nil {
101-
return err
102-
}
103-
authConfig.ServerAddress = defaultIndexServer
104-
configFile.AuthConfigs[defaultIndexServer] = authConfig
105-
} else {
106-
for k, authConfig := range configFile.AuthConfigs {
107-
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
108-
if err != nil {
109-
return err
110-
}
111-
authConfig.Auth = ""
112-
authConfig.ServerAddress = k
113-
configFile.AuthConfigs[k] = authConfig
114-
}
115-
}
116-
return nil
117-
}
118-
11974
// LoadFromReader reads the configuration data given and sets up the auth config
12075
// information with given directory and populates the receiver object
12176
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
@@ -55,7 +55,7 @@ Deprecated | [Linux containers on Windows (LCOW)](#linux-containers-on-windows-l
5555
Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options–with-cgroups-v1) | v20.10 | -
5656
Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | -
5757
Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | -
58-
Deprecated | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | -
58+
Removed | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | v21.xx
5959
Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | -
6060
Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | -
6161
Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10
@@ -171,6 +171,7 @@ deprecated, and will be disabled or removed in a future release.
171171
### Support for legacy `~/.dockercfg` configuration files
172172

173173
**Deprecated in Release: v20.10**
174+
**Removed in Release: v21.xx**
174175

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

181182
Given that the old file format encourages insecure storage of credentials
182183
(credentials are stored unencrypted), and that no version of the CLI since
183-
Docker v1.7.0 has created this file, the file is marked deprecated, and support
184-
for this file will be removed in a future release.
184+
Docker v1.7.0 has created this file, support for this file, and its format has
185+
been removed.
186+
187+
A warning is printed in situations where the CLI would fall back to the old file,
188+
notifying the user that the legacy file is present, but ignored.
185189

186190
### Configuration options for experimental CLI features
187191

0 commit comments

Comments
 (0)