diff --git a/CHANGES.md b/CHANGES.md index 7218f89..ee4310c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,9 @@ ## 4.4.1 (not yet released) -(nothing yet) +- #83, #84 Fix running `triton` on Windows. + Note: Triton config is stored in "%APPDATA%/Joyent/Triton/..." on Windows, + "~/.triton/..." on other platforms. ## 4.4.0 diff --git a/README.md b/README.md index eaf645f..b0b8f07 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ for example: Install Bash completion with ```bash -triton completion > /usr/local/etc/bash_completion.d/triton +triton completion > /usr/local/etc/bash_completion.d/triton # Mac +triton completion > /etc/bash_completion.d/triton # Linux ``` Alternatively, if you don't have or don't want to use a "bash\_completion.d" @@ -258,7 +259,7 @@ A basic example: This section defines all the vars in a TritonApi config. The baked in defaults are in "etc/defaults.json" and can be overriden for the CLI in -"~/.triton/config.json". +"~/.triton/config.json" (on Windows: "%APPDATA%/Joyent/Triton/config.json"). | Name | Description | | ---- | ----------- | diff --git a/lib/cli.js b/lib/cli.js index af35c50..a9cecfa 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -5,7 +5,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2016 Joyent, Inc. * * The `triton` CLI class. */ @@ -34,7 +34,21 @@ var tritonapi = require('./tritonapi'); var pkg = require('../package.json'); -var CONFIG_DIR = path.resolve(process.env.HOME, '.triton'); +var CONFIG_DIR; +if (process.platform === 'win32') { + /* + * For better or worse we are using APPDATA (i.e. the *Roaming* AppData + * dir) over LOCALAPPDATA (non-roaming). The former is meant for "user" + * data, the latter for "machine" data. + * + * TODO: We should likely separate out the *cache* subdir to + * machine-specific data dir. + */ + CONFIG_DIR = path.resolve(process.env.APPDATA, 'Joyent', 'Triton'); +} else { + CONFIG_DIR = path.resolve(process.env.HOME, '.triton'); +} + var OPTIONS = [ { diff --git a/lib/common.js b/lib/common.js index d2a1312..b5af9c7 100644 --- a/lib/common.js +++ b/lib/common.js @@ -855,19 +855,24 @@ function deepEqual(a, b) { /** * Resolve "~/..." and "~" to an absolute path. * - * Limitations: This does not handle "~user/...". This depends on the HOME - * envvar being defined. A better alternative is the "tilde-expansion" - * module (used elsewhere in node-triton), but that doesn't have a sync - * option. + * Limitations: + * - This does not handle "~user/...". + * - This depends on the HOME envvar being defined (%USERPROFILE% on Windows). */ function tildeSync(s) { - var home = process.env.HOME; + var envvar = (process.platform === 'win32' ? 'USERPROFILE' : 'HOME'); + var home = process.env[envvar]; if (!home) { - return s; - } else if (s === '~') { + throw new Error(format('cannot determine home dir: %s environment ' + + 'variable is not defined', envvar)); + } + + if (s === '~') { return home; - } else if (s.slice(0, 2) === '~/') { - return home + s.slice(1); + } else if (s.slice(0, 2) === '~/' || + (process.platform === 'win32' && s.slice(0, 2) === '~'+path.sep)) + { + return path.resolve(home, s.slice(2)); } else { return s; } diff --git a/lib/do_env.js b/lib/do_env.js index aca133e..900a7a6 100644 --- a/lib/do_env.js +++ b/lib/do_env.js @@ -9,7 +9,6 @@ var format = require('util').format; var fs = require('fs'); var strsplit = require('strsplit'); var sshpk = require('sshpk'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); var common = require('./common'); diff --git a/lib/do_image/do_create.js b/lib/do_image/do_create.js index 14910a1..d577ea7 100644 --- a/lib/do_image/do_create.js +++ b/lib/do_image/do_create.js @@ -15,7 +15,6 @@ var format = require('util').format; var fs = require('fs'); var strsplit = require('strsplit'); var tabula = require('tabula'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); var common = require('../common'); diff --git a/lib/do_profile/do_create.js b/lib/do_profile/do_create.js index d4f4e18..d0b0a92 100644 --- a/lib/do_profile/do_create.js +++ b/lib/do_profile/do_create.js @@ -8,7 +8,6 @@ var assert = require('assert-plus'); var format = require('util').format; var fs = require('fs'); var sshpk = require('sshpk'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); var common = require('../common'); @@ -168,29 +167,28 @@ function _createProfile(opts, cb) { } // Try as a local path. - tilde(value, function (keyPath) { - fs.stat(keyPath, function (statErr, stats) { - if (statErr || !stats.isFile()) { - return valCb(new Error(format( - '"%s" is neither a valid fingerprint, ' + - 'nor an existing file', value))); + var keyPath = common.tildeSync(value); + fs.stat(keyPath, function (statErr, stats) { + if (statErr || !stats.isFile()) { + return valCb(new Error(format( + '"%s" is neither a valid fingerprint, ' + + 'nor an existing file', value))); + } + fs.readFile(keyPath, function (readErr, keyData) { + if (readErr) { + return valCb(readErr); + } + var keyType = (keyPath.slice(-4) === '.pub' + ? 'ssh' : 'pem'); + try { + var key = sshpk.parseKey(keyData, keyType); + } catch (keyErr) { + return valCb(keyErr); } - fs.readFile(keyPath, function (readErr, keyData) { - if (readErr) { - return valCb(readErr); - } - var keyType = (keyPath.slice(-4) === '.pub' - ? 'ssh' : 'pem'); - try { - var key = sshpk.parseKey(keyData, keyType); - } catch (keyErr) { - return valCb(keyErr); - } - var newVal = key.fingerprint('md5').toString(); - console.log('Fingerprint: %s', newVal); - valCb(null, newVal); - }); + var newVal = key.fingerprint('md5').toString(); + console.log('Fingerprint: %s', newVal); + valCb(null, newVal); }); }); } diff --git a/lib/do_profile/do_get.js b/lib/do_profile/do_get.js index 466b8e5..9e7f997 100644 --- a/lib/do_profile/do_get.js +++ b/lib/do_profile/do_get.js @@ -9,7 +9,6 @@ var format = require('util').format; var fs = require('fs'); var strsplit = require('strsplit'); var sshpk = require('sshpk'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); var common = require('../common'); diff --git a/lib/metadataandtags.js b/lib/metadataandtags.js index 9543f9f..480f9a0 100644 --- a/lib/metadataandtags.js +++ b/lib/metadataandtags.js @@ -14,9 +14,9 @@ var assert = require('assert-plus'); var format = require('util').format; var fs = require('fs'); var strsplit = require('strsplit'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); +var common = require('./common'); var errors = require('./errors'); @@ -184,38 +184,37 @@ function _addMetadataFromJsonStr(ilk, metadata, s, from, cb) { function _addMetadataFromFile(ilk, metadata, file, cb) { assert.string(ilk, 'ilk'); - tilde(file, function (metaPath) { - fs.stat(metaPath, function (statErr, stats) { - if (statErr || !stats.isFile()) { - cb(new errors.TritonError(format( - '"%s" is not an existing file', file))); + var metaPath = common.tildeSync(file); + fs.stat(metaPath, function (statErr, stats) { + if (statErr || !stats.isFile()) { + cb(new errors.TritonError(format( + '"%s" is not an existing file', file))); + return; + } + fs.readFile(metaPath, 'utf8', function (readErr, data) { + if (readErr) { + cb(readErr); return; } - fs.readFile(metaPath, 'utf8', function (readErr, data) { - if (readErr) { - cb(readErr); - return; - } - /* - * The file is either a JSON object (first non-space - * char is '{'), or newline-separated key=value - * pairs. - */ - var dataTrim = data.trim(); - if (dataTrim.length && dataTrim[0] === '{') { - _addMetadataFromJsonStr(ilk, metadata, dataTrim, file, cb); - } else { - var lines = dataTrim.split(/\r?\n/g).filter( - function (line) { return line.trim(); }); - vasync.forEachPipeline({ - inputs: lines, - func: function oneLine(line, next) { - _addMetadataFromKvStr( - ilk, metadata, line, file, next); - } - }, cb); - } - }); + /* + * The file is either a JSON object (first non-space + * char is '{'), or newline-separated key=value + * pairs. + */ + var dataTrim = data.trim(); + if (dataTrim.length && dataTrim[0] === '{') { + _addMetadataFromJsonStr(ilk, metadata, dataTrim, file, cb); + } else { + var lines = dataTrim.split(/\r?\n/g).filter( + function (line) { return line.trim(); }); + vasync.forEachPipeline({ + inputs: lines, + func: function oneLine(line, next) { + _addMetadataFromKvStr( + ilk, metadata, line, file, next); + } + }, cb); + } }); }); } @@ -266,20 +265,19 @@ function _addMetadataFromKfStr(ilk, metadata, s, from, cb) { function _addMetadatumFromFile(ilk, metadata, key, file, from, cb) { assert.string(ilk, 'ilk'); - tilde(file, function (filePath) { - fs.stat(filePath, function (statErr, stats) { - if (statErr || !stats.isFile()) { - cb(new errors.TritonError(format( - '%s path "%s" is not an existing file', ilk, file))); + var filePath = common.tildeSync(file); + fs.stat(filePath, function (statErr, stats) { + if (statErr || !stats.isFile()) { + cb(new errors.TritonError(format( + '%s path "%s" is not an existing file', ilk, file))); + return; + } + fs.readFile(filePath, 'utf8', function (readErr, content) { + if (readErr) { + cb(readErr); return; } - fs.readFile(filePath, 'utf8', function (readErr, content) { - if (readErr) { - cb(readErr); - return; - } - _addMetadatum(ilk, metadata, key, content, from, cb); - }); + _addMetadatum(ilk, metadata, key, content, from, cb); }); }); } diff --git a/lib/rbac.js b/lib/rbac.js index cc8e9ab..4c595e3 100644 --- a/lib/rbac.js +++ b/lib/rbac.js @@ -17,7 +17,6 @@ var mkdirp = require('mkdirp'); var path = require('path'); var rimraf = require('rimraf'); var sshpk = require('sshpk'); -var tilde = require('tilde-expansion'); var vasync = require('vasync'); var common = require('./common'); @@ -785,14 +784,13 @@ function executeRbacUpdatePlan(ctx, cb) { c.user); vasync.pipeline({arg: {}, funcs: [ function vars(ctx2, next2) { - tilde('~', function (s) { - ctx2.homeDir = s; - ctx2.keyName = format('%s user %s', - c.currProfile.name, c.user); - ctx2.keyPath = format('%s/.ssh/%s-user-%s.id_rsa', - ctx2.homeDir, c.currProfile.name, c.user); - next2(); - }); + ctx2.homeDir = common.tildeSync('~'); + ctx2.keyName = format('%s user %s', + c.currProfile.name, c.user); + ctx2.keyPath = path.resolve(ctx2.homeDir, '.ssh', + format('%s-user-%s.id_rsa', + c.currProfile.name, c.user)); + next2(); }, function rmOldPrivKey(ctx2, next2) { rimraf(ctx2.keyPath, next2); diff --git a/package.json b/package.json index e4553f7..ba4bf38 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "smartdc-auth": "2.2.3", "strsplit": "1.0.0", "tabula": "1.7.0", - "tilde-expansion": "0.0.0", "vasync": "1.6.3", "verror": "1.6.0", "wordwrap": "1.0.0"