Merge pull request #4195 from ipfs/feat/config-patch

config: command to apply profile after init
This commit is contained in:
Whyrusleeping 2018-01-02 14:02:31 -08:00 committed by GitHub
commit f4fd369d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 293 additions and 38 deletions

View File

@ -160,7 +160,7 @@ func doInit(out io.Writer, repoRoot string, empty bool, nBitsForKeypair int, con
}
for _, profile := range confProfiles {
transformer, ok := config.ConfigProfiles[profile]
transformer, ok := config.Profiles[profile]
if !ok {
return fmt.Errorf("invalid configuration profile: %s", profile)
}

View File

@ -142,6 +142,7 @@ Set the value of the 'Datastore.Path' key:
"show": configShowCmd,
"edit": configEditCmd,
"replace": configReplaceCmd,
"profile": configProfileCmd,
},
}
@ -293,6 +294,64 @@ can't be undone.
},
}
var configProfileCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Apply profiles to config.",
},
Subcommands: map[string]*cmds.Command{
"apply": configProfileApplyCmd,
},
}
var configProfileApplyCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Apply profile to config.",
},
Arguments: []cmdkit.Argument{
cmdkit.StringArg("profile", true, false, "The profile to apply to the config."),
},
Run: func(req cmds.Request, res cmds.Response) {
profile, ok := config.Profiles[req.Arguments()[0]]
if !ok {
res.SetError(fmt.Errorf("%s is not a profile", req.Arguments()[0]), cmdkit.ErrNormal)
return
}
err := transformConfig(req.InvocContext().ConfigRoot, req.Arguments()[0], profile)
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
res.SetOutput(nil)
},
}
func transformConfig(configRoot string, configName string, transformer config.Transformer) error {
r, err := fsrepo.Open(configRoot)
if err != nil {
return err
}
defer r.Close()
cfg, err := r.Config()
if err != nil {
return err
}
err = transformer(cfg)
if err != nil {
return err
}
_, err = r.BackupConfig("pre-" + configName + "-")
if err != nil {
return err
}
return r.SetConfig(cfg)
}
func getConfig(r repo.Repo, key string) (*ConfigField, error) {
value, err := r.GetConfigKey(key)
if err != nil {

View File

@ -4,6 +4,46 @@ The go-ipfs config file is a json document. It is read once at node instantiatio
either for an offline command, or when starting the daemon. Commands that execute
on a running daemon do not read the config file at runtime.
#### Profiles
Configuration profiles allow to tweak configuration quickly. Profiles can be
applied with `--profile` flag to `ipfs init` or with `ipfs config profile apply`
command. When a profile is applied a backup of the configuration file will
be created in $IPFS_PATH
Available profiles:
- `server`
Recommended for nodes with public IPv4 address (servers, VPSes, etc.),
disables host and content discovery in local networks.
- `local-discovery`
Sets default values to fields affected by `server` profile, enables
discovery in local networks.
- `test`
Reduces external interference, useful for running ipfs in test environments.
Note that with these settings node won't be able to talk to the rest of the
network without manual bootstrap.
- `default-networking`
Restores default network settings. Inverse profile of the `test` profile.
- `badgerds`
Replaces default datastore configuration with experimental badger datastore.
If you apply this profile after `ipfs init`, you will need to convert your
datastore to the new configuration. You can do this using [ipfs-ds-convert](https://github.com/ipfs/ipfs-ds-convert)
WARNING: badger datastore is experimental. Make sure to backup your data
frequently.
- `default-datastore`
Restores default datastore configuration.
## Table of Contents
- [`Addresses`](#addresses)

View File

@ -28,17 +28,7 @@ func Init(out io.Writer, nBitsForKeypair int) (*Config, error) {
// setup the node's default addresses.
// NOTE: two swarm listen addrs, one tcp, one utp.
Addresses: Addresses{
Swarm: []string{
"/ip4/0.0.0.0/tcp/4001",
// "/ip4/0.0.0.0/udp/4002/utp", // disabled for now.
"/ip6/::/tcp/4001",
},
Announce: []string{},
NoAnnounce: []string{},
API: "/ip4/127.0.0.1/tcp/5001",
Gateway: "/ip4/127.0.0.1/tcp/8080",
},
Addresses: addressesConfig(),
Datastore: datastore,
Bootstrap: BootstrapPeerStrings(bootstrapPeers),
@ -97,6 +87,20 @@ const DefaultConnMgrLowWater = 600
// grace period
const DefaultConnMgrGracePeriod = time.Second * 20
func addressesConfig() Addresses {
return Addresses{
Swarm: []string{
"/ip4/0.0.0.0/tcp/4001",
// "/ip4/0.0.0.0/udp/4002/utp", // disabled for now.
"/ip6/::/tcp/4001",
},
Announce: []string{},
NoAnnounce: []string{},
API: "/ip4/127.0.0.1/tcp/5001",
Gateway: "/ip4/127.0.0.1/tcp/8080",
}
}
// DefaultDatastoreConfig is an internal function exported to aid in testing.
func DefaultDatastoreConfig() Datastore {
return Datastore{

View File

@ -1,46 +1,62 @@
package config
// ConfigProfiles is a map holding configuration transformers
var ConfigProfiles = map[string]func(*Config) error{
// Transformer is a function which takes configuration and applies some filter to it
type Transformer func(c *Config) error
// defaultServerFilters has a list of non-routable IPv4 prefixes
// according to http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
var defaultServerFilters = []string{
"/ip4/10.0.0.0/ipcidr/8",
"/ip4/100.64.0.0/ipcidr/10",
"/ip4/169.254.0.0/ipcidr/16",
"/ip4/172.16.0.0/ipcidr/12",
"/ip4/192.0.0.0/ipcidr/24",
"/ip4/192.0.0.0/ipcidr/29",
"/ip4/192.0.0.8/ipcidr/32",
"/ip4/192.0.0.170/ipcidr/32",
"/ip4/192.0.0.171/ipcidr/32",
"/ip4/192.0.2.0/ipcidr/24",
"/ip4/192.168.0.0/ipcidr/16",
"/ip4/198.18.0.0/ipcidr/15",
"/ip4/198.51.100.0/ipcidr/24",
"/ip4/203.0.113.0/ipcidr/24",
"/ip4/240.0.0.0/ipcidr/4",
}
// Profiles is a map holding configuration transformers. Docs are in docs/config.md
var Profiles = map[string]Transformer{
"server": func(c *Config) error {
// defaultServerFilters has a list of non-routable IPv4 prefixes
// according to http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
defaultServerFilters := []string{
"/ip4/10.0.0.0/ipcidr/8",
"/ip4/100.64.0.0/ipcidr/10",
"/ip4/169.254.0.0/ipcidr/16",
"/ip4/172.16.0.0/ipcidr/12",
"/ip4/192.0.0.0/ipcidr/24",
"/ip4/192.0.0.0/ipcidr/29",
"/ip4/192.0.0.8/ipcidr/32",
"/ip4/192.0.0.170/ipcidr/32",
"/ip4/192.0.0.171/ipcidr/32",
"/ip4/192.0.2.0/ipcidr/24",
"/ip4/192.168.0.0/ipcidr/16",
"/ip4/198.18.0.0/ipcidr/15",
"/ip4/198.51.100.0/ipcidr/24",
"/ip4/203.0.113.0/ipcidr/24",
"/ip4/240.0.0.0/ipcidr/4",
}
c.Swarm.AddrFilters = append(c.Swarm.AddrFilters, defaultServerFilters...)
c.Addresses.NoAnnounce = appendSingle(c.Addresses.NoAnnounce, defaultServerFilters)
c.Swarm.AddrFilters = appendSingle(c.Swarm.AddrFilters, defaultServerFilters)
c.Discovery.MDNS.Enabled = false
return nil
},
"local-discovery": func(c *Config) error {
c.Addresses.NoAnnounce = deleteEntries(c.Addresses.NoAnnounce, defaultServerFilters)
c.Swarm.AddrFilters = deleteEntries(c.Swarm.AddrFilters, defaultServerFilters)
c.Discovery.MDNS.Enabled = true
return nil
},
"test": func(c *Config) error {
c.Addresses.API = "/ip4/127.0.0.1/tcp/0"
c.Addresses.Gateway = "/ip4/127.0.0.1/tcp/0"
c.Swarm.DisableNatPortMap = true
c.Addresses.Swarm = []string{
"/ip4/127.0.0.1/tcp/0",
}
c.Swarm.DisableNatPortMap = true
c.Bootstrap = []string{}
c.Discovery.MDNS.Enabled = false
return nil
},
"default-networking": func(c *Config) error {
c.Addresses = addressesConfig()
c.Swarm.DisableNatPortMap = false
c.Discovery.MDNS.Enabled = true
return nil
},
"badgerds": func(c *Config) error {
c.Datastore.Spec = map[string]interface{}{
"type": "measure",
@ -53,4 +69,38 @@ var ConfigProfiles = map[string]func(*Config) error{
}
return nil
},
"default-datastore": func(c *Config) error {
c.Datastore.Spec = DefaultDatastoreConfig().Spec
return nil
},
}
func appendSingle(a []string, b []string) []string {
m := map[string]struct{}{}
for _, f := range a {
m[f] = struct{}{}
}
for _, f := range b {
m[f] = struct{}{}
}
return mapKeys(m)
}
func deleteEntries(arr []string, del []string) []string {
m := map[string]struct{}{}
for _, f := range arr {
m[f] = struct{}{}
}
for _, f := range del {
delete(m, f)
}
return mapKeys(m)
}
func mapKeys(m map[string]struct{}) []string {
out := make([]string, 0, len(m))
for f := range m {
out = append(out, f)
}
return out
}

View File

@ -480,6 +480,32 @@ func (r *FSRepo) FileManager() *filestore.FileManager {
return r.filemgr
}
func (r *FSRepo) BackupConfig(prefix string) (string, error) {
temp, err := ioutil.TempFile(r.path, "config-"+prefix)
if err != nil {
return "", err
}
defer temp.Close()
configFilename, err := config.Filename(r.path)
if err != nil {
return "", err
}
orig, err := os.OpenFile(configFilename, os.O_RDONLY, 0600)
if err != nil {
return "", err
}
defer orig.Close()
_, err = io.Copy(temp, orig)
if err != nil {
return "", err
}
return orig.Name(), nil
}
// setConfigUnsynced is for private use.
func (r *FSRepo) setConfigUnsynced(updated *config.Config) error {
configFilename, err := config.Filename(r.path)

View File

@ -28,6 +28,10 @@ func (m *Mock) SetConfig(updated *config.Config) error {
return nil
}
func (m *Mock) BackupConfig(prefix string) (string, error) {
return "", errTODO
}
func (m *Mock) SetConfigKey(key string, value interface{}) error {
return errTODO
}

View File

@ -18,6 +18,7 @@ var (
type Repo interface {
Config() (*config.Config, error)
BackupConfig(prefix string) (string, error)
SetConfig(*config.Config) error
SetConfigKey(key string, value interface{}) error

View File

@ -48,6 +48,33 @@ CONFIG_SET_JSON_TEST='{
}
}'
test_profile_apply_revert() {
profile=$1
inverse_profile=$2
test_expect_success "save expected config" '
ipfs config show >expected
'
test_expect_success "'ipfs config profile apply ${profile}' works" '
ipfs config profile apply '${profile}'
'
test_expect_success "profile ${profile} changed something" '
ipfs config show >actual &&
test_must_fail test_cmp expected actual
'
test_expect_success "'ipfs config profile apply ${inverse_profile}' works" '
ipfs config profile apply '${inverse_profile}'
'
test_expect_success "config is back to previous state after ${inverse_profile} was applied" '
ipfs config show >actual &&
test_cmp expected actual
'
}
test_config_cmd() {
test_config_cmd_set "beep" "boop"
test_config_cmd_set "beep1" "boop2"
@ -151,6 +178,50 @@ test_config_cmd() {
echo "Error: setting private key with API is not supported" > replace_expected
test_cmp replace_out replace_expected
'
test_expect_success "'ipfs config Swarm.AddrFilters' looks good" '
ipfs config Swarm.AddrFilters > actual_config &&
test $(cat actual_config | wc -l) = 1
'
test_expect_success "copy ipfs config" '
cp "$IPFS_PATH/config" before_patch
'
test_expect_success "'ipfs config profile apply server' works" '
ipfs config profile apply server
'
test_expect_success "backup was created and looks good" '
test_cmp "$(find "$IPFS_PATH" -name "config-*")" before_patch
'
test_expect_success "'ipfs config Swarm.AddrFilters' looks good with server profile" '
ipfs config Swarm.AddrFilters > actual_config &&
test $(cat actual_config | wc -l) = 17
'
test_expect_success "'ipfs config profile apply local-discovery' works" '
ipfs config profile apply local-discovery
'
test_expect_success "'ipfs config Swarm.AddrFilters' looks good with applied local-discovery profile" '
ipfs config Swarm.AddrFilters > actual_config &&
test $(cat actual_config | wc -l) = 1
'
test_profile_apply_revert server local-discovery
# won't work as we already have this profile applied
# test_profile_apply_revert test
# won't work as it changes datastore definition, which makes ipfs not launch
# without converting first
# test_profile_apply_revert badgerds
test_expect_success "cleanup config backups" '
find "$IPFS_PATH" -name "config-*" -exec rm {} \;
'
}
test_init_ipfs