mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
fa92366009
feat(cli): add typed ~/.zot config layer and strict validation
Introduce pkg/cli/client/config.go with ZliConfigFile/ZliConfig and
ReadZliConfigFile, replacing the loose map[string]any load/save path in
config_cmd.go.
Parsing now rejects malformed JSON with ErrCliBadConfig and requires a
non-null configs array (ErrCliMissingConfigsField when wrapped). Each
profile must have non-empty _name and url.
Config commands delegate to typed helpers (Find, AddEntry, RemoveEntry,
GetVar/SetVar/ResetVar, FormatNames, WriteFile). Fresh or minimal files
still behave as empty via isFreshCliRead (ErrEmptyJSON or missing configs).
Tests: prefer t.Setenv("HOME", t.TempDir()) where CLI resolution uses --url
only; align CVE/client/search tests with mandatory profile URL and HOME
isolation.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
222 lines
5.3 KiB
Go
222 lines
5.3 KiB
Go
//go:build search
|
|
|
|
package client_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
zerr "zotregistry.dev/zot/v2/errors"
|
|
"zotregistry.dev/zot/v2/pkg/cli/client"
|
|
)
|
|
|
|
func writeZotFile(t *testing.T, dir, json string) string {
|
|
t.Helper()
|
|
|
|
p := filepath.Join(dir, ".zot")
|
|
assert.NoError(t, os.WriteFile(p, []byte(json), 0o600))
|
|
|
|
return p
|
|
}
|
|
|
|
func TestReadZliConfigFile_errors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
cfgContents string
|
|
wantSubs string
|
|
wantErrIs []error
|
|
}{
|
|
{
|
|
name: "configs_not_array",
|
|
cfgContents: `{"configs":{"x":1}}`,
|
|
wantSubs: `field "configs" must be a JSON array`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "configs_element_not_object",
|
|
cfgContents: `{"configs":[1]}`,
|
|
wantSubs: "must be a JSON object",
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "missing_profile_name",
|
|
cfgContents: `{"configs":[{"url":"https://example.com"}]}`,
|
|
wantSubs: `"_name" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "empty_profile_name",
|
|
cfgContents: `{"configs":[{"_name":"","url":"https://example.com"}]}`,
|
|
wantSubs: `"_name" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "profile_name_not_string",
|
|
cfgContents: `{"configs":[{"_name":1,"url":"https://example.com"}]}`,
|
|
wantSubs: `"_name" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "missing_url",
|
|
cfgContents: `{"configs":[{"_name":"main"}]}`,
|
|
wantSubs: `"url" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "empty_url",
|
|
cfgContents: `{"configs":[{"_name":"main","url":""}]}`,
|
|
wantSubs: `"url" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "url_not_string",
|
|
cfgContents: `{"configs":[{"_name":"main","url":123}]}`,
|
|
wantSubs: `"url" must be a non-empty string`,
|
|
wantErrIs: []error{zerr.ErrCliBadConfig},
|
|
},
|
|
{
|
|
name: "missing_configs_field",
|
|
cfgContents: `{}`,
|
|
wantSubs: "",
|
|
wantErrIs: []error{zerr.ErrCliBadConfig, zerr.ErrCliMissingConfigsField},
|
|
},
|
|
}
|
|
|
|
for _, tableCase := range tests {
|
|
t.Run(tableCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := writeZotFile(t, t.TempDir(), tableCase.cfgContents)
|
|
_, err := client.ReadZliConfigFile(p)
|
|
|
|
require.Error(t, err)
|
|
|
|
for _, target := range tableCase.wantErrIs {
|
|
require.ErrorIs(t, err, target)
|
|
}
|
|
|
|
if tableCase.wantSubs != "" {
|
|
assert.Contains(t, err.Error(), tableCase.wantSubs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestZliConfigFile_RemoveEntry_NotFound(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
f := client.ZliConfigFile{
|
|
Configs: []client.ZliConfig{{Name: "only", URL: "https://example.com"}},
|
|
}
|
|
|
|
err := f.RemoveEntry("missing")
|
|
assert.ErrorIs(t, err, zerr.ErrConfigNotFound)
|
|
}
|
|
|
|
func TestZliConfig_GetVar(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
zliCfg := client.ZliConfig{Name: "main", URL: "https://example.com"}
|
|
|
|
okCases := []struct {
|
|
key string
|
|
want string
|
|
}{
|
|
{"_name", "main"},
|
|
{client.URLFlag, "https://example.com"},
|
|
{"showspinner", ""},
|
|
{"verify-tls", ""},
|
|
}
|
|
|
|
for _, okCase := range okCases {
|
|
t.Run(okCase.key, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := zliCfg.GetVar(okCase.key)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, okCase.want, got)
|
|
})
|
|
}
|
|
|
|
t.Run("illegal_key", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := zliCfg.GetVar("not-a-real-key")
|
|
assert.ErrorIs(t, err, zerr.ErrIllegalConfigKey)
|
|
})
|
|
}
|
|
|
|
func TestZliConfig_SetVar(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errCases := []struct {
|
|
name string
|
|
key string
|
|
val string
|
|
wantErr error
|
|
}{
|
|
{"cannot_set_name", "_name", "other", zerr.ErrIllegalConfigKey},
|
|
{"illegal_key", "bogus", "x", zerr.ErrIllegalConfigKey},
|
|
{"invalid_url", client.URLFlag, "not-a-valid-url", zerr.ErrInvalidURL},
|
|
}
|
|
|
|
for _, errCase := range errCases {
|
|
t.Run(errCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := client.ZliConfig{Name: "main", URL: "https://example.com"}
|
|
assert.ErrorIs(t, cfg.SetVar(errCase.key, errCase.val), errCase.wantErr)
|
|
})
|
|
}
|
|
|
|
t.Run("parses_verify_tls_bool", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := client.ZliConfig{Name: "main", URL: "https://example.com"}
|
|
require.NoError(t, cfg.SetVar("verify-tls", "false"))
|
|
|
|
v, ok := cfg.VerifyTLS.(bool)
|
|
assert.True(t, ok)
|
|
assert.False(t, v)
|
|
})
|
|
}
|
|
|
|
func TestZliConfig_ResetVar(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
base := client.ZliConfig{Name: "main", URL: "https://example.com"}
|
|
|
|
errCases := []struct {
|
|
name string
|
|
key string
|
|
wantErr error
|
|
}{
|
|
{"url", client.URLFlag, zerr.ErrCannotResetConfigKey},
|
|
{"name", "_name", zerr.ErrCannotResetConfigKey},
|
|
{"illegal_key", "bogus", zerr.ErrIllegalConfigKey},
|
|
}
|
|
|
|
for _, errCase := range errCases {
|
|
t.Run(errCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := base
|
|
assert.ErrorIs(t, cfg.ResetVar(errCase.key), errCase.wantErr)
|
|
})
|
|
}
|
|
|
|
t.Run("clears_verify_tls", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := client.ZliConfig{Name: "main", URL: "https://example.com", VerifyTLS: false}
|
|
require.NoError(t, cfg.ResetVar("verify-tls"))
|
|
assert.Nil(t, cfg.VerifyTLS)
|
|
})
|
|
}
|