mirror of https://github.com/docker/cli.git
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 <github@gone.nl>
This commit is contained in:
parent
59449a57f8
commit
ee218fa89e
|
@ -19,7 +19,7 @@ const (
|
||||||
// ConfigFileName is the name of config file
|
// ConfigFileName is the name of config file
|
||||||
ConfigFileName = "config.json"
|
ConfigFileName = "config.json"
|
||||||
configFileDir = ".docker"
|
configFileDir = ".docker"
|
||||||
oldConfigfile = ".dockercfg"
|
oldConfigfile = ".dockercfg" // Deprecated: remove once we stop printing deprecation warning
|
||||||
contextsDir = "contexts"
|
contextsDir = "contexts"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,16 +84,6 @@ func Path(p ...string) (string, error) {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
|
|
||||||
// a non-nested reader
|
|
||||||
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
||||||
configFile := configfile.ConfigFile{
|
|
||||||
AuthConfigs: make(map[string]types.AuthConfig),
|
|
||||||
}
|
|
||||||
err := configFile.LegacyLoadFromReader(configData)
|
|
||||||
return &configFile, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
||||||
// a reader
|
// a reader
|
||||||
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||||
|
@ -140,12 +130,8 @@ func load(configDir string) (*configfile.ConfigFile, bool, error) {
|
||||||
|
|
||||||
// Can't find latest config file so check for the old one
|
// Can't find latest config file so check for the old one
|
||||||
filename = filepath.Join(getHomeDir(), oldConfigfile)
|
filename = filepath.Join(getHomeDir(), oldConfigfile)
|
||||||
if file, err := os.Open(filename); err == nil {
|
if _, err := os.Stat(filename); err == nil {
|
||||||
printLegacyFileWarning = true
|
printLegacyFileWarning = true
|
||||||
defer file.Close()
|
|
||||||
if err := configFile.LegacyLoadFromReader(file); err != nil {
|
|
||||||
return configFile, printLegacyFileWarning, errors.Wrap(err, filename)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return configFile, printLegacyFileWarning, nil
|
return configFile, printLegacyFileWarning, nil
|
||||||
}
|
}
|
||||||
|
@ -158,7 +144,7 @@ func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
|
||||||
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
|
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
|
||||||
}
|
}
|
||||||
if printLegacyFileWarning {
|
if printLegacyFileWarning {
|
||||||
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release")
|
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored")
|
||||||
}
|
}
|
||||||
if !configFile.ContainsAuth() {
|
if !configFile.ContainsAuth() {
|
||||||
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
|
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
|
||||||
|
|
|
@ -96,115 +96,6 @@ func TestEmptyJSON(t *testing.T) {
|
||||||
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOldInvalidsAuth(t *testing.T) {
|
|
||||||
invalids := map[string]string{
|
|
||||||
`username = test`: "The Auth config file is empty",
|
|
||||||
`username
|
|
||||||
password`: "Invalid Auth config file",
|
|
||||||
`username = test
|
|
||||||
email`: "Invalid auth configuration file",
|
|
||||||
}
|
|
||||||
|
|
||||||
resetHomeDir()
|
|
||||||
tmpHome := t.TempDir()
|
|
||||||
defer env.Patch(t, homeKey, tmpHome)()
|
|
||||||
|
|
||||||
for content, expectedError := range invalids {
|
|
||||||
fn := filepath.Join(tmpHome, oldConfigfile)
|
|
||||||
err := os.WriteFile(fn, []byte(content), 0600)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
_, err = Load(tmpHome)
|
|
||||||
assert.ErrorContains(t, err, expectedError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOldValidAuth(t *testing.T) {
|
|
||||||
resetHomeDir()
|
|
||||||
tmpHome := t.TempDir()
|
|
||||||
defer env.Patch(t, homeKey, tmpHome)()
|
|
||||||
|
|
||||||
fn := filepath.Join(tmpHome, oldConfigfile)
|
|
||||||
js := `username = am9lam9lOmhlbGxv
|
|
||||||
email = user@example.com`
|
|
||||||
err := os.WriteFile(fn, []byte(js), 0600)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
config, err := Load(tmpHome)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
// defaultIndexserver is https://index.docker.io/v1/
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
|
||||||
assert.Equal(t, ac.Username, "joejoe")
|
|
||||||
assert.Equal(t, ac.Password, "hello")
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
|
||||||
|
|
||||||
expConfStr := `{
|
|
||||||
"auths": {
|
|
||||||
"https://index.docker.io/v1/": {
|
|
||||||
"auth": "am9lam9lOmhlbGxv"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
assert.Check(t, is.Equal(expConfStr, configStr))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOldJSONInvalid(t *testing.T) {
|
|
||||||
resetHomeDir()
|
|
||||||
tmpHome := t.TempDir()
|
|
||||||
defer env.Patch(t, homeKey, tmpHome)()
|
|
||||||
|
|
||||||
fn := filepath.Join(tmpHome, oldConfigfile)
|
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
|
|
||||||
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := Load(tmpHome)
|
|
||||||
// Use Contains instead of == since the file name will change each time
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
|
|
||||||
t.Fatalf("Expected an error got : %v, %v", config, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOldJSON(t *testing.T) {
|
|
||||||
resetHomeDir()
|
|
||||||
tmpHome := t.TempDir()
|
|
||||||
defer env.Patch(t, homeKey, tmpHome)()
|
|
||||||
|
|
||||||
fn := filepath.Join(tmpHome, oldConfigfile)
|
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
||||||
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := Load(tmpHome)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
|
||||||
assert.Equal(t, ac.Username, "joejoe")
|
|
||||||
assert.Equal(t, ac.Password, "hello")
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
|
||||||
|
|
||||||
expConfStr := `{
|
|
||||||
"auths": {
|
|
||||||
"https://index.docker.io/v1/": {
|
|
||||||
"auth": "am9lam9lOmhlbGxv",
|
|
||||||
"email": "user@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
if configStr != expConfStr {
|
|
||||||
t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
|
func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
|
tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
|
||||||
|
@ -218,15 +109,8 @@ func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
configFile := LoadDefaultConfigFile(buffer)
|
configFile := LoadDefaultConfigFile(buffer)
|
||||||
expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
|
expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
|
||||||
expected.AuthConfigs = map[string]types.AuthConfig{
|
expected.AuthConfigs = map[string]types.AuthConfig{}
|
||||||
"https://index.docker.io/v1/": {
|
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"))
|
||||||
Username: "joejoe",
|
|
||||||
Password: "hello",
|
|
||||||
Email: "user@example.com",
|
|
||||||
ServerAddress: "https://index.docker.io/v1/",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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"))
|
|
||||||
assert.Check(t, is.DeepEqual(expected, configFile))
|
assert.Check(t, is.DeepEqual(expected, configFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,17 +302,6 @@ func TestJSONReaderNoFile(t *testing.T) {
|
||||||
assert.Equal(t, ac.Password, "hello")
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOldJSONReaderNoFile(t *testing.T) {
|
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
||||||
|
|
||||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
|
||||||
assert.Equal(t, ac.Username, "joejoe")
|
|
||||||
assert.Equal(t, ac.Password, "hello")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONWithPsFormatNoFile(t *testing.T) {
|
func TestJSONWithPsFormatNoFile(t *testing.T) {
|
||||||
js := `{
|
js := `{
|
||||||
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
@ -474,36 +347,6 @@ func TestJSONSaveWithNoFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
||||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
|
||||||
assert.NilError(t, err)
|
|
||||||
err = config.Save()
|
|
||||||
assert.ErrorContains(t, err, "with empty filename")
|
|
||||||
|
|
||||||
tmpHome := t.TempDir()
|
|
||||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
|
||||||
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
assert.NilError(t, config.SaveToWriter(f))
|
|
||||||
buf, err := os.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
expConfStr := `{
|
|
||||||
"auths": {
|
|
||||||
"https://index.docker.io/v1/": {
|
|
||||||
"auth": "am9lam9lOmhlbGxv",
|
|
||||||
"email": "user@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
if string(buf) != expConfStr {
|
|
||||||
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadDefaultConfigFile(t *testing.T) {
|
func TestLoadDefaultConfigFile(t *testing.T) {
|
||||||
dir := setupConfigDir(t)
|
dir := setupConfigDir(t)
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
|
@ -14,13 +14,6 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// This constant is only used for really old config files when the
|
|
||||||
// URL wasn't saved as part of the config file and it was just
|
|
||||||
// assumed to be this value.
|
|
||||||
defaultIndexServer = "https://index.docker.io/v1/"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigFile ~/.docker/config.json file info
|
// ConfigFile ~/.docker/config.json file info
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
||||||
|
@ -71,44 +64,6 @@ func New(fn string) *ConfigFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
|
||||||
// auth config information with given directory and populates the receiver object
|
|
||||||
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
|
||||||
b, err := io.ReadAll(configData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
|
||||||
arr := strings.Split(string(b), "\n")
|
|
||||||
if len(arr) < 2 {
|
|
||||||
return errors.Errorf("The Auth config file is empty")
|
|
||||||
}
|
|
||||||
authConfig := types.AuthConfig{}
|
|
||||||
origAuth := strings.Split(arr[0], " = ")
|
|
||||||
if len(origAuth) != 2 {
|
|
||||||
return errors.Errorf("Invalid Auth config file")
|
|
||||||
}
|
|
||||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authConfig.ServerAddress = defaultIndexServer
|
|
||||||
configFile.AuthConfigs[defaultIndexServer] = authConfig
|
|
||||||
} else {
|
|
||||||
for k, authConfig := range configFile.AuthConfigs {
|
|
||||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authConfig.Auth = ""
|
|
||||||
authConfig.ServerAddress = k
|
|
||||||
configFile.AuthConfigs[k] = authConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFromReader reads the configuration data given and sets up the auth config
|
// LoadFromReader reads the configuration data given and sets up the auth config
|
||||||
// information with given directory and populates the receiver object
|
// information with given directory and populates the receiver object
|
||||||
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||||
|
|
|
@ -59,7 +59,7 @@ Removed | [Linux containers on Windows (LCOW)](#linux-containers-on-windows-l
|
||||||
Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options-with-cgroups-v1) | v20.10 | -
|
Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options-with-cgroups-v1) | v20.10 | -
|
||||||
Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | -
|
Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | -
|
||||||
Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | -
|
Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | -
|
||||||
Deprecated | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | -
|
Removed | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | v21.xx
|
||||||
Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | -
|
Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | -
|
||||||
Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | -
|
Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | -
|
||||||
Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10
|
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.
|
||||||
### Support for legacy `~/.dockercfg` configuration files
|
### Support for legacy `~/.dockercfg` configuration files
|
||||||
|
|
||||||
**Deprecated in Release: v20.10**
|
**Deprecated in Release: v20.10**
|
||||||
|
**Removed in Release: v21.xx**
|
||||||
|
|
||||||
The docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials
|
The docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials
|
||||||
after authenticating to a registry (`docker login`). Docker v1.7.0 replaced this
|
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.
|
||||||
|
|
||||||
Given that the old file format encourages insecure storage of credentials
|
Given that the old file format encourages insecure storage of credentials
|
||||||
(credentials are stored unencrypted), and that no version of the CLI since
|
(credentials are stored unencrypted), and that no version of the CLI since
|
||||||
Docker v1.7.0 has created this file, the file is marked deprecated, and support
|
Docker v1.7.0 has created this file, support for this file, and its format has
|
||||||
for this file will be removed in a future release.
|
been removed.
|
||||||
|
|
||||||
|
A warning is printed in situations where the CLI would fall back to the old file,
|
||||||
|
notifying the user that the legacy file is present, but ignored.
|
||||||
|
|
||||||
### Configuration options for experimental CLI features
|
### Configuration options for experimental CLI features
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue