diff --git a/cli/lakeflags/datadir.go b/cli/lakeflags/datadir.go new file mode 100644 index 0000000000..1f3755e398 --- /dev/null +++ b/cli/lakeflags/datadir.go @@ -0,0 +1,30 @@ +package lakeflags + +import ( + "os" + "path/filepath" + "runtime" +) + +// getDefaultDataDir returns the default data directory for the current user. +// Derived from https://github.com/btcsuite/btcd/blob/master/btcutil/appdata.go +func getDefaultDataDir() string { + // Resolve the XDG data home directory if set. + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return filepath.Join(xdgDataHome, "zed") + } + if runtime.GOOS == "windows" { + if appData := os.Getenv("LOCALAPPDATA"); appData != "" { + return filepath.Join(appData, "zed") + } + } + if homeDir, _ := os.UserHomeDir(); homeDir != "" { + // Follow the XDG spec which states: + // If $XDG_DATA_HOME is either not set or empty, a default equal to + // $HOME/.local/share should be used. + return filepath.Join(homeDir, ".local", "share", "zed") + } + // Return an empty string which will cause an error if a default data + // directory cannot be found. + return "" +} diff --git a/cli/lakeflags/flags.go b/cli/lakeflags/flags.go index b4678fba3c..c1852170a0 100644 --- a/cli/lakeflags/flags.go +++ b/cli/lakeflags/flags.go @@ -21,11 +21,10 @@ var ErrNoHEAD = errors.New("HEAD not specified: indicate with -use or run the \" type Flags struct { ConfigDir string - // LakeSpecified is set to true if the lake is explicitly set via either - // command line flag or environment variable. - LakeSpecified bool - Lake string - Quiet bool + Lake string + Quiet bool + + lakeSpecified bool } func (l *Flags) SetFlags(fs *flag.FlagSet) { @@ -35,20 +34,17 @@ func (l *Flags) SetFlags(fs *flag.FlagSet) { dir = filepath.Join(dir, ".zed") } fs.StringVar(&l.ConfigDir, "configdir", dir, "configuration and credentials directory") - l.Lake = "http://localhost:9867" if s, ok := os.LookupEnv("ZED_LAKE"); ok { - l.Lake = strings.TrimRight(s, "/") - l.LakeSpecified = true + l.Lake, l.lakeSpecified = s, true } fs.Func("lake", fmt.Sprintf("lake location (env ZED_LAKE) (default %s)", l.Lake), func(s string) error { - l.Lake = strings.TrimRight(s, "/") - l.LakeSpecified = true + l.Lake, l.lakeSpecified = s, true return nil }) } func (l *Flags) Connection() (*client.Connection, error) { - uri, err := l.URI() + uri, err := l.ClientURI() if err != nil { return nil, err } @@ -63,7 +59,7 @@ func (l *Flags) Connection() (*client.Connection, error) { } func (l *Flags) Open(ctx context.Context) (api.Interface, error) { - uri, err := l.URI() + uri, err := l.ClientURI() if err != nil { return nil, err } @@ -86,12 +82,35 @@ func (l *Flags) AuthStore() *auth0.Store { } func (l *Flags) URI() (*storage.URI, error) { - if l.Lake == "" { + lk := strings.TrimRight(l.Lake, "/") + if !l.lakeSpecified { + lk = getDefaultDataDir() + } + if lk == "" { return nil, errors.New("lake location must be set (either with the -lake flag or ZED_LAKE environment variable)") } - u, err := storage.ParseURI(l.Lake) + u, err := storage.ParseURI(lk) if err != nil { err = fmt.Errorf("error parsing lake location: %w", err) } return u, err } + +// ClientURI returns the URI of the lake to connect to. If the lake path is +// the defaultDataDir, it first checks if a zed service is running on +// localhost:9867 and if so uses http://localhost:9867 as the lake location. +func (l *Flags) ClientURI() (*storage.URI, error) { + u, err := l.URI() + if err != nil { + return nil, err + } + if !l.lakeSpecified && localServer() { + u = storage.MustParseURI("http://localhost:9867") + } + return u, nil +} + +func localServer() bool { + _, err := client.NewConnection().Ping(context.Background()) + return err == nil +} diff --git a/cmd/zed/init/command.go b/cmd/zed/init/command.go index 32e9870b50..16a4462574 100644 --- a/cmd/zed/init/command.go +++ b/cmd/zed/init/command.go @@ -8,6 +8,7 @@ import ( "github.com/brimdata/zed/cmd/zed/root" "github.com/brimdata/zed/lake/api" "github.com/brimdata/zed/pkg/charm" + "github.com/brimdata/zed/pkg/storage" "go.uber.org/zap" ) @@ -35,23 +36,28 @@ func (c *Command) Run(args []string) error { return err } defer cleanup() - var path string + var u *storage.URI if len(args) == 0 { - path = c.LakeFlags.Lake + if u, err = c.LakeFlags.URI(); err != nil { + return err + } } else if len(args) == 1 { - path = args[0] + path := args[0] + if path == "" { + return errors.New("single lake path argument required") + } + if u, err = storage.ParseURI(path); err != nil { + return err + } } - if path == "" { - return errors.New("single lake path argument required") - } - if api.IsLakeService(path) { + if api.IsLakeService(u.String()) { return fmt.Errorf("init command not valid on remote lake") } - if _, err := api.CreateLocalLake(ctx, zap.Must(zap.NewProduction()), path); err != nil { + if _, err := api.CreateLocalLake(ctx, zap.Must(zap.NewProduction()), u.String()); err != nil { return err } if !c.LakeFlags.Quiet { - fmt.Printf("lake created: %s\n", path) + fmt.Printf("lake created: %s\n", u) } return nil } diff --git a/cmd/zed/serve/command.go b/cmd/zed/serve/command.go index 4caf08458c..d6e42c8174 100644 --- a/cmd/zed/serve/command.go +++ b/cmd/zed/serve/command.go @@ -73,17 +73,12 @@ func (c *Command) Run(args []string) error { return err } defer cleanup() - if !c.LakeFlags.LakeSpecified { - c.LakeFlags.Lake = "" - } - uri, err := c.LakeFlags.URI() - if err != nil { + if c.conf.Root, err = c.LakeFlags.URI(); err != nil { return err } - if api.IsLakeService(uri.String()) { + if api.IsLakeService(c.conf.Root.String()) { return errors.New("serve command available for local lakes only") } - c.conf.Root = uri if c.rootContentFile != "" { f, err := fs.Open(c.rootContentFile) if err != nil { diff --git a/cmd/zed/use/command.go b/cmd/zed/use/command.go index 527123c247..6923aeecb7 100644 --- a/cmd/zed/use/command.go +++ b/cmd/zed/use/command.go @@ -84,7 +84,7 @@ func (c *Command) Run(args []string) error { return errors.New("default pool and branch unset") } fmt.Printf("HEAD at %s\n", head) - if u, err := c.LakeFlags.URI(); err == nil { + if u, err := c.LakeFlags.ClientURI(); err == nil { fmt.Printf("Lake at %s\n", u) } return nil diff --git a/cmd/zed/ztests/no-lake-location.yaml b/cmd/zed/ztests/no-lake-location.yaml index cedc088f87..0fade08661 100644 --- a/cmd/zed/ztests/no-lake-location.yaml +++ b/cmd/zed/ztests/no-lake-location.yaml @@ -1,6 +1,6 @@ script: | ! zed ls -lake '' - ! zed serve + ! zed serve -lake '' outputs: - name: stderr diff --git a/cmd/zed/ztests/xdg-data-home.yaml b/cmd/zed/ztests/xdg-data-home.yaml new file mode 100644 index 0000000000..ad080d453c --- /dev/null +++ b/cmd/zed/ztests/xdg-data-home.yaml @@ -0,0 +1,8 @@ +script: | + export XDG_DATA_HOME=path/to/lake + zed init + +outputs: + - name: stdout + regexp: | + lake created: file:.*path/to/lake/zed diff --git a/compiler/ztests/from-error.yaml b/compiler/ztests/from-error.yaml index 209544a444..c40e85b392 100644 --- a/compiler/ztests/from-error.yaml +++ b/compiler/ztests/from-error.yaml @@ -1,5 +1,5 @@ script: | - ! zc -C -s 'from p' + ! zc -lake='' -C -s 'from p' echo === >&2 export ZED_LAKE=test zed init