diff --git a/src/config.ts b/src/config.ts index 9dfee8e7293..3af34dd3e75 100644 --- a/src/config.ts +++ b/src/config.ts @@ -516,34 +516,70 @@ export function bufferFromFileOrString(file?: string, data?: string): Buffer | n return null; } +function dropDuplicatesAndNils(a: string[]): string[] { + return a.reduce( + (acceptedValues, currentValue) => { + // Good-enough algorithm for reducing a small (3 items at this point) array into an ordered list + // of unique non-empty strings. + if (currentValue && !acceptedValues.includes(currentValue)) { + return acceptedValues.concat(currentValue); + } else { + return acceptedValues; + } + }, + [] as string[], + ); +} + // Only public for testing. export function findHomeDir(): string | null { - if (process.env.HOME) { + if (process.platform !== 'win32') { + if (process.env.HOME) { + try { + fs.accessSync(process.env.HOME); + return process.env.HOME; + // tslint:disable-next-line:no-empty + } catch (ignore) {} + } + return null; + } + // $HOME is always favoured, but the k8s go-client prefers the other two env vars + // differently depending on whether .kube/config exists or not. + const homeDrivePath = + process.env.HOMEDRIVE && process.env.HOMEPATH + ? path.join(process.env.HOMEDRIVE, process.env.HOMEPATH) + : ''; + const homePath = process.env.HOME || ''; + const userProfile = process.env.USERPROFILE || ''; + const favourHomeDrivePathList: string[] = dropDuplicatesAndNils([homePath, homeDrivePath, userProfile]); + const favourUserProfileList: string[] = dropDuplicatesAndNils([homePath, userProfile, homeDrivePath]); + // 1. the first of %HOME%, %HOMEDRIVE%%HOMEPATH%, %USERPROFILE% containing a `.kube\config` file is returned. + for (const dir of favourHomeDrivePathList) { try { - fs.accessSync(process.env.HOME); - return process.env.HOME; + fs.accessSync(path.join(dir, '.kube', 'config')); + return dir; // tslint:disable-next-line:no-empty } catch (ignore) {} } - if (process.platform !== 'win32') { - return null; - } - if (process.env.HOMEDRIVE && process.env.HOMEPATH) { - const dir = path.join(process.env.HOMEDRIVE, process.env.HOMEPATH); + // 2. ...the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists and is writeable is returned + for (const dir of favourUserProfileList) { try { - fs.accessSync(dir); + fs.accessSync(dir, fs.constants.W_OK); return dir; // tslint:disable-next-line:no-empty } catch (ignore) {} } - if (process.env.USERPROFILE) { + // 3. ...the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists is returned. + for (const dir of favourUserProfileList) { try { - fs.accessSync(process.env.USERPROFILE); - return process.env.USERPROFILE; + fs.accessSync(dir); + return dir; // tslint:disable-next-line:no-empty } catch (ignore) {} } - return null; + // 4. if none of those locations exists, the first of + // %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that is set is returned. + return favourUserProfileList[0] || null; } export interface Named { diff --git a/src/config_test.ts b/src/config_test.ts index 65dc3e2f8bf..19eedaf9bb1 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -461,39 +461,134 @@ describe('KubeConfig', () => { process.env.HOMEPATH = originalEnvVars.HOMEPATH; }); - it('should return null if no home is present', () => { + it('should return null if no home-ish env vars are set', () => { const dir = findHomeDir(); expect(dir).to.equal(null); }); - it('should load from HOMEDRIVE/HOMEPATH if present', () => { - process.env.HOMEDRIVE = 'foo'; - process.env.HOMEPATH = 'bar'; - const dir = join(process.env.HOMEDRIVE, process.env.HOMEPATH); - const arg = {}; - arg[dir] = { config: 'data' }; - mockfs(arg); + describe('look for an existing .kube/config', () => { + let allDirs; + let homeDrive; + beforeEach(() => { + allDirs = {}; + process.env.HOME = 'home'; + process.env.HOMEDRIVE = 'drive'; + process.env.HOMEPATH = 'a-path'; + process.env.USERPROFILE = 'a-userprofile'; + homeDrive = join(process.env.HOMEDRIVE, process.env.HOMEPATH); + allDirs[process.env.HOME] = {}; + allDirs[homeDrive] = {}; + allDirs[process.env.USERPROFILE] = {}; + }); + it('should load from HOME if present', () => { + const dir = process.env.HOME as string; + allDirs[dir]['.kube'] = { config: 'data' }; + mockfs(allDirs); - const home = findHomeDir(); + const home = findHomeDir(); - mockfs.restore(); - expect(home).to.equal(dir); + mockfs.restore(); + expect(home).to.equal(dir); + }); + it('should favor HOME when present', () => { + const dir = process.env.HOME as string; + allDirs[dir]['.kube'] = { config: 'data' }; + allDirs[homeDrive]['.kube'] = { config: 'data' }; + allDirs[process.env.USERPROFILE as string]['.kube'] = { config: 'data' }; + mockfs(allDirs); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(dir); + }); + + it('should load from HOMEDRIVE/HOMEPATH if present', () => { + const dir = homeDrive; + allDirs[dir]['.kube'] = { config: 'data' }; + mockfs(allDirs); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(dir); + }); + + it('should favor HOMEDRIVE/HOMEPATH over USERPROFILE', () => { + const dir = homeDrive; + allDirs[dir]['.kube'] = { config: 'data' }; + allDirs[process.env.USERPROFILE as string]['.kube'] = { config: 'data' }; + mockfs(allDirs); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(dir); + }); + + it('should load from USERPROFILE if present', () => { + const dir = process.env.USERPROFILE as string; + allDirs[dir]['.kube'] = { config: 'data' }; + mockfs(allDirs); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(dir); + }); }); - it('should load from USERPROFILE if present', () => { - const dir = 'someplace'; + // Just test for existence,but this will include the writeability order + describe('look for an existing directory', () => { + let allDirs; + let homeDrive; + beforeEach(() => { + allDirs = {}; + process.env.HOME = 'home'; + process.env.HOMEDRIVE = 'drive'; + process.env.HOMEPATH = 'a-path'; + process.env.USERPROFILE = 'a-userprofile'; + homeDrive = join(process.env.HOMEDRIVE, process.env.HOMEPATH); + }); + it('should load from HOME if present', () => { + allDirs[process.env.HOME as string] = 'data'; + allDirs[homeDrive] = 'data'; + allDirs[process.env.USERPROFILE as string] = 'data'; + const dir = process.env.HOME; + mockfs(allDirs); - process.env.HOMEDRIVE = 'foo'; - process.env.HOMEPATH = 'bar'; - process.env.USERPROFILE = dir; - const arg = {}; - arg[dir] = { config: 'data' }; - mockfs(arg); + const home = findHomeDir(); - const home = findHomeDir(); + mockfs.restore(); + expect(home).to.equal(dir); + }); + it('should load from USERPROFILE if present', () => { + allDirs[homeDrive] = 'data'; + allDirs[process.env.USERPROFILE as string] = 'data'; + mockfs(allDirs); - mockfs.restore(); - expect(home).to.equal(dir); + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(process.env.USERPROFILE); + }); + it('should load from homeDrive if present', () => { + allDirs[homeDrive] = 'data'; + mockfs(allDirs); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(homeDrive); + }); + it('should return HOME when no home-ish directories are present', () => { + mockfs({}); + + const home = findHomeDir(); + + mockfs.restore(); + expect(home).to.equal(process.env.HOME); + }); }); });