Fix `panic: $HOME is not set` (#1027)

This PR adds error to `env.UserHomeDir(ctx)`

Fixes https://github.com/databricks/setup-cli/issues/73

---------

Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com>
This commit is contained in:
Serge Smertin 2023-11-29 20:08:27 +01:00 committed by GitHub
parent 4d8d825746
commit 65458cbde6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 168 additions and 96 deletions

View File

@ -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
},

View File

@ -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)

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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):]
}

11
libs/env/context.go vendored
View File

@ -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

View File

@ -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)
}