Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: command to apply profile after init #4195

Merged
merged 11 commits into from
Jan 2, 2018
4 changes: 2 additions & 2 deletions cmd/ipfs/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ 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)
}

if err := transformer(conf); err != nil {
if err := transformer.Apply(conf); err != nil {
return err
}
}
Expand Down
87 changes: 87 additions & 0 deletions core/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Set the value of the 'Datastore.Path' key:
"show": configShowCmd,
"edit": configEditCmd,
"replace": configReplaceCmd,
"profile": configProfileCmd,
},
}

Expand Down Expand Up @@ -293,6 +294,92 @@ can't be undone.
},
}

var configProfileCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Apply profiles to config.",
},

Subcommands: map[string]*cmds.Command{
"apply": configProfileApplyCmd,
"revert": configProfileRevertCmd,
Copy link
Member

@Stebalien Stebalien Dec 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually reverting is impossible (for us). We're not reverting, we're applying the inverse of the profile. Do we really need to provide this feature?

We could, instead, provide inverse profiles: local-discovery and flatfs-ds.

I'm worried that a user will think that profiles act like drop-ins (that's how a lot of unix daemons work) and will believe that revert will revert the apply.


Alternatively, we could just do this with layered drop-ins (but that's more complex).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @Stebalien this is not really a revert so we perhaps we should name it something else, not sure what, "unapply" comes to mind.

The idea of instead providing inverse profiles is also a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like the idea of inverse profiles better, I'll see how that looks

},
}

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, profile.Apply)
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
res.SetOutput(nil)
},
}

var configProfileRevertCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Revert profile changes.",
ShortDescription: `Reverts profile-related changes to the default values.

Reverting some profiles may damage the configuration or not be possible.
Backing up the config before running this command is advised.`,
},
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, profile.Revert)
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
res.SetOutput(nil)
},
}

func transformConfig(configRoot string, transformer config.Transformer) error {
r, err := fsrepo.Open(configRoot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the daemon is running while the user runs this they might run into issues. You cant open the repo again

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably best to pass in the repo from the IpfsNode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what other commands here do. Looking at repo.Open it should be safe:

func Open(repoPath string) (repo.Repo, error) {
	fn := func() (repo.Repo, error) {
		return open(repoPath)
	}
	return onlyOne.Open(repoPath, fn)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i see. good point.

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("profile-")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After making config.Transformer I would make this

_, err = r.BackupConfig(transformer.Name + "-")

To provide more helpful backup names.

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 {
Expand Down
27 changes: 27 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would mention that a backup is created and what it is called.


Available profiles:
- `server`

Recommended for nodes with public IPv4 address, disables host and content
discovery in local networks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or those running on VPSs.


- `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.

- `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 experimantal. Make sure to backup your data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

badger datastore is experimental

frequently


## Table of Contents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whyrusleeping should we document the badgerds profile here but label it as experimental?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its somewhat out of scope, but probably a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope?

I meant to add under the new Profiles section in case that wasn't clear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevina ah, gotcha. Yeah, lets get that in here.


- [`Addresses`](#addresses)
Expand Down
26 changes: 15 additions & 11 deletions repo/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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{
Expand Down
133 changes: 83 additions & 50 deletions repo/config/profile.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,89 @@
package config

// ConfigProfiles is a map holding configuration transformers
var ConfigProfiles = map[string]func(*Config) error{
"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.Discovery.MDNS.Enabled = false
return nil
// Transformer is a function which takes configuration and applies some filter to it
type Transformer func(c *Config) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this a struct something like

type Transformer struct {
  Name string
  Apply func(c *Config) error
}


// Profile applies some set of changes to the configuration
type Profile struct {
Apply Transformer
Revert Transformer
}

// Profiles is a map holding configuration transformers. Docs are in docs/config.md
var Profiles = map[string]*Profile{
"server": {
Apply: 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",
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really related to this PR, but i was just thinking that we should probably add LAN addresses to the NoAnnounce field for the server config.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

c.Addresses.NoAnnounce = append(c.Addresses.NoAnnounce, defaultServerFilters...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably try merging these instead of just appending.

c.Swarm.AddrFilters = append(c.Swarm.AddrFilters, defaultServerFilters...)
c.Discovery.MDNS.Enabled = false
return nil
},
Revert: func(c *Config) error {
c.Addresses.NoAnnounce = []string{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do keep the revert command, we should only remove the ones we've added.

c.Swarm.AddrFilters = []string{}
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.Bootstrap = []string{}
c.Discovery.MDNS.Enabled = false
return nil
"test": {
Apply: 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.Addresses.Swarm = []string{
"/ip4/127.0.0.1/tcp/0",
}

c.Swarm.DisableNatPortMap = true

c.Bootstrap = []string{}
c.Discovery.MDNS.Enabled = false
return nil
},
Revert: 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",
"prefix": "badger.datastore",
"child": map[string]interface{}{
"type": "badgerds",
"path": "badgerds",
"syncWrites": true,
},
}
return nil
"badgerds": {
Apply: func(c *Config) error {
c.Datastore.Spec = map[string]interface{}{
"type": "measure",
"prefix": "badger.datastore",
"child": map[string]interface{}{
"type": "badgerds",
"path": "badgerds",
"syncWrites": true,
},
}
return nil
},
Revert: func(c *Config) error {
c.Datastore.Spec = DefaultDatastoreConfig().Spec
return nil
},
},
}
26 changes: 26 additions & 0 deletions repo/fsrepo/fsrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions repo/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading