diff --git a/cmd/labs/clear_cache.go b/cmd/labs/clear_cache.go index e2f531cf..e136c13c 100644 --- a/cmd/labs/clear_cache.go +++ b/cmd/labs/clear_cache.go @@ -19,13 +19,17 @@ func newClearCacheCommand() *cobra.Command { if err != nil { return err } - _ = os.Remove(project.PathInLabs(ctx, "databrickslabs-repositories.json")) + cache, err := project.PathInLabs(ctx, "databrickslabs-repositories.json") + if err != nil { + return err + } + _ = os.Remove(cache) logger := log.GetLogger(ctx) for _, prj := range projects { logger.Info("clearing labs project cache", slog.String("name", prj.Name)) - _ = os.RemoveAll(prj.CacheDir(ctx)) + _ = os.RemoveAll(prj.CacheDir()) // recreating empty cache folder for downstream apps to work normally - _ = prj.EnsureFoldersExist(ctx) + _ = prj.EnsureFoldersExist() } return nil }, diff --git a/cmd/labs/labs.go b/cmd/labs/labs.go index cccf8ac4..c8c8546a 100644 --- a/cmd/labs/labs.go +++ b/cmd/labs/labs.go @@ -4,6 +4,7 @@ import ( "context" "github.com/databricks/cli/cmd/labs/project" + "github.com/databricks/cli/libs/log" "github.com/spf13/cobra" ) @@ -30,7 +31,8 @@ func New(ctx context.Context) *cobra.Command { ) all, err := project.Installed(ctx) if err != nil { - panic(err) + log.Errorf(ctx, "Cannot retrieve installed labs: %s", err) + return cmd } for _, v := range all { v.Register(cmd) diff --git a/cmd/labs/list.go b/cmd/labs/list.go index 07cc180c..b9624cbb 100644 --- a/cmd/labs/list.go +++ b/cmd/labs/list.go @@ -16,7 +16,10 @@ type labsMeta struct { } func allRepos(ctx context.Context) (github.Repositories, error) { - cacheDir := project.PathInLabs(ctx) + cacheDir, err := project.PathInLabs(ctx) + if err != nil { + return nil, err + } cache := github.NewRepositoryCache("databrickslabs", cacheDir) return cache.Load(ctx) } diff --git a/cmd/labs/project/entrypoint.go b/cmd/labs/project/entrypoint.go index fedd70a4..113bf321 100644 --- a/cmd/labs/project/entrypoint.go +++ b/cmd/labs/project/entrypoint.go @@ -54,15 +54,15 @@ func (e *Entrypoint) NeedsWarehouse() bool { func (e *Entrypoint) Prepare(cmd *cobra.Command) (map[string]string, error) { ctx := cmd.Context() - libDir := e.EffectiveLibDir(ctx) + libDir := e.EffectiveLibDir() environment := map[string]string{ "DATABRICKS_CLI_VERSION": build.GetInfo().Version, - "DATABRICKS_LABS_CACHE_DIR": e.CacheDir(ctx), - "DATABRICKS_LABS_CONFIG_DIR": e.ConfigDir(ctx), - "DATABRICKS_LABS_STATE_DIR": e.StateDir(ctx), + "DATABRICKS_LABS_CACHE_DIR": e.CacheDir(), + "DATABRICKS_LABS_CONFIG_DIR": e.ConfigDir(), + "DATABRICKS_LABS_STATE_DIR": e.StateDir(), "DATABRICKS_LABS_LIB_DIR": libDir, } - if e.IsPythonProject(ctx) { + if e.IsPythonProject() { e.preparePython(ctx, environment) } cfg, err := e.validLogin(cmd) @@ -112,7 +112,7 @@ func (e *Entrypoint) preparePython(ctx context.Context, environment map[string]s // Here we are also supporting the "src" layout for python projects. // // See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH - libDir := e.EffectiveLibDir(ctx) + libDir := e.EffectiveLibDir() // The intention for every install is to be sandboxed - not dependent on anything else than Python binary. // Having ability to override PYTHONPATH in the mix will break this assumption. Need strong evidence that // this is really needed. @@ -139,21 +139,28 @@ func (e *Entrypoint) joinPaths(paths ...string) string { return strings.Join(paths, string(os.PathListSeparator)) } -func (e *Entrypoint) envAwareConfig(ctx context.Context) *config.Config { +func (e *Entrypoint) envAwareConfig(ctx context.Context) (*config.Config, error) { + home, err := env.UserHomeDir(ctx) + if err != nil { + return nil, err + } return &config.Config{ - ConfigFile: filepath.Join(env.UserHomeDir(ctx), ".databrickscfg"), + ConfigFile: filepath.Join(home, ".databrickscfg"), Loaders: []config.Loader{ env.NewConfigLoader(ctx), config.ConfigAttributes, config.ConfigFile, }, - } + }, nil } -func (e *Entrypoint) envAwareConfigWithProfile(ctx context.Context, profile string) *config.Config { - cfg := e.envAwareConfig(ctx) +func (e *Entrypoint) envAwareConfigWithProfile(ctx context.Context, profile string) (*config.Config, error) { + cfg, err := e.envAwareConfig(ctx) + if err != nil { + return nil, err + } cfg.Profile = profile - return cfg + return cfg, nil } func (e *Entrypoint) getLoginConfig(cmd *cobra.Command) (*loginConfig, *config.Config, error) { @@ -164,11 +171,18 @@ func (e *Entrypoint) getLoginConfig(cmd *cobra.Command) (*loginConfig, *config.C profileOverride := e.profileOverride(cmd) if profileOverride != "" { log.Infof(ctx, "Overriding login profile: %s", profileOverride) - return &loginConfig{}, e.envAwareConfigWithProfile(ctx, profileOverride), nil + cfg, err := e.envAwareConfigWithProfile(ctx, profileOverride) + if err != nil { + return nil, nil, err + } + return &loginConfig{}, cfg, nil } lc, err := e.loadLoginConfig(ctx) isNoLoginConfig := errors.Is(err, fs.ErrNotExist) - defaultConfig := e.envAwareConfig(ctx) + defaultConfig, err := e.envAwareConfig(ctx) + if err != nil { + return nil, nil, err + } if isNoLoginConfig && !e.IsBundleAware && e.isAuthConfigured(defaultConfig) { log.Debugf(ctx, "Login is configured via environment variables") return &loginConfig{}, defaultConfig, nil @@ -181,7 +195,11 @@ func (e *Entrypoint) getLoginConfig(cmd *cobra.Command) (*loginConfig, *config.C } if e.IsAccountLevel { log.Debugf(ctx, "Using account-level login profile: %s", lc.AccountProfile) - return lc, e.envAwareConfigWithProfile(ctx, lc.AccountProfile), nil + cfg, err := e.envAwareConfigWithProfile(ctx, lc.AccountProfile) + if err != nil { + return nil, nil, err + } + return lc, cfg, nil } if e.IsBundleAware { err = root.TryConfigureBundle(cmd, []string{}) @@ -194,7 +212,11 @@ func (e *Entrypoint) getLoginConfig(cmd *cobra.Command) (*loginConfig, *config.C } } log.Debugf(ctx, "Using workspace-level login profile: %s", lc.WorkspaceProfile) - return lc, e.envAwareConfigWithProfile(ctx, lc.WorkspaceProfile), nil + cfg, err := e.envAwareConfigWithProfile(ctx, lc.WorkspaceProfile) + if err != nil { + return nil, nil, err + } + return lc, cfg, nil } func (e *Entrypoint) validLogin(cmd *cobra.Command) (*config.Config, error) { diff --git a/cmd/labs/project/fetcher.go b/cmd/labs/project/fetcher.go index b677bcd9..8f4fafde 100644 --- a/cmd/labs/project/fetcher.go +++ b/cmd/labs/project/fetcher.go @@ -29,7 +29,10 @@ func (d *devInstallation) Install(ctx context.Context) error { } _, err := d.Installer.validLogin(d.Command) if errors.Is(err, ErrNoLoginConfig) { - cfg := d.Installer.envAwareConfig(ctx) + cfg, err := d.Installer.envAwareConfig(ctx) + if err != nil { + return err + } lc := &loginConfig{Entrypoint: d.Installer.Entrypoint} _, err = lc.askWorkspace(ctx, cfg) if err != nil { @@ -39,7 +42,7 @@ func (d *devInstallation) Install(ctx context.Context) error { if err != nil { return fmt.Errorf("ask for account: %w", err) } - err = lc.EnsureFoldersExist(ctx) + err = lc.EnsureFoldersExist() if err != nil { return fmt.Errorf("folders: %w", err) } @@ -97,7 +100,10 @@ func NewUpgrader(cmd *cobra.Command, name string) (*installer, error) { if err != nil { return nil, fmt.Errorf("remote: %w", err) } - prj.folder = PathInLabs(cmd.Context(), name) + prj.folder, err = PathInLabs(cmd.Context(), name) + if err != nil { + return nil, err + } return &installer{ Project: prj, version: version, @@ -111,7 +117,10 @@ type fetcher struct { func (f *fetcher) checkReleasedVersions(cmd *cobra.Command, version string) (string, error) { ctx := cmd.Context() - cacheDir := PathInLabs(ctx, f.name, "cache") + cacheDir, err := PathInLabs(ctx, f.name, "cache") + if err != nil { + return "", err + } // `databricks labs isntall X` doesn't know which exact version to fetch, so first // we fetch all versions and then pick the latest one dynamically. versions, err := github.NewReleaseCache("databrickslabs", f.name, cacheDir).Load(ctx) diff --git a/cmd/labs/project/helpers.go b/cmd/labs/project/helpers.go index 9117d875..118c0ff0 100644 --- a/cmd/labs/project/helpers.go +++ b/cmd/labs/project/helpers.go @@ -12,10 +12,13 @@ import ( "github.com/databricks/cli/libs/env" ) -func PathInLabs(ctx context.Context, dirs ...string) string { - homdeDir := env.UserHomeDir(ctx) - prefix := []string{homdeDir, ".databricks", "labs"} - return filepath.Join(append(prefix, dirs...)...) +func PathInLabs(ctx context.Context, dirs ...string) (string, error) { + homeDir, err := env.UserHomeDir(ctx) + if err != nil { + return "", err + } + prefix := []string{homeDir, ".databricks", "labs"} + return filepath.Join(append(prefix, dirs...)...), nil } func tryLoadAndParseJSON[T any](jsonFile string) (*T, error) { diff --git a/cmd/labs/project/installed.go b/cmd/labs/project/installed.go index 77fee544..9a98a780 100644 --- a/cmd/labs/project/installed.go +++ b/cmd/labs/project/installed.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/databricks/cli/folders" + "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" ) @@ -26,7 +27,13 @@ func projectInDevMode(ctx context.Context) (*Project, error) { } func Installed(ctx context.Context) (projects []*Project, err error) { - labsDir, err := os.ReadDir(PathInLabs(ctx)) + root, err := PathInLabs(ctx) + if errors.Is(err, env.ErrNoHomeEnv) { + return nil, nil + } else if err != nil { + return nil, err + } + labsDir, err := os.ReadDir(root) if err != nil && !errors.Is(err, fs.ErrNotExist) { return nil, err } @@ -44,7 +51,7 @@ func Installed(ctx context.Context) (projects []*Project, err error) { if projectDev != nil && v.Name() == projectDev.Name { continue } - labsYml := PathInLabs(ctx, v.Name(), "lib", "labs.yml") + labsYml := filepath.Join(root, v.Name(), "lib", "labs.yml") prj, err := Load(ctx, labsYml) if errors.Is(err, fs.ErrNotExist) { continue diff --git a/cmd/labs/project/installer.go b/cmd/labs/project/installer.go index 2e09ed37..fa676819 100644 --- a/cmd/labs/project/installer.go +++ b/cmd/labs/project/installer.go @@ -55,7 +55,7 @@ func (h *hook) runHook(cmd *cobra.Command) error { if err != nil { return fmt.Errorf("prepare: %w", err) } - libDir := h.EffectiveLibDir(ctx) + libDir := h.EffectiveLibDir() args := []string{} if strings.HasSuffix(h.Script, ".py") { args = append(args, h.virtualEnvPython(ctx)) @@ -80,14 +80,20 @@ type installer struct { } func (i *installer) Install(ctx context.Context) error { - err := i.EnsureFoldersExist(ctx) + err := i.EnsureFoldersExist() if err != nil { return fmt.Errorf("folders: %w", err) } - i.folder = PathInLabs(ctx, i.Name) + i.folder, err = PathInLabs(ctx, i.Name) + if err != nil { + return err + } w, err := i.login(ctx) if err != nil && errors.Is(err, databrickscfg.ErrNoConfiguration) { - cfg := i.Installer.envAwareConfig(ctx) + cfg, err := i.Installer.envAwareConfig(ctx) + if err != nil { + return err + } w, err = databricks.NewWorkspaceClient((*databricks.Config)(cfg)) if err != nil { return fmt.Errorf("no ~/.databrickscfg: %w", err) @@ -138,7 +144,7 @@ func (i *installer) warningf(text string, v ...any) { } func (i *installer) cleanupLib(ctx context.Context) error { - libDir := i.LibDir(ctx) + libDir := i.LibDir() err := os.RemoveAll(libDir) if err != nil { return fmt.Errorf("remove all: %w", err) @@ -157,7 +163,10 @@ func (i *installer) login(ctx context.Context) (*databricks.WorkspaceClient, err } cfg, err := i.metaEntrypoint(ctx).validLogin(i.cmd) if errors.Is(err, ErrNoLoginConfig) { - cfg = i.Installer.envAwareConfig(ctx) + cfg, err = i.Installer.envAwareConfig(ctx) + if err != nil { + return nil, err + } } else if err != nil { return nil, fmt.Errorf("valid: %w", err) } @@ -188,7 +197,7 @@ func (i *installer) downloadLibrary(ctx context.Context) error { if err != nil { return fmt.Errorf("cleanup: %w", err) } - libTarget := i.LibDir(ctx) + libTarget := i.LibDir() // we may support wheels, jars, and golang binaries. but those are not zipballs if i.IsZipball() { feedback <- fmt.Sprintf("Downloading and unpacking zipball for %s", i.version) @@ -254,10 +263,10 @@ func (i *installer) setupPythonVirtualEnvironment(ctx context.Context, w *databr } func (i *installer) installPythonDependencies(ctx context.Context, spec string) error { - if !i.IsPythonProject(ctx) { + if !i.IsPythonProject() { return nil } - libDir := i.LibDir(ctx) + libDir := i.LibDir() log.Debugf(ctx, "Installing Python dependencies for: %s", libDir) // maybe we'll need to add call one of the two scripts: // - python3 -m ensurepip --default-pip @@ -281,6 +290,6 @@ func (i *installer) runInstallHook(ctx context.Context) error { if i.Installer.Script == "" { return nil } - log.Debugf(ctx, "Launching installer script %s in %s", i.Installer.Script, i.LibDir(ctx)) + log.Debugf(ctx, "Launching installer script %s in %s", i.Installer.Script, i.LibDir()) return i.Installer.runHook(i.cmd) } diff --git a/cmd/labs/project/installer_test.go b/cmd/labs/project/installer_test.go index b61026f2..60af43c6 100644 --- a/cmd/labs/project/installer_test.go +++ b/cmd/labs/project/installer_test.go @@ -105,7 +105,7 @@ func installerContext(t *testing.T, server *httptest.Server) context.Context { ctx = github.WithUserContentOverride(ctx, server.URL) ctx = env.WithUserHomeDir(ctx, t.TempDir()) // trick release cache to thing it went to github already - cachePath := project.PathInLabs(ctx, "blueprint", "cache") + cachePath, _ := project.PathInLabs(ctx, "blueprint", "cache") err := os.MkdirAll(cachePath, ownerRWXworldRX) require.NoError(t, err) bs := []byte(`{"refreshed_at": "2033-01-01T00:00:00.92857+02:00","data": [{"tag_name": "v0.3.15"}]}`) @@ -317,8 +317,8 @@ func TestInstallerWorksForDevelopment(t *testing.T) { // development installer assumes it's in the active virtualenv ctx = env.Set(ctx, "PYTHON_BIN", py) - - err = os.WriteFile(filepath.Join(env.UserHomeDir(ctx), ".databrickscfg"), []byte(fmt.Sprintf(` + home, _ := env.UserHomeDir(ctx) + err = os.WriteFile(filepath.Join(home, ".databrickscfg"), []byte(fmt.Sprintf(` [profile-one] host = %s token = ... @@ -399,7 +399,7 @@ func TestUpgraderWorksForReleases(t *testing.T) { py, _ = filepath.Abs(py) ctx = env.Set(ctx, "PYTHON_BIN", py) - cachePath := project.PathInLabs(ctx, "blueprint", "cache") + cachePath, _ := project.PathInLabs(ctx, "blueprint", "cache") bs := []byte(`{"refreshed_at": "2033-01-01T00:00:00.92857+02:00","data": [{"tag_name": "v0.4.0"}]}`) err := os.WriteFile(filepath.Join(cachePath, "databrickslabs-blueprint-releases.json"), bs, ownerRW) require.NoError(t, err) diff --git a/cmd/labs/project/project.go b/cmd/labs/project/project.go index 6adf9a3c..75f5e584 100644 --- a/cmd/labs/project/project.go +++ b/cmd/labs/project/project.go @@ -49,6 +49,11 @@ func readFromBytes(ctx context.Context, labsYmlRaw []byte) (*Project, error) { if project.Uninstaller != nil { project.Uninstaller.Entrypoint = e } + rootDir, err := PathInLabs(ctx, project.Name) + if err != nil { + return nil, err + } + project.rootDir = rootDir return &project, nil } @@ -63,7 +68,8 @@ type Project struct { MinPython string `yaml:"min_python"` Commands []*proxy `yaml:"commands,omitempty"` - folder string + folder string + rootDir string } func (p *Project) IsZipball() bool { @@ -108,22 +114,22 @@ func (p *Project) fileExists(name string) bool { return err == nil } -func (p *Project) projectFilePath(ctx context.Context, name string) string { - return filepath.Join(p.EffectiveLibDir(ctx), name) +func (p *Project) projectFilePath(name string) string { + return filepath.Join(p.EffectiveLibDir(), name) } -func (p *Project) IsPythonProject(ctx context.Context) bool { - if p.fileExists(p.projectFilePath(ctx, "setup.py")) { +func (p *Project) IsPythonProject() bool { + if p.fileExists(p.projectFilePath("setup.py")) { return true } - if p.fileExists(p.projectFilePath(ctx, "pyproject.toml")) { + if p.fileExists(p.projectFilePath("pyproject.toml")) { return true } return false } -func (p *Project) IsDeveloperMode(ctx context.Context) bool { - return p.folder != "" && !strings.HasPrefix(p.LibDir(ctx), p.folder) +func (p *Project) IsDeveloperMode() bool { + return p.folder != "" && !strings.HasPrefix(p.LibDir(), p.folder) } func (p *Project) HasFolder() bool { @@ -161,36 +167,32 @@ func (p *Project) Register(parent *cobra.Command) { } } -func (p *Project) rootDir(ctx context.Context) string { - return PathInLabs(ctx, p.Name) +func (p *Project) CacheDir() string { + return filepath.Join(p.rootDir, "cache") } -func (p *Project) CacheDir(ctx context.Context) string { - return filepath.Join(p.rootDir(ctx), "cache") +func (p *Project) ConfigDir() string { + return filepath.Join(p.rootDir, "config") } -func (p *Project) ConfigDir(ctx context.Context) string { - return filepath.Join(p.rootDir(ctx), "config") +func (p *Project) LibDir() string { + return filepath.Join(p.rootDir, "lib") } -func (p *Project) LibDir(ctx context.Context) string { - return filepath.Join(p.rootDir(ctx), "lib") -} - -func (p *Project) EffectiveLibDir(ctx context.Context) string { - if p.IsDeveloperMode(ctx) { +func (p *Project) EffectiveLibDir() string { + if p.IsDeveloperMode() { // developer is working on a local checkout, that is not inside of installed root return p.folder } - return p.LibDir(ctx) + return p.LibDir() } -func (p *Project) StateDir(ctx context.Context) string { - return filepath.Join(p.rootDir(ctx), "state") +func (p *Project) StateDir() string { + return filepath.Join(p.rootDir, "state") } -func (p *Project) EnsureFoldersExist(ctx context.Context) error { - dirs := []string{p.CacheDir(ctx), p.ConfigDir(ctx), p.LibDir(ctx), p.StateDir(ctx)} +func (p *Project) EnsureFoldersExist() error { + dirs := []string{p.CacheDir(), p.ConfigDir(), p.LibDir(), p.StateDir()} for _, v := range dirs { err := os.MkdirAll(v, ownerRWXworldRX) if err != nil { @@ -209,11 +211,11 @@ func (p *Project) Uninstall(cmd *cobra.Command) error { } ctx := cmd.Context() log.Infof(ctx, "Removing project: %s", p.Name) - return os.RemoveAll(p.rootDir(ctx)) + return os.RemoveAll(p.rootDir) } func (p *Project) virtualEnvPath(ctx context.Context) string { - if p.IsDeveloperMode(ctx) { + if p.IsDeveloperMode() { // When a virtual environment has been activated, the VIRTUAL_ENV environment variable // is set to the path of the environment. Since explicitly activating a virtual environment // is not required to use it, VIRTUAL_ENV cannot be relied upon to determine whether a virtual @@ -225,14 +227,14 @@ func (p *Project) virtualEnvPath(ctx context.Context) string { logger.Debugf(ctx, "(development mode) using active virtual environment from: %s", activatedVenv) return activatedVenv } - nonActivatedVenv, err := python.DetectVirtualEnvPath(p.EffectiveLibDir(ctx)) + nonActivatedVenv, err := python.DetectVirtualEnvPath(p.EffectiveLibDir()) if err == nil { logger.Debugf(ctx, "(development mode) using virtual environment from: %s", nonActivatedVenv) return nonActivatedVenv } } // by default, we pick Virtual Environment from DATABRICKS_LABS_STATE_DIR - return filepath.Join(p.StateDir(ctx), "venv") + return filepath.Join(p.StateDir(), "venv") } func (p *Project) virtualEnvPython(ctx context.Context) string { @@ -247,13 +249,13 @@ func (p *Project) virtualEnvPython(ctx context.Context) string { } func (p *Project) loginFile(ctx context.Context) string { - if p.IsDeveloperMode(ctx) { + if p.IsDeveloperMode() { // developers may not want to pollute the state in // ~/.databricks/labs/X/config while the version is not yet // released - return p.projectFilePath(ctx, ".databricks-login.json") + return p.projectFilePath(".databricks-login.json") } - return filepath.Join(p.ConfigDir(ctx), "login.json") + return filepath.Join(p.ConfigDir(), "login.json") } func (p *Project) loadLoginConfig(ctx context.Context) (*loginConfig, error) { @@ -268,11 +270,11 @@ func (p *Project) loadLoginConfig(ctx context.Context) (*loginConfig, error) { } func (p *Project) versionFile(ctx context.Context) string { - return filepath.Join(p.StateDir(ctx), "version.json") + return filepath.Join(p.StateDir(), "version.json") } func (p *Project) InstalledVersion(ctx context.Context) (*version, error) { - if p.IsDeveloperMode(ctx) { + if p.IsDeveloperMode() { return &version{ Version: "*", Date: time.Now(), @@ -300,12 +302,12 @@ func (p *Project) writeVersionFile(ctx context.Context, ver string) error { // giving users hints when they need to update their installations. func (p *Project) checkUpdates(cmd *cobra.Command) error { ctx := cmd.Context() - if p.IsDeveloperMode(ctx) { + if p.IsDeveloperMode() { // skipping update check for projects in developer mode, that // might not be installed yet return nil } - r := github.NewReleaseCache("databrickslabs", p.Name, p.CacheDir(ctx)) + r := github.NewReleaseCache("databrickslabs", p.Name, p.CacheDir()) versions, err := r.Load(ctx) if err != nil { return err diff --git a/cmd/labs/project/proxy.go b/cmd/labs/project/proxy.go index ae7df286..d872560a 100644 --- a/cmd/labs/project/proxy.go +++ b/cmd/labs/project/proxy.go @@ -60,7 +60,7 @@ func (cp *proxy) runE(cmd *cobra.Command, _ []string) error { cmd.OutOrStdout(), cmd.ErrOrStderr(), process.WithEnvs(envs)) - if errors.Is(err, fs.ErrNotExist) && cp.IsPythonProject(ctx) { + if errors.Is(err, fs.ErrNotExist) && cp.IsPythonProject() { msg := "cannot find Python %s. Please re-run: databricks labs install %s" return fmt.Errorf(msg, cp.MinPython, cp.Name) } @@ -113,9 +113,9 @@ func (cp *proxy) commandInput(cmd *cobra.Command) ([]string, error) { } args := []string{} ctx := cmd.Context() - if cp.IsPythonProject(ctx) { + if cp.IsPythonProject() { args = append(args, cp.virtualEnvPython(ctx)) - libDir := cp.EffectiveLibDir(cmd.Context()) + libDir := cp.EffectiveLibDir() entrypoint := filepath.Join(libDir, cp.Main) args = append(args, entrypoint) } diff --git a/cmd/labs/show.go b/cmd/labs/show.go index fc9d175c..1ae6498c 100644 --- a/cmd/labs/show.go +++ b/cmd/labs/show.go @@ -37,7 +37,7 @@ func newShowCommand() *cobra.Command { } name := args[0] for _, v := range installed { - isDev := name == "." && v.IsDeveloperMode(ctx) + isDev := name == "." && v.IsDeveloperMode() isMatch := name == v.Name if !(isDev || isMatch) { continue @@ -45,10 +45,10 @@ func newShowCommand() *cobra.Command { return cmdio.Render(ctx, map[string]any{ "name": v.Name, "description": v.Description, - "cache_dir": v.CacheDir(ctx), - "config_dir": v.ConfigDir(ctx), - "lib_dir": v.EffectiveLibDir(ctx), - "is_python": v.IsPythonProject(ctx), + "cache_dir": v.CacheDir(), + "config_dir": v.ConfigDir(), + "lib_dir": v.EffectiveLibDir(), + "is_python": v.IsPythonProject(), }) } return nil diff --git a/libs/databrickscfg/profiles.go b/libs/databrickscfg/profiles.go index 9f31eff6..c7bb2719 100644 --- a/libs/databrickscfg/profiles.go +++ b/libs/databrickscfg/profiles.go @@ -76,7 +76,10 @@ func GetPath(ctx context.Context) (string, error) { configFile = "~/.databrickscfg" } if strings.HasPrefix(configFile, "~") { - homedir := env.UserHomeDir(ctx) + homedir, err := env.UserHomeDir(ctx) + if err != nil { + return "", err + } configFile = filepath.Join(homedir, configFile[1:]) } return configFile, nil @@ -108,7 +111,11 @@ func LoadProfiles(ctx context.Context, fn ProfileMatchFunction) (file string, pr // Replace homedir with ~ if applicable. // This is to make the output more readable. file = filepath.Clean(f.Path()) - homedir := filepath.Clean(env.UserHomeDir(ctx)) + home, err := env.UserHomeDir(ctx) + if err != nil { + return "", nil, err + } + homedir := filepath.Clean(home) if strings.HasPrefix(file, homedir) { file = "~" + file[len(homedir):] } diff --git a/libs/env/context.go b/libs/env/context.go index 84518ad7..af4d1afa 100644 --- a/libs/env/context.go +++ b/libs/env/context.go @@ -2,7 +2,7 @@ package env import ( "context" - "fmt" + "errors" "os" "runtime" "strings" @@ -76,12 +76,15 @@ func WithUserHomeDir(ctx context.Context, value string) context.Context { return Set(ctx, homeEnvVar(), value) } -func UserHomeDir(ctx context.Context) string { +// ErrNoHomeEnv indicates the absence of $HOME env variable +var ErrNoHomeEnv = errors.New("$HOME is not set") + +func UserHomeDir(ctx context.Context) (string, error) { home := Get(ctx, homeEnvVar()) if home == "" { - panic(fmt.Errorf("$HOME is not set")) + return "", ErrNoHomeEnv } - return home + return home, nil } // All returns environment variables that are defined in both os.Environ diff --git a/libs/env/context_test.go b/libs/env/context_test.go index 5befe4ac..28a8d880 100644 --- a/libs/env/context_test.go +++ b/libs/env/context_test.go @@ -51,6 +51,7 @@ func TestContext(t *testing.T) { func TestHome(t *testing.T) { ctx := context.Background() ctx = WithUserHomeDir(ctx, "...") - home := UserHomeDir(ctx) + home, err := UserHomeDir(ctx) assert.Equal(t, "...", home) + assert.NoError(t, err) }