From 501cf9a46e3a74a0ee0eb6234d335607b685daf8 Mon Sep 17 00:00:00 2001 From: b5 Date: Tue, 3 Apr 2018 08:02:24 -0400 Subject: [PATCH] fix(config): fix issues created by config overhaul changes to config introduced a number of problems that need addressing. Namely profile mismatches between fsrepo & the config profile storage, port mismatches, a few default configuration errors, and some missing / bad error reporting around configuration loading. closes #328, closes #329 --- api/server.go | 30 ++++++++------------------- cmd/config.go | 33 ++++++++++++++++++++++++----- cmd/connect.go | 19 +++++++---------- cmd/repo.go | 8 +++---- cmd/setup.go | 32 +++++++--------------------- config/api.go | 2 +- config/config.go | 23 +++++++++++++++++++++ config/config_test.go | 9 ++++++++ config/profile.go | 47 ++++++++++++++++++++++++++++++++++++++++++ config/profile_test.go | 40 +++++++++++++++++++++++++++++++++++ config/rpc.go | 2 +- config/webapp.go | 2 +- repo/profile/type.go | 22 ++++++++++++-------- 13 files changed, 191 insertions(+), 78 deletions(-) diff --git a/api/server.go b/api/server.go index 2d9987208..578897ef8 100644 --- a/api/server.go +++ b/api/server.go @@ -77,26 +77,10 @@ func New(r repo.Repo, options ...func(*config.Config)) (s *Server, err error) { return s, nil } -// Serve starts the server. It will block while the server -// is running +// Serve starts the server. It will block while the server is running func (s *Server) Serve() (err error) { server := &http.Server{} server.Handler = NewServerRoutes(s) - p, err := s.qriNode.Repo.Profile() - if err != nil { - return err - } - - if s.cfg.API.Enabled { - // log.Info("qri profile id:", s.qriNode.Identity.Pretty()) - info := fmt.Sprintf("connecting to qri:\n peername: %s\n QRI ID: %s\n API port: %s\n IPFS Addreses:", p.Peername, p.ID, s.cfg.API.Port) - for _, a := range s.qriNode.EncapsulatedAddresses() { - info = fmt.Sprintf("%s\n %s", info, a.String()) - } - log.Info(info) - } else { - log.Info("running qri in offline mode, no peer-2-peer connections") - } go s.ServeRPC() go s.ServeWebapp() @@ -111,6 +95,13 @@ func (s *Server) Serve() (err error) { }() } + info := s.cfg.SummaryString() + info += "IPFS Addresses:" + for _, a := range s.qriNode.EncapsulatedAddresses() { + info = fmt.Sprintf("%s\n %s", info, a.String()) + } + log.Info(info) + // http.ListenAndServe will not return unless there's an error return StartServer(s.cfg.API, server) } @@ -134,7 +125,6 @@ func (s *Server) ServeRPC() { } } - log.Infof("accepting RPC requests on port %s", s.cfg.RPC.Port) rpc.Accept(listener) return } @@ -156,8 +146,6 @@ func (s *Server) ServeWebapp() { m := http.NewServeMux() m.Handle("/", s.middleware(s.WebappHandler)) webappserver := &http.Server{Handler: m} - - log.Infof("webapp available on port %s", s.cfg.Webapp.Port) webappserver.Serve(listener) return } @@ -176,7 +164,7 @@ func (s *Server) resolveWebappPath() { } log.Debugf("webapp path: %s", p.String()) s.cfg.Webapp.Scripts = []string{ - fmt.Sprintf("http://localhost:2503%s", p.String()), + fmt.Sprintf("http://localhost:%s%s", s.cfg.API.Port, p.String()), } } diff --git a/cmd/config.go b/cmd/config.go index a77c2f5b4..016a229d8 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -9,8 +9,12 @@ import ( "gopkg.in/yaml.v2" ) -// cfg is the global configuration object for the CLI -var cfg *config.Config +var ( + // cfg is the global configuration object for the CLI + cfg *config.Config + // setting ignoreCfg to true will prevent loadConfig from doing anything + ignoreCfg bool +) // configCmd represents commands that read & modify configuration settings var configCmd = &cobra.Command{ @@ -85,10 +89,29 @@ func init() { RootCmd.AddCommand(configCmd) } -func loadConfig() { - var err error +func loadConfig() (err error) { + if ignoreCfg { + return nil + } + cfg, err = config.ReadFromFile(configFilepath()) + + if err == nil && cfg.Profile == nil { + err = fmt.Errorf("missing profile") + } + if err != nil { - cfg = config.Config{}.Default() + str := `couldn't read config file. error + %s +if you've recently updated qri your config file may no longer be valid. +The easiest way to fix this is to delete your repository at: + %s +and start with a fresh qri install by running 'qri setup' again. +Sorry, we know this is not exactly a great experience, from this point forward +we won't be shipping changes that require starting over. +` + err = fmt.Errorf(str, err.Error(), QriRepoPath) } + + return err } diff --git a/cmd/connect.go b/cmd/connect.go index 595691d04..b82dc9357 100644 --- a/cmd/connect.go +++ b/cmd/connect.go @@ -8,7 +8,7 @@ import ( ) var ( - connectCmdPort string + connectCmdAPIPort string connectCmdRPCPort string connectCmdWebappPort string @@ -32,11 +32,7 @@ things: - Start a local API server When you run connect you are connecting to the distributed web, interacting with -peers & swapping data. - -The default port for the local API server is 2503. We call port 2503, -“the qri port”. It’s a good port, lots of cool numbers in there. Some might even -call it a “prime” port number.`, +peers & swapping data.`, Run: func(cmd *cobra.Command, args []string) { var ( r repo.Repo @@ -52,13 +48,13 @@ call it a “prime” port number.`, s, err := api.New(r, func(c *config.Config) { *c = *cfg - if connectCmdPort != config.DefaultAPIPort { - c.API.Enabled = connectCmdPort != "" - c.API.Port = connectCmdPort + if connectCmdAPIPort != config.DefaultAPIPort { + c.API.Enabled = connectCmdAPIPort != "" + c.API.Port = connectCmdAPIPort } if connectCmdRPCPort != config.DefaultRPCPort { - c.RPC.Enabled = connectCmdPort != "" + c.RPC.Enabled = connectCmdRPCPort != "" c.RPC.Port = connectCmdRPCPort } @@ -67,7 +63,6 @@ call it a “prime” port number.`, c.RPC.Port = connectCmdWebappPort } }) - ExitIfErr(err) err = s.Serve() @@ -76,7 +71,7 @@ call it a “prime” port number.`, } func init() { - connectCmd.Flags().StringVarP(&connectCmdPort, "api-port", "", config.DefaultAPIPort, "port to start api on") + connectCmd.Flags().StringVarP(&connectCmdAPIPort, "api-port", "", config.DefaultAPIPort, "port to start api on") connectCmd.Flags().StringVarP(&connectCmdRPCPort, "rpc-port", "", config.DefaultRPCPort, "port to start rpc listener on") connectCmd.Flags().StringVarP(&connectCmdWebappPort, "webapp-port", "", config.DefaultWebappPort, "port to serve webapp on") connectCmd.Flags().BoolVarP(&connectSetup, "setup", "", false, "run setup if necessary, reading options from enviornment variables") diff --git a/cmd/repo.go b/cmd/repo.go index 6bdb11ae4..054538ae7 100644 --- a/cmd/repo.go +++ b/cmd/repo.go @@ -28,16 +28,16 @@ func getRepo(online bool) repo.Repo { ErrExit(fmt.Errorf("no qri repo found, please run `qri setup`")) } - // cfg, err := readConfigFile() - // ExitIfErr(err) - pk, err := cfg.Profile.DecodePrivateKey() ExitIfErr(err) - fs := getIpfsFilestore(online) + pro, err := cfg.Profile.DecodeProfile() + ExitIfErr(err) + fs := getIpfsFilestore(online) r, err := fsrepo.NewRepo(fs, QriRepoPath, cfg.Profile.ID) r.SetPrivateKey(pk) + r.SetProfile(pro) ExitIfErr(err) diff --git a/cmd/setup.go b/cmd/setup.go index a0912001f..c4cc564e0 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -41,6 +41,9 @@ running setup. If setup has already been run, by default qri won’t let you overwrite this info.`, Example: ` run setup with a peername of your choosing: $ qri setup --peername=your_great_peername`, + PreRun: func(cmd *cobra.Command, args []string) { + ignoreCfg = true + }, Run: func(cmd *cobra.Command, args []string) { // var cfgData []byte @@ -60,7 +63,7 @@ overwrite this info.`, } mapEnvVars(envVars) - // // if cfgFile is specified, override + // if cfgFile is specified, override // if cfgFile != "" { // f, err := os.Open(cfgFile) // ExitIfErr(err) @@ -77,11 +80,6 @@ overwrite this info.`, ExitIfErr(err) } - // cfg = &config.Config{} - // err := yaml.Unmarshal(cfgData, cfg) - // ExitIfErr(err) - // loadConfig() - // TODO - re-enable // err = cfg.ensurePrivateKey() // ExitIfErr(err) @@ -92,9 +90,6 @@ overwrite this info.`, err := cfg.WriteToFile(configFilepath()) ExitIfErr(err) - // err = viper.ReadInConfig() - // ExitIfErr(err) - if setupIPFS { tmpIPFSConfigPath := "" @@ -122,15 +117,6 @@ overwrite this info.`, printWarning("no IPFS repo exists at %s, things aren't going to work properly", IpfsFsPath) } - // p := &core.Profile{} - // if setupProfileData != "" { - // err = readAtFile(&setupProfileData) - // ExitIfErr(err) - // err = json.Unmarshal([]byte(setupProfileData), p) - // ExitIfErr(err) - // } else { - // } - anon, err := cmd.Flags().GetBool("anonymous") ExitIfErr(err) if setupPeername == "" && !anon { @@ -141,12 +127,10 @@ overwrite this info.`, err = cfg.WriteToFile(configFilepath()) ExitIfErr(err) - // loadConfig() - // pr, err := profileRequests(false) - // ExitIfErr(err) - // res := &core.Profile{} - // err = pr.SavePeername(p, res) - // err = pr.SaveProfile(p, res) + // call SetProfile to give repo a chance to save updated profile data + pro, err := cfg.Profile.DecodeProfile() + ExitIfErr(err) + getRepo(false).SetProfile(pro) ExitIfErr(err) }, diff --git a/config/api.go b/config/api.go index 90aa692a8..2193b5a72 100644 --- a/config/api.go +++ b/config/api.go @@ -26,7 +26,7 @@ func (API) Default() *API { return &API{ Enabled: true, Port: DefaultAPIPort, - TLS: true, + TLS: false, AllowedOrigins: []string{ "http://localhost:2505", }, diff --git a/config/config.go b/config/config.go index 306ab2543..c7390f912 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,29 @@ func (Config) Default() *Config { } } +// SummaryString creates a pretty string summarizing the +// configuration, useful for log output +func (cfg Config) SummaryString() (summary string) { + summary = "\n" + if cfg.Profile != nil { + summary += fmt.Sprintf("peername:\t%s\nprofileID:\t%s\n", cfg.Profile.Peername, cfg.Profile.ID) + } + + if cfg.API != nil && cfg.API.Enabled { + summary += fmt.Sprintf("API port:\t%s\n", cfg.API.Port) + } + + if cfg.RPC != nil && cfg.RPC.Enabled { + summary += fmt.Sprintf("RPC port:\t%s\n", cfg.RPC.Port) + } + + if cfg.Webapp != nil && cfg.Webapp.Enabled { + summary += fmt.Sprintf("Webapp port:\t%s\n", cfg.Webapp.Port) + } + + return summary +} + // ReadFromFile reads a YAML configuration file from path func ReadFromFile(path string) (cfg *Config, err error) { var data []byte diff --git a/config/config_test.go b/config/config_test.go index 81063b8f8..507aaa734 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "testing" ) @@ -36,6 +37,14 @@ func TestWriteToFile(t *testing.T) { } } +func TestConfigSummaryString(t *testing.T) { + summary := Config{}.Default().SummaryString() + t.Log(summary) + if !strings.Contains(summary, "API") { + t.Errorf("expected summary to list API port") + } +} + func TestConfigGet(t *testing.T) { cfg := Config{}.Default() cases := []struct { diff --git a/config/profile.go b/config/profile.go index 7d4619c0e..7ca9893a7 100644 --- a/config/profile.go +++ b/config/profile.go @@ -4,7 +4,9 @@ import ( "crypto/rand" "encoding/base64" "fmt" + "time" + "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p-crypto" "github.com/libp2p/go-libp2p-peer" "github.com/qri-io/doggos" @@ -16,6 +18,10 @@ type Profile struct { ID string PrivKey string Peername string + // Created timestamp + Created time.Time + // Updated timestamp + Updated time.Time // specifies weather this is a user or an organization Type string // user's email address @@ -61,6 +67,47 @@ func (Profile) Default() *Profile { return p } +// DecodeProfile turns a cfg.Profile into a profile.Profile +func (cfg *Profile) DecodeProfile() (*profile.Profile, error) { + id, err := profile.IDB58Decode(cfg.ID) + if err != nil { + return nil, err + } + + t, err := profile.ParseType(cfg.Type) + if err != nil { + return nil, err + } + + p := &profile.Profile{ + ID: id, + Type: t, + Peername: cfg.Peername, + Created: cfg.Created, + Updated: cfg.Updated, + Email: cfg.Email, + Name: cfg.Name, + Description: cfg.Description, + HomeURL: cfg.HomeURL, + Color: cfg.Color, + Twitter: cfg.Twitter, + } + + if cfg.Thumb != "" { + p.Thumb = datastore.NewKey(cfg.Thumb) + } + + if cfg.Poster != "" { + p.Poster = datastore.NewKey(cfg.Poster) + } + + if cfg.Profile != "" { + p.Profile = datastore.NewKey(cfg.Profile) + } + + return p, nil +} + // DecodePrivateKey generates a PrivKey instance from base64-encoded config file bytes func (cfg *Profile) DecodePrivateKey() (crypto.PrivKey, error) { if cfg.PrivKey == "" { diff --git a/config/profile_test.go b/config/profile_test.go index cbfbe3895..fc46a0ef6 100644 --- a/config/profile_test.go +++ b/config/profile_test.go @@ -4,6 +4,46 @@ import ( "testing" ) +func TestProfileDecodeProfile(t *testing.T) { + p := &Profile{} + _, err := p.DecodeProfile() + if err == nil { + t.Errorf("expected missing ID to error") + } + + p = &Profile{ + ID: "QmTwtwLMKHHKCrugNxyAaZ31nhBqRUQVysT2xK911n4m6F", + Type: "dinosaur", + } + + _, err = p.DecodeProfile() + if err == nil { + t.Errorf("expected invalid type to error") + } + + p = &Profile{ + ID: "QmTwtwLMKHHKCrugNxyAaZ31nhBqRUQVysT2xK911n4m6F", + Poster: "foo", + Profile: "bar", + Thumb: "baz", + } + + pro, err := p.DecodeProfile() + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + + if pro.Poster.String() != "/foo" { + t.Error("poster mismatch") + } + if pro.Profile.String() != "/bar" { + t.Error("profile mismatch") + } + if pro.Thumb.String() != "/baz" { + t.Error("thumb mismatch") + } +} + func TestProfileDecodePrivateKey(t *testing.T) { missingErr := "missing private key" p := &Profile{} diff --git a/config/rpc.go b/config/rpc.go index c619044a7..d64a70042 100644 --- a/config/rpc.go +++ b/config/rpc.go @@ -7,7 +7,7 @@ type RPC struct { } // DefaultRPCPort is local the port RPC serves on by default -var DefaultRPCPort = "2505" +var DefaultRPCPort = "2504" // Default creates a new default RPC configuration func (RPC) Default() *RPC { diff --git a/config/webapp.go b/config/webapp.go index 13343e521..c5c954c4a 100644 --- a/config/webapp.go +++ b/config/webapp.go @@ -23,7 +23,7 @@ func (Webapp) Default() *Webapp { // this is fetched and replaced via dnslink when the webapp server starts // the value provided here is just a sensible fallback for when dnslink lookup fails, // pointing to a known prior version of the the webapp - "http://localhost:2503/ipfs/QmYDkLrvzDpzDzKeLD3okiQUzSL1ksNsfYU6ZwRYYn8ViS", + "http://localhost:2503/ipfs/QmUnnihBehFsCRGNjAEiMtYL3p8diBXNhM3DCZwhSq98UJ", }, } } diff --git a/repo/profile/type.go b/repo/profile/type.go index 1caaa15b7..128bed7fe 100644 --- a/repo/profile/type.go +++ b/repo/profile/type.go @@ -27,6 +27,16 @@ func (t Type) String() string { return "unknown" } +// ParseType decodes a peer type from a string +func ParseType(t string) (Type, error) { + got, ok := map[string]Type{"": TypePeer, "user": TypePeer, "peer": TypePeer, "organization": TypeOrganization}[t] + if !ok { + return TypePeer, fmt.Errorf("invalid Type %q", t) + } + + return got, nil +} + // MarshalJSON implements the json.Marshaler interface for Type func (t Type) MarshalJSON() ([]byte, error) { s, ok := map[Type]string{TypePeer: "peer", TypeOrganization: "organization"}[t] @@ -38,17 +48,11 @@ func (t Type) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements the json.Unmarshaler interface for Type -func (t *Type) UnmarshalJSON(data []byte) error { +func (t *Type) UnmarshalJSON(data []byte) (err error) { var s string if err := json.Unmarshal(data, &s); err != nil { return fmt.Errorf("Peer type should be a string, got %s", data) } - - got, ok := map[string]Type{"user": TypePeer, "peer": TypePeer, "organization": TypeOrganization}[s] - if !ok { - return fmt.Errorf("invalid Type %q", s) - } - - *t = got - return nil + *t, err = ParseType(s) + return err }