Skip to content

Commit

Permalink
#46 Triton help for setting up and using Docker against a Triton DC
Browse files Browse the repository at this point in the history
This implements all but step #4 (`triton docker ...`) in the proposal.
  • Loading branch information
trentm committed Mar 18, 2016
1 parent 5929632 commit 8cd4dd8
Show file tree
Hide file tree
Showing 13 changed files with 698 additions and 47 deletions.
29 changes: 27 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,34 @@ Known issues:
- `triton ssh ...` disables ssh ControlMaster to avoid issue #52.


## 4.8.1 (not yet released)
## 4.9.0 (not yet released)

(nothing yet)
- #46 Initial support for `triton` helping setup and manage configuration for
using `docker` against a Triton datacenter. Triton datacenters can provide
a Docker Remote API endpoint against which you can run the normal `docker`
client. See <https://www.joyent.com/triton> for and overview and
<https://github.com/joyent/sdc-docker> for developer details.

- `triton profile create` will now setup the profile for running Docker,
if the Triton datacenter provides a docker endpoint. The typical flow
would be:

$ triton profile create
name: foo
...
$ triton profile set foo # make foo my default profile
$ eval "$(triton env --docker)" # set 'DOCKER_' envvars
$ docker info

- For existing Triton CLI profiles, there is a new `triton profile
docker-setup [PROFILE]`.

$ triton profile docker-setup
$ eval "$(triton env --docker)"
$ docker info

- `triton env` will now emit commands to setup `DOCKER_` envvars. That
can be limited to just the Docker-relevant env via `triton env --docker`.


## 4.8.0
Expand Down
66 changes: 55 additions & 11 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,25 +276,38 @@ CLI.prototype.init = function (opts, args, callback) {

this.configDir = CONFIG_DIR;

this.__defineGetter__('tritonapi', function () {
if (self._tritonapi === undefined) {
var config = mod_config.loadConfig({
this.__defineGetter__('config', function getConfig() {
if (self._config === undefined) {
self._config = mod_config.loadConfig({
configDir: self.configDir
});
self.log.trace({config: config}, 'loaded config');
self.log.trace({config: self._config}, 'loaded config');
}
return self._config;
});

this.__defineGetter__('profileName', function getProfileName() {
return (opts.profile || self.config.profile || 'env');
});

var profileName = opts.profile || config.profile || 'env';
var profile = mod_config.loadProfile({
this.__defineGetter__('profile', function getProfile() {
if (self._profile === undefined) {
self._profile = mod_config.loadProfile({
configDir: self.configDir,
name: profileName
name: self.profileName
});
self._applyProfileOverrides(profile);
self.log.trace({profile: profile}, 'loaded profile');
self._applyProfileOverrides(self._profile);
self.log.trace({profile: self._profile}, 'loaded profile');
}
return self._profile;
});

this.__defineGetter__('tritonapi', function getTritonapi() {
if (self._tritonapi === undefined) {
self._tritonapi = tritonapi.createClient({
log: self.log,
profile: profile,
config: config
profile: self.profile,
config: self.config
});
}
return self._tritonapi;
Expand Down Expand Up @@ -538,6 +551,37 @@ CLI.prototype._applyProfileOverrides =
};


/*
* Create and return a TritonApi instance for the given profile name and using
* the CLI's config. Callers of this should remember to `tritonapi.close()`
* when complete... otherwise an HTTP Agent using keep-alive will keep node
* from exiting until it times out.
*/
CLI.prototype.tritonapiFromProfileName =
function tritonapiFromProfileName(opts) {
assert.object(opts, 'opts');
assert.string(opts.profileName, 'opts.profileName');

var profile;
if (opts.profileName === this.profileName) {
profile = this.profile;
} else {
profile = mod_config.loadProfile({
configDir: this.configDir,
name: opts.profileName
});
this.log.trace({profile: profile},
'tritonapiFromProfileName: loaded profile');
}

return tritonapi.createClient({
log: this.log,
profile: profile,
config: this.config
});
};


// Meta
CLI.prototype.do_completion = require('./do_completion');
CLI.prototype.do_profiles = require('./do_profiles');
Expand Down
16 changes: 16 additions & 0 deletions lib/cloudapi2.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,22 @@ CloudApi.prototype._passThrough = function _passThrough(endpoint, opts, cb) {
};


// ---- ping

CloudApi.prototype.ping = function ping(opts, cb) {
assert.object(opts, 'opts');
assert.func(cb, 'cb');

var reqOpts = {
path: '/--ping',
// Ping should be fast. We don't want 15s of retrying.
retry: false
};
this.client.get(reqOpts, function (err, req, res, body) {
cb(err, body, res);
});
};


// ---- networks

Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ function saveProfileSync(opts) {
mkdirp.sync(path.dirname(profilePath));
}
fs.writeFileSync(profilePath, JSON.stringify(toSave, null, 4), 'utf8');
console.log('Saved profile "%s"', name);
console.log('Saved profile "%s".', name);
}


Expand Down
75 changes: 60 additions & 15 deletions lib/do_env.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/*
* Copyright (c) 2015 Joyent Inc.
* Copyright 2016 Joyent Inc.
*
* `triton env ...`
*/

var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var path = require('path');
var strsplit = require('strsplit');
var sshpk = require('sshpk');
var vasync = require('vasync');
Expand All @@ -18,6 +19,7 @@ var mod_config = require('./config');


function do_env(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
Expand All @@ -27,16 +29,27 @@ function do_env(subcmd, opts, args, cb) {
}

var profileName = args[0] || this.tritonapi.profile.name;
var allClientTypes = ['smartdc', 'triton'];
var allClientTypes = ['triton', 'docker', 'smartdc'];
var clientTypes = [];
if (opts.smartdc) {
clientTypes.push('smartdc');
}
var explicit;
var shortOpts = '';
if (opts.triton) {
shortOpts += 't';
clientTypes.push('triton');
}
if (opts.docker) {
shortOpts += 'd';
clientTypes.push('docker');
}
if (opts.smartdc) {
shortOpts += 's';
clientTypes.push('smartdc');
}
if (clientTypes.length === 0) {
explicit = false;
clientTypes = allClientTypes;
} else {
explicit = true;
}

try {
Expand All @@ -52,15 +65,40 @@ function do_env(subcmd, opts, args, cb) {
}

var p = console.log;
var shortOpts = '';
clientTypes.forEach(function (clientType) {
switch (clientType) {
case 'triton':
shortOpts += 't';
p('export TRITON_PROFILE="%s"', profile.name);
break;
case 'docker':
var setupJson = path.resolve(self.configDir, 'docker',
common.profileSlug(profile), 'setup.json');
if (fs.existsSync(setupJson)) {
var setup;
try {
setup = JSON.parse(fs.readFileSync(setupJson));
} catch (err) {
cb(new errors.ConfigError(err, format(
'error determining Docker environment from "%s": %s',
setupJson, err)));
return;
}
Object.keys(setup.env).forEach(function (key) {
var val = setup.env[key];
if (val === null) {
p('unset %s', key);
} else {
p('export %s=%s', key, val);
}
});
} else if (explicit) {
cb(new errors.ConfigError(format('could not find Docker '
+ 'environment setup for profile "%s":\n Run `triton '
+ 'profile docker-setup %s` to setup.',
profile.name, profile.name)));
}
break;
case 'smartdc':
shortOpts += 's';
p('export SDC_URL="%s"', profile.url);
p('export SDC_ACCOUNT="%s"', profile.account);
if (profile.user) {
Expand All @@ -82,8 +120,10 @@ function do_env(subcmd, opts, args, cb) {
});

p('# Run this command to configure your shell:');
p('# eval "$(triton env%s %s)"',
(shortOpts ? ' -'+shortOpts : ''), profile.name);
p('# eval "$(triton env%s%s)"',
(shortOpts ? ' -'+shortOpts : ''),
(profile.name === this.tritonapi.profile.name
? '' : ' ' + profile.name));
}

do_env.options = [
Expand All @@ -92,16 +132,21 @@ do_env.options = [
type: 'bool',
help: 'Show this help.'
},
{
names: ['smartdc', 's'],
type: 'bool',
help: 'Emit environment for node-smartdc (i.e. the "SDC_*" variables).'
},
{
names: ['triton', 't'],
type: 'bool',
help: 'Emit environment commands for node-triton itself (i.e. the ' +
'"TRITON_PROFILE" variable).'
},
{
names: ['docker', 'd'],
type: 'bool',
help: 'Emit environment commands for docker ("DOCKER_HOST" et al).'
},
{
names: ['smartdc', 's'],
type: 'bool',
help: 'Emit environment for node-smartdc (i.e. the "SDC_*" variables).'
}
];

Expand Down
45 changes: 43 additions & 2 deletions lib/do_profile/do_create.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var mod_config = require('../config');
var profilecommon = require('./profilecommon');


function _createProfile(opts, cb) {
assert.object(opts.cli, 'opts.cli');
assert.optionalString(opts.file, 'opts.file');
assert.optionalString(opts.copy, 'opts.copy');
assert.optionalBool(opts.noDocker, 'opts.noDocker');
assert.func(cb, 'cb');
var cli = opts.cli;
var log = cli.log;
Expand Down Expand Up @@ -136,6 +138,8 @@ function _createProfile(opts, cb) {
desc: 'The CloudAPI endpoint URL.',
default: defaults.url,
key: 'url'
// TODO: shortcut to allow 'ssh nightly1' to have this ssh
// in and find cloudapi for me
}, {
desc: 'Your account login name.',
key: 'account',
Expand Down Expand Up @@ -186,6 +190,19 @@ function _createProfile(opts, cb) {
return valCb(keyErr);
}

/*
* Save the user's explicit given key path. We will
* using it later for Docker setup. Trying to use
* the same format as node-smartdc's loadSSHKey
* `keyPaths` param.
*/
ctx.keyPaths = {};
if (keyType === 'ssh') {
ctx.keyPaths.public = keyPath;
} else {
ctx.keyPaths.private = keyPath;
}

var newVal = key.fingerprint('md5').toString();
console.log('Fingerprint: %s', newVal);
valCb(null, newVal);
Expand Down Expand Up @@ -259,6 +276,21 @@ function _createProfile(opts, cb) {
}
next();
},

function dockerSetup(ctx, next) {
if (opts.noDocker || process.platform === 'win32') {
next();
return;
}

profilecommon.profileDockerSetup({
cli: cli,
name: data.name,
keyPaths: ctx.keyPaths,
implicit: true
}, next);
},

function setCurrIfTheOnlyProfile(ctx, next) {
if (ctx.profiles.length !== 0) {
next();
Expand All @@ -276,7 +308,7 @@ function _createProfile(opts, cb) {
return;
}
console.log('Set "%s" as current profile (because it is ' +
'your only profile)', data.name);
'your only profile).', data.name);
next();
});
}
Expand All @@ -299,7 +331,8 @@ function do_create(subcmd, opts, args, cb) {
_createProfile({
cli: this.top,
file: opts.file,
copy: opts.copy
copy: opts.copy,
noDocker: opts.no_docker
}, cb);
}

Expand All @@ -321,6 +354,14 @@ do_create.options = [
type: 'string',
helpArg: 'NAME',
help: 'A profile from which to copy values.'
},
{
names: ['no-docker'],
type: 'bool',
help: 'As of Triton CLI 4.9, creating a profile will attempt (on '
+ 'non-Windows) to also setup for running Docker. This is '
+ 'experimental and might fail. Use this option to disable '
+ 'the attempt.'
}
];

Expand Down
Loading

0 comments on commit 8cd4dd8

Please sign in to comment.