mirror of https://github.com/status-im/consul.git
config: add -config-format option (#3626)
* config: refactor ReadPath(s) methods without side-effects Return the sources instead of modifying the state. * config: clean data dir before every test * config: add tests for config-file and config-dir * config: add -config-format option Starting with Consul 1.0 all config files must have a '.json' or '.hcl' extension to make it unambigous how the data should be parsed. Some automation tools generate temporary files by appending a random string to the generated file which obfuscates the extension and prevents the file type detection. This patch adds a -config-format option which can be used to override the auto-detection behavior by forcing all config files or all files within a config directory independent of their extension to be interpreted as of this format. Fixes #3620
This commit is contained in:
parent
4092d1e906
commit
874e350b2f
|
@ -110,14 +110,16 @@ func NewBuilder(flags Flags) (*Builder, error) {
|
||||||
slices, values := b.splitSlicesAndValues(b.Flags.Config)
|
slices, values := b.splitSlicesAndValues(b.Flags.Config)
|
||||||
b.Head = append(b.Head, newSource("flags.slices", slices))
|
b.Head = append(b.Head, newSource("flags.slices", slices))
|
||||||
for _, path := range b.Flags.ConfigFiles {
|
for _, path := range b.Flags.ConfigFiles {
|
||||||
if err := b.ReadPath(path); err != nil {
|
sources, err := b.ReadPath(path)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
b.Sources = append(b.Sources, sources...)
|
||||||
}
|
}
|
||||||
b.Tail = append(b.Tail, newSource("flags.values", values))
|
b.Tail = append(b.Tail, newSource("flags.values", values))
|
||||||
for i, s := range b.Flags.HCL {
|
for i, s := range b.Flags.HCL {
|
||||||
b.Tail = append(b.Tail, Source{
|
b.Tail = append(b.Tail, Source{
|
||||||
Name: fmt.Sprintf("flags.hcl.%d", i),
|
Name: fmt.Sprintf("flags-%d.hcl", i),
|
||||||
Format: "hcl",
|
Format: "hcl",
|
||||||
Data: s,
|
Data: s,
|
||||||
})
|
})
|
||||||
|
@ -131,64 +133,59 @@ func NewBuilder(flags Flags) (*Builder, error) {
|
||||||
|
|
||||||
// ReadPath reads a single config file or all files in a directory (but
|
// ReadPath reads a single config file or all files in a directory (but
|
||||||
// not its sub-directories) and appends them to the list of config
|
// not its sub-directories) and appends them to the list of config
|
||||||
// sources. If path refers to a file then the format is assumed to be
|
// sources.
|
||||||
// JSON unless the file has a '.hcl' suffix. If path refers to a
|
func (b *Builder) ReadPath(path string) ([]Source, error) {
|
||||||
// directory then the format is determined by the suffix and only files
|
|
||||||
// with a '.json' or '.hcl' suffix are processed.
|
|
||||||
func (b *Builder) ReadPath(path string) error {
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: Open failed on %s. %s", path, err)
|
return nil, fmt.Errorf("config: Open failed on %s. %s", path, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: Stat failed on %s. %s", path, err)
|
return nil, fmt.Errorf("config: Stat failed on %s. %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
return b.ReadFile(path)
|
src, err := b.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []Source{src}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fis, err := f.Readdir(-1)
|
fis, err := f.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: Readdir failed on %s. %s", path, err)
|
return nil, fmt.Errorf("config: Readdir failed on %s. %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort files by name
|
// sort files by name
|
||||||
sort.Sort(byName(fis))
|
sort.Sort(byName(fis))
|
||||||
|
|
||||||
|
var sources []Source
|
||||||
for _, fi := range fis {
|
for _, fi := range fis {
|
||||||
// do not recurse into sub dirs
|
// do not recurse into sub dirs
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip files without json or hcl extension
|
src, err := b.ReadFile(filepath.Join(path, fi.Name()))
|
||||||
if !strings.HasSuffix(fi.Name(), ".json") && !strings.HasSuffix(fi.Name(), ".hcl") {
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sources = append(sources, src)
|
||||||
if err := b.ReadFile(filepath.Join(path, fi.Name())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
return sources, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile parses a JSON or HCL config file and appends it to the list of
|
// ReadFile parses a JSON or HCL config file and appends it to the list of
|
||||||
// config sources.
|
// config sources.
|
||||||
func (b *Builder) ReadFile(path string) error {
|
func (b *Builder) ReadFile(path string) (Source, error) {
|
||||||
if !strings.HasSuffix(path, ".json") && !strings.HasSuffix(path, ".hcl") {
|
|
||||||
return fmt.Errorf(`Missing or invalid file extension for %q. Please use ".json" or ".hcl".`, path)
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: ReadFile failed on %s: %s", path, err)
|
return Source{}, fmt.Errorf("config: ReadFile failed on %s: %s", path, err)
|
||||||
}
|
}
|
||||||
b.Sources = append(b.Sources, NewSource(path, string(data)))
|
return Source{Name: path, Data: string(data)}, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type byName []os.FileInfo
|
type byName []os.FileInfo
|
||||||
|
@ -222,10 +219,24 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
|
||||||
// merge config sources as follows
|
// merge config sources as follows
|
||||||
//
|
//
|
||||||
|
|
||||||
|
configFormat := b.stringVal(b.Flags.ConfigFormat)
|
||||||
|
if configFormat != "" && configFormat != "json" && configFormat != "hcl" {
|
||||||
|
return RuntimeConfig{}, fmt.Errorf("config: -config-format must be either 'hcl' or 'json'")
|
||||||
|
}
|
||||||
|
|
||||||
// build the list of config sources
|
// build the list of config sources
|
||||||
var srcs []Source
|
var srcs []Source
|
||||||
srcs = append(srcs, b.Head...)
|
srcs = append(srcs, b.Head...)
|
||||||
srcs = append(srcs, b.Sources...)
|
for _, src := range b.Sources {
|
||||||
|
src.Format = FormatFrom(src.Name)
|
||||||
|
if configFormat != "" {
|
||||||
|
src.Format = configFormat
|
||||||
|
}
|
||||||
|
if src.Format == "" {
|
||||||
|
return RuntimeConfig{}, fmt.Errorf(`config: Missing or invalid file extension for %q. Please use ".json" or ".hcl".`, src.Name)
|
||||||
|
}
|
||||||
|
srcs = append(srcs, src)
|
||||||
|
}
|
||||||
srcs = append(srcs, b.Tail...)
|
srcs = append(srcs, b.Tail...)
|
||||||
|
|
||||||
// parse the config sources into a configuration
|
// parse the config sources into a configuration
|
||||||
|
|
|
@ -21,15 +21,15 @@ type Source struct {
|
||||||
Data string
|
Data string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSource(name, data string) Source {
|
|
||||||
return Source{Name: name, Format: FormatFrom(name), Data: data}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatFrom(name string) string {
|
func FormatFrom(name string) string {
|
||||||
if strings.HasSuffix(name, ".hcl") {
|
switch {
|
||||||
return "hcl"
|
case strings.HasSuffix(name, ".json"):
|
||||||
}
|
|
||||||
return "json"
|
return "json"
|
||||||
|
case strings.HasSuffix(name, ".hcl"):
|
||||||
|
return "hcl"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a config fragment in either JSON or HCL format.
|
// Parse parses a config fragment in either JSON or HCL format.
|
||||||
|
|
|
@ -16,11 +16,15 @@ type Flags struct {
|
||||||
// that should be read.
|
// that should be read.
|
||||||
ConfigFiles []string
|
ConfigFiles []string
|
||||||
|
|
||||||
|
// ConfigFormat forces all config files to be interpreted as this
|
||||||
|
// format independent of their extension.
|
||||||
|
ConfigFormat *string
|
||||||
|
|
||||||
|
// HCL contains an arbitrary config in hcl format.
|
||||||
// DevMode indicates whether the agent should be started in development
|
// DevMode indicates whether the agent should be started in development
|
||||||
// mode. This cannot be configured in a config file.
|
// mode. This cannot be configured in a config file.
|
||||||
DevMode *bool
|
DevMode *bool
|
||||||
|
|
||||||
// HCL contains an arbitrary config in hcl format.
|
|
||||||
HCL []string
|
HCL []string
|
||||||
|
|
||||||
// Args contains the remaining unparsed flags.
|
// Args contains the remaining unparsed flags.
|
||||||
|
@ -57,6 +61,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) {
|
||||||
add(&f.Config.ClientAddr, "client", "Sets the address to bind for client access. This includes RPC, DNS, HTTP and HTTPS (if configured).")
|
add(&f.Config.ClientAddr, "client", "Sets the address to bind for client access. This includes RPC, DNS, HTTP and HTTPS (if configured).")
|
||||||
add(&f.ConfigFiles, "config-dir", "Path to a directory to read configuration files from. This will read every file ending in '.json' as configuration in this directory in alphabetical order. Can be specified multiple times.")
|
add(&f.ConfigFiles, "config-dir", "Path to a directory to read configuration files from. This will read every file ending in '.json' as configuration in this directory in alphabetical order. Can be specified multiple times.")
|
||||||
add(&f.ConfigFiles, "config-file", "Path to a JSON file to read configuration from. Can be specified multiple times.")
|
add(&f.ConfigFiles, "config-file", "Path to a JSON file to read configuration from. Can be specified multiple times.")
|
||||||
|
add(&f.ConfigFormat, "config-format", "Config files are in this format irrespective of their extension. Must be 'hcl' or 'json'")
|
||||||
add(&f.Config.DataDir, "data-dir", "Path to a data directory to store agent state.")
|
add(&f.Config.DataDir, "data-dir", "Path to a data directory to store agent state.")
|
||||||
add(&f.Config.Datacenter, "datacenter", "Datacenter of the agent.")
|
add(&f.Config.Datacenter, "datacenter", "Datacenter of the agent.")
|
||||||
add(&f.DevMode, "dev", "Starts the agent in development mode.")
|
add(&f.DevMode, "dev", "Starts the agent in development mode.")
|
||||||
|
|
|
@ -177,6 +177,50 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
rt.DataDir = dataDir
|
rt.DataDir = dataDir
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-dir",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
`-config-dir`, filepath.Join(dataDir, "conf.d"),
|
||||||
|
},
|
||||||
|
patch: func(rt *RuntimeConfig) {
|
||||||
|
rt.Datacenter = "a"
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
},
|
||||||
|
pre: func() {
|
||||||
|
writeFile(filepath.Join(dataDir, "conf.d/conf.json"), []byte(`{"datacenter":"a"}`))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-file json",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
`-config-file`, filepath.Join(dataDir, "conf.json"),
|
||||||
|
},
|
||||||
|
patch: func(rt *RuntimeConfig) {
|
||||||
|
rt.Datacenter = "a"
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
},
|
||||||
|
pre: func() {
|
||||||
|
writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"a"}`))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-file hcl and json",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
`-config-file`, filepath.Join(dataDir, "conf.hcl"),
|
||||||
|
`-config-file`, filepath.Join(dataDir, "conf.json"),
|
||||||
|
},
|
||||||
|
patch: func(rt *RuntimeConfig) {
|
||||||
|
rt.Datacenter = "b"
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
},
|
||||||
|
pre: func() {
|
||||||
|
writeFile(filepath.Join(dataDir, "conf.hcl"), []byte(`datacenter = "a"`))
|
||||||
|
writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"b"}`))
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "-data-dir empty",
|
desc: "-data-dir empty",
|
||||||
args: []string{
|
args: []string{
|
||||||
|
@ -317,6 +361,43 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
rt.DataDir = dataDir
|
rt.DataDir = dataDir
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-format=json",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
`-config-format=json`,
|
||||||
|
`-config-file`, filepath.Join(dataDir, "conf"),
|
||||||
|
},
|
||||||
|
patch: func(rt *RuntimeConfig) {
|
||||||
|
rt.Datacenter = "a"
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
},
|
||||||
|
pre: func() {
|
||||||
|
writeFile(filepath.Join(dataDir, "conf"), []byte(`{"datacenter":"a"}`))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-format=hcl",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
`-config-format=hcl`,
|
||||||
|
`-config-file`, filepath.Join(dataDir, "conf"),
|
||||||
|
},
|
||||||
|
patch: func(rt *RuntimeConfig) {
|
||||||
|
rt.Datacenter = "a"
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
},
|
||||||
|
pre: func() {
|
||||||
|
writeFile(filepath.Join(dataDir, "conf"), []byte(`datacenter = "a"`))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "-config-format invalid",
|
||||||
|
args: []string{
|
||||||
|
`-config-format=foobar`,
|
||||||
|
},
|
||||||
|
err: "-config-format must be either 'hcl' or 'json'",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "-http-port",
|
desc: "-http-port",
|
||||||
args: []string{
|
args: []string{
|
||||||
|
@ -1723,9 +1804,6 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
pre: func() {
|
pre: func() {
|
||||||
writeFile(filepath.Join(dataDir, SerfLANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
|
writeFile(filepath.Join(dataDir, SerfLANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
|
||||||
},
|
},
|
||||||
post: func() {
|
|
||||||
os.Remove(filepath.Join(filepath.Join(dataDir, SerfLANKeyring)))
|
|
||||||
},
|
|
||||||
warns: []string{`WARNING: LAN keyring exists but -encrypt given, using keyring`},
|
warns: []string{`WARNING: LAN keyring exists but -encrypt given, using keyring`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1745,9 +1823,6 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
pre: func() {
|
pre: func() {
|
||||||
writeFile(filepath.Join(dataDir, SerfWANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
|
writeFile(filepath.Join(dataDir, SerfWANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
|
||||||
},
|
},
|
||||||
post: func() {
|
|
||||||
os.Remove(filepath.Join(filepath.Join(dataDir, SerfWANKeyring)))
|
|
||||||
},
|
|
||||||
warns: []string{`WARNING: WAN keyring exists but -encrypt given, using keyring`},
|
warns: []string{`WARNING: WAN keyring exists but -encrypt given, using keyring`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1855,6 +1930,9 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
func testConfig(t *testing.T, tests []configTest, dataDir string) {
|
func testConfig(t *testing.T, tests []configTest, dataDir string) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
for pass, format := range []string{"json", "hcl"} {
|
for pass, format := range []string{"json", "hcl"} {
|
||||||
|
// clean data dir before every test
|
||||||
|
cleanDir(dataDir)
|
||||||
|
|
||||||
// when we test only flags then there are no JSON or HCL
|
// when we test only flags then there are no JSON or HCL
|
||||||
// sources and we need to make only one pass over the
|
// sources and we need to make only one pass over the
|
||||||
// tests.
|
// tests.
|
||||||
|
@ -1895,6 +1973,15 @@ func testConfig(t *testing.T, tests []configTest, dataDir string) {
|
||||||
}
|
}
|
||||||
flags.Args = fs.Args()
|
flags.Args = fs.Args()
|
||||||
|
|
||||||
|
if tt.pre != nil {
|
||||||
|
tt.pre()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if tt.post != nil {
|
||||||
|
tt.post()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Then create a builder with the flags.
|
// Then create a builder with the flags.
|
||||||
b, err := NewBuilder(flags)
|
b, err := NewBuilder(flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1926,28 +2013,20 @@ func testConfig(t *testing.T, tests []configTest, dataDir string) {
|
||||||
// read the source fragements
|
// read the source fragements
|
||||||
for i, data := range srcs {
|
for i, data := range srcs {
|
||||||
b.Sources = append(b.Sources, Source{
|
b.Sources = append(b.Sources, Source{
|
||||||
Name: fmt.Sprintf("%s-%d", format, i),
|
Name: fmt.Sprintf("src-%d.%s", i, format),
|
||||||
Format: format,
|
Format: format,
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, data := range tails {
|
for i, data := range tails {
|
||||||
b.Tail = append(b.Tail, Source{
|
b.Tail = append(b.Tail, Source{
|
||||||
Name: fmt.Sprintf("%s-%d", format, i),
|
Name: fmt.Sprintf("tail-%d.%s", i, format),
|
||||||
Format: format,
|
Format: format,
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// build/merge the config fragments
|
// build/merge the config fragments
|
||||||
if tt.pre != nil {
|
|
||||||
tt.pre()
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if tt.post != nil {
|
|
||||||
tt.post()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
rt, err := b.BuildAndValidate()
|
rt, err := b.BuildAndValidate()
|
||||||
if err == nil && tt.err != "" {
|
if err == nil && tt.err != "" {
|
||||||
t.Fatalf("got no error want %q", tt.err)
|
t.Fatalf("got no error want %q", tt.err)
|
||||||
|
@ -3484,7 +3563,7 @@ func TestFullConfig(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewBuilder: %s", err)
|
t.Fatalf("NewBuilder: %s", err)
|
||||||
}
|
}
|
||||||
b.Sources = append(b.Sources, Source{Name: "full", Format: format, Data: data})
|
b.Sources = append(b.Sources, Source{Name: "full." + format, Data: data})
|
||||||
b.Tail = append(b.Tail, tail[format]...)
|
b.Tail = append(b.Tail, tail[format]...)
|
||||||
b.Tail = append(b.Tail, VersionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn"))
|
b.Tail = append(b.Tail, VersionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn"))
|
||||||
|
|
||||||
|
@ -4027,6 +4106,19 @@ func writeFile(path string, data []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanDir(path string) {
|
||||||
|
root := path
|
||||||
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if path == root {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func randomString(n int) string {
|
func randomString(n int) string {
|
||||||
s := ""
|
s := ""
|
||||||
for ; n > 0; n-- {
|
for ; n > 0; n-- {
|
||||||
|
|
|
@ -125,6 +125,12 @@ will exit with an error at startup.
|
||||||
For more information on the format of the configuration files, see the
|
For more information on the format of the configuration files, see the
|
||||||
[Configuration Files](#configuration_files) section.
|
[Configuration Files](#configuration_files) section.
|
||||||
|
|
||||||
|
* <a name="_config_format"></a><a href="#_config_format">`-config-format`</a> - The format
|
||||||
|
of the configuration files to load. Normally, Consul detects the format of the
|
||||||
|
config files from the ".json" or ".hcl" extension. Setting this option to
|
||||||
|
either "json" or "hcl" forces Consul to interpret any file with or without
|
||||||
|
extension to be interpreted in that format.
|
||||||
|
|
||||||
* <a name="_data_dir"></a><a href="#_data_dir">`-data-dir`</a> - This flag provides
|
* <a name="_data_dir"></a><a href="#_data_dir">`-data-dir`</a> - This flag provides
|
||||||
a data directory for the agent to store state.
|
a data directory for the agent to store state.
|
||||||
This is required for all agents. The directory should be durable across reboots.
|
This is required for all agents. The directory should be durable across reboots.
|
||||||
|
|
Loading…
Reference in New Issue