service: multiple fixes around init and identities

* Fix error messages (they must be in the form "doing something")
* Improve/reword some error messages
* Document the identity.json existance in the cli docs
* Fix a bunch of typos
* Fix missing folder path in the --help
* Fix cluster not locking when configuration is not there but folder is
* Fix force flag not overriding the config overwrite prompt
* Fix deletion of Raft state on re-init (not necessary if identity persists)
* Fix overwriting on identity (should not be overwritten if already exists)

Much of this paves the way to be able to run without service.json:

* Either taking default values (and using env vars) - maybe someday
* Either by getting a configuration template it from somewhere (ipfs, http)
  at runtime - sooner.
This commit is contained in:
Hector Sanjuan 2019-05-16 15:31:36 +02:00
parent 7a66fc3484
commit e62d10f83a
4 changed files with 86 additions and 67 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"os" "os"
"path/filepath" "path/filepath"
@ -96,35 +97,47 @@ func loadIdentity() *config.Identity {
_, err := os.Stat(identityPath) _, err := os.Stat(identityPath)
ident := &config.Identity{} ident := &config.Identity{}
// temporary hack to convert identity
if os.IsNotExist(err) { if os.IsNotExist(err) {
clusterConfig, err := config.GetClusterConfig(configPath) clusterConfig, err := config.GetClusterConfig(configPath)
checkErr("couldn not get cluster config", err) checkErr("loading configuration", err)
err = ident.LoadJSON(clusterConfig) err = ident.LoadJSON(clusterConfig)
checkErr("could not load identity from cluster config", err) if err != nil {
checkErr("", errors.New("error loading identity"))
}
err = ident.SaveJSON(identityPath) err = ident.SaveJSON(identityPath)
checkErr("could not save identity.json ", err) checkErr("saving identity.json ", err)
err = ident.ApplyEnvVars() err = ident.ApplyEnvVars()
checkErr("could not apply environment variables tot the identity ", err) checkErr("applying environment variables to the identity", err)
out("\nNOTICE: identity information extracted from %s and saved as %s.\n\n", DefaultConfigFile, DefaultIdentityFile)
return ident return ident
} }
err = ident.LoadJSONFromFile(identityPath) err = ident.LoadJSONFromFile(identityPath)
checkErr("could not load identity from identity.json", err) checkErr("loading identity from %s", err, DefaultIdentityFile)
err = ident.ApplyEnvVars() err = ident.ApplyEnvVars()
checkErr("could not apply environment variables to the identity ", err) checkErr("applying environment variables to the identity", err)
return ident return ident
} }
func makeConfigFolder() {
f := filepath.Dir(configPath)
if _, err := os.Stat(f); os.IsNotExist(err) {
err := os.MkdirAll(f, 0700)
checkErr("creating configuration folder (%s)", err, f)
}
}
func saveConfig(cfg *config.Manager) { func saveConfig(cfg *config.Manager) {
err := os.MkdirAll(filepath.Dir(configPath), 0700) makeConfigFolder()
err = cfg.SaveJSON(configPath) err := cfg.SaveJSON(configPath)
checkErr("saving new configuration", err) checkErr("saving new configuration", err)
out("%s configuration written to %s\n", programName, configPath) out("configuration written to %s\n", configPath)
} }
func propagateTracingConfig(ident *config.Identity, cfgs *cfgs, tracingFlag bool) *cfgs { func propagateTracingConfig(ident *config.Identity, cfgs *cfgs, tracingFlag bool) *cfgs {

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os"
"path" "path"
fslock "github.com/ipfs/go-fs-lock" fslock "github.com/ipfs/go-fs-lock"
@ -28,11 +27,8 @@ func (l *lock) lock() {
checkErr("", errors.New("cannot acquire lock twice")) checkErr("", errors.New("cannot acquire lock twice"))
} }
if _, err := os.Stat(configPath); os.IsNotExist(err) { // we should have a config folder whenever we try to lock
errMsg := "%s config hasn't been initialized. Please run '%s init'" makeConfigFolder()
errMsg = fmt.Sprintf(errMsg, programName, programName)
checkErr("", errors.New(errMsg))
}
// set the lock file within this function // set the lock file within this function
logger.Debug("checking lock") logger.Debug("checking lock")

View File

@ -30,8 +30,8 @@ const (
) )
const ( const (
stateCleanupPrompt = "The peer's state will be removed from the load path. Existing pins may be lost." stateCleanupPrompt = "The peer state will be removed. Existing pins may be lost."
configurationOverwritePrompt = "Configuration(service.json) and Identity(identity.json) will be overwritten." configurationOverwritePrompt = "The configuration file will be overwritten."
) )
// We store a commit id here // We store a commit id here
@ -70,16 +70,17 @@ ipfs-cluster-service | HTTP
+----------+--------+-----+----------------------+ +-------------+ +----------+--------+-----+----------------------+ +-------------+
%s needs a valid configuration to run. This configuration is %s needs valid configuration and identity files to run.
independent from IPFS and includes its own LibP2P key-pair. It can be These are independent from IPFS. The identity includes its own
initialized with "init" and its default location is libp2p key-pair. They can be initialized with "init" and their
~/%s/%s. default locations are ~/%s/%s
and ~/%s/%s.
For feedback, bug reports or any additional information, visit For feedback, bug reports or any additional information, visit
https://github.com/ipfs/ipfs-cluster. https://github.com/ipfs/ipfs-cluster.
EXAMPLES EXAMPLES:
Initial configuration: Initial configuration:
@ -95,14 +96,19 @@ $ ipfs-cluster-service daemon --bootstrap /ip4/192.168.1.2/tcp/9096/ipfs/QmPSoSa
`, `,
programName, programName,
programName, programName,
DefaultPath, DefaultFolder,
DefaultConfigFile) DefaultConfigFile,
DefaultFolder,
DefaultIdentityFile,
)
var logger = logging.Logger("service") var logger = logging.Logger("service")
// Default location for the configurations and data // Default location for the configurations and data
var ( var (
// DefaultPath is initialized to $HOME/.ipfs-cluster // DefaultFolder is the name of the cluster folder
DefaultFolder = ".ipfs-cluster"
// DefaultPath is set on init() to $HOME/DefaultFolder
// and holds all the ipfs-cluster data // and holds all the ipfs-cluster data
DefaultPath string DefaultPath string
// The name of the configuration file inside DefaultPath // The name of the configuration file inside DefaultPath
@ -135,7 +141,7 @@ func init() {
home = usr.HomeDir home = usr.HomeDir
} }
DefaultPath = filepath.Join(home, ".ipfs-cluster") DefaultPath = filepath.Join(home, DefaultFolder)
} }
func out(m string, a ...interface{}) { func out(m string, a ...interface{}) {
@ -145,7 +151,7 @@ func out(m string, a ...interface{}) {
func checkErr(doing string, err error, args ...interface{}) { func checkErr(doing string, err error, args ...interface{}) {
if err != nil { if err != nil {
if len(args) > 0 { if len(args) > 0 {
doing = fmt.Sprintf(doing, args) doing = fmt.Sprintf(doing, args...)
} }
out("error %s: %s\n", doing, err) out("error %s: %s\n", doing, err)
err = locker.tryUnlock() err = locker.tryUnlock()
@ -207,21 +213,26 @@ func main() {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "init", Name: "init",
Usage: "create a default configuration and exit", Usage: "Creates a configuration and generates an identity",
Description: fmt.Sprintf(` Description: fmt.Sprintf(`
This command will initialize a new service.json configuration file This command will initialize a new %s configuration file and, if it
for %s. does already exist, generate a new %s for %s.
By default, %s requires a cluster secret. This secret will be By default, %s requires a cluster secret. This secret will be
automatically generated, but can be manually provided with --custom-secret automatically generated, but can be manually provided with --custom-secret
(in which case it will be prompted), or by setting the CLUSTER_SECRET (in which case it will be prompted), or by setting the CLUSTER_SECRET
environment variable. environment variable.
The private key for the libp2p node is randomly generated in all cases.
Note that the --force first-level-flag allows to overwrite an existing Note that the --force first-level-flag allows to overwrite an existing
configuration. configuration with default values. To generate a new identity, please
`, programName, programName), remove the %s file first and clean any Raft state.
`,
DefaultConfigFile,
DefaultIdentityFile,
programName,
programName,
DefaultIdentityFile,
),
ArgsUsage: " ", ArgsUsage: " ",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
@ -235,37 +246,35 @@ configuration.
cfgMgr, cfgs := makeConfigs() cfgMgr, cfgs := makeConfigs()
defer cfgMgr.Shutdown() // wait for saves defer cfgMgr.Shutdown() // wait for saves
var alreadyInitialized bool configExists := false
if _, err := os.Stat(configPath); !os.IsNotExist(err) { if _, err := os.Stat(configPath); !os.IsNotExist(err) {
alreadyInitialized = true configExists = true
} }
if alreadyInitialized { identityExists := false
if _, err := os.Stat(identityPath); !os.IsNotExist(err) {
identityExists = true
}
if configExists || identityExists {
// cluster might be running
// acquire lock for config folder // acquire lock for config folder
locker.lock() locker.lock()
defer locker.tryUnlock() defer locker.tryUnlock()
}
if configExists {
confirm := fmt.Sprintf( confirm := fmt.Sprintf(
"%s\n%s Continue? [y/n]:", "%s Continue? [y/n]:",
stateCleanupPrompt,
configurationOverwritePrompt, configurationOverwritePrompt,
) )
if !c.Bool("force") && !yesNoPrompt(confirm) { // --force allows override of the prompt
if !c.GlobalBool("force") {
if !yesNoPrompt(confirm) {
return nil return nil
} }
}
ident := loadIdentity()
err := cfgMgr.LoadJSONFileAndEnv(configPath)
checkErr("reading configuration", err)
// rafts needs cleanup on re-init because
// the peer ID of this peer changes
// and is no longer part of the old
// peerset.
mgr := newStateManager("raft", ident, cfgs)
checkErr("cleaning up raft data", mgr.Clean())
} }
// Generate defaults for all registered components // Generate defaults for all registered components
@ -283,23 +292,24 @@ configuration.
// Save // Save
saveConfig(cfgMgr) saveConfig(cfgMgr)
if !identityExists {
// Create a new identity and save it // Create a new identity and save it
ident, err := config.NewIdentity() ident, err := config.NewIdentity()
checkErr("could not generate a public-private key pair", err) checkErr("generating an identity", err)
err = ident.ApplyEnvVars() err = ident.ApplyEnvVars()
checkErr("could not apply environment variables to the identity ", err) checkErr("applying environment variables to the identity", err)
err = ident.SaveJSON(identityPath) err = ident.SaveJSON(identityPath)
checkErr("could not save identity.json", err) checkErr("saving "+DefaultIdentityFile, err)
out("%s identitry written to %s\n", programName, identityPath) out("new identity written to %s\n", identityPath)
}
return nil return nil
}, },
}, },
{ {
Name: "daemon", Name: "daemon",
Usage: "run the IPFS Cluster peer (default)", Usage: "Runs the IPFS Cluster peer (default)",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "upgrade, u", Name: "upgrade, u",
@ -343,7 +353,7 @@ configuration.
}, },
{ {
Name: "state", Name: "state",
Usage: "Manage the peer's consensus state (pinset)", Usage: "Manages the peer's consensus state (pinset)",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Name: "export", Name: "export",
@ -486,7 +496,7 @@ to all effects. Peers may need to bootstrap and sync from scratch after this.
}, },
{ {
Name: "version", Name: "version",
Usage: "Print the ipfs-cluster version", Usage: "Prints the ipfs-cluster version",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
fmt.Printf("%s\n", version.Version) fmt.Printf("%s\n", version.Version)
return nil return nil

View File

@ -64,7 +64,7 @@ func (ident *Identity) ConfigKey() string {
// SaveJSON saves the JSON representation of the Identity to // SaveJSON saves the JSON representation of the Identity to
// the given path. // the given path.
func (ident *Identity) SaveJSON(path string) error { func (ident *Identity) SaveJSON(path string) error {
logger.Info("Saving configuration") logger.Info("Saving identity")
bs, err := ident.ToJSON() bs, err := ident.ToJSON()
if err != nil { if err != nil {