Compare commits

...
This repository has been archived on 2020-01-20. You can view files and clone it, but cannot push or open issues or pull requests.

489 Commits

Author SHA1 Message Date
caa6da7821 merge with upstream 2019-03-12 21:55:51 +02:00
Alyssa Ross
5212530b37 joyent/node-triton#254 Fix Markdown syntax in README 2019-03-11 17:38:02 -07:00
Trent Mick
cc07717008 joyent/node-triton#258 triton instance create picks disabled image
Reviewed by: Brian Bennett <brian.bennett@joyent.com>
Approved by: Brian Bennett <brian.bennett@joyent.com>
2019-03-06 17:25:00 -08:00
Joshua M. Clulow
4921fd2e36 joyent/node-triton#259 "triton ssh" could support proxying through a bastion host
Reviewed by: Tim Foster <tim.foster@joyent.com>
Approved by: Brian Bennett <brian.bennett@joyent.com>
2018-12-18 00:40:41 +00:00
Joshua M. Clulow
05f1bae869 joyent/node-triton#255 creating fabric networks still broken
joyent/node-triton#257 "triton vlan create" should not assert on missing argument
Reviewed by: Dave Eddy <dave.eddy@joyent.com>
Reviewed by: Robert Mustacchi <rm@joyent.com>
Approved by: Cody Mello <cody.mello@joyent.com>
2018-10-05 21:45:52 +00:00
66114eb940 npm version bump 2018-08-09 09:15:25 +03:00
161f879723 changed default api endpoint when creating profile 2018-08-09 09:14:33 +03:00
2d92219fef fixed triton/spearhead in eval output 2018-07-26 17:39:00 +03:00
e980829ca9 sync with upstream 2018-07-24 13:22:28 +03:00
75ec73a31c merge with upstream 2018-07-24 13:22:09 +03:00
Todd Whiteman
aea9b2b7b3 joyent/node-triton#249 Error when creating or deleting profiles when using node v10
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-07-19 17:17:50 -07:00
Joshua M. Clulow
aa58982e2a TRITON-598 "triton network get-default" should print error when no default network is set
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-07-17 16:51:29 +00:00
4684efe22a changed triton to spearhead in help out for env setup 2018-07-07 13:35:39 +03:00
Todd Whiteman
c86804cfe4 TRITON-52 x-DC image copy
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-06-29 16:44:17 -07:00
Todd Whiteman
dc5dc12052 TRITON-53 x-account image clone
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-06-27 17:28:02 -07:00
Trent Mick
264f69dc54 joyent/node-triton#250 triton profile list doesn't seem to work without full env
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Pedro Palazón Candel <pedro@joyent.com>
Approved by: Marsell Kukuljevic <marsell@joyent.com>
Approved by: Pedro Palazón Candel <pedro@joyent.com>
2018-06-26 16:57:15 -07:00
Marsell Kukuljevic
3584c82e05 TRITON-324 node-triton cli-affinity.test.js failures: create timeout, .end() called twice
Reviewed by: Pedro P. Candel <pedro@joyent.com>
Approved by: Pedro P. Candel <pedro@joyent.com>
2018-06-26 11:36:28 +00:00
Marsell Kukuljevic
0bc11c1e33 TRITON-401 Add support for fabric vlans and networks to node-triton
Reviewed by: Trent Mick <trent.mick@joyent.com>
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
Reviewed by: Pedro P. Candel <pedro@joyent.com>
Approved by: Pedro P. Candel <pedro@joyent.com>
2018-06-25 17:42:33 +02:00
Trent Mick
f100c4dbb5 TRITON-524 'triton inst get' should support '--credentials'
Reviewed by: Marsell Kukuljevic <marsell@joyent.com>
Approved by: Marsell Kukuljevic <marsell@joyent.com>
2018-06-19 14:42:10 -07:00
ef91454769 Revert "6.1.0"
This reverts commit 180560dc1e.
2018-06-06 13:50:35 +03:00
180560dc1e 6.1.0 2018-06-06 13:49:41 +03:00
5438723d06 merge with latest from upstream 2018-06-06 13:21:15 +03:00
Alex Wilson
5734123e75 joyent/node-triton#245 triton profile should generate separate keys for Docker
Reviewed by: Trent Mick <trent.mick@joyent.com>
Reviewed by: Marsell Kukuljevic <marsell@joyent.com>
2018-05-15 15:06:22 -07:00
Trent Mick
6015cf2145 TRITON-323 Markdown syntax tweak in node-triton/CHANGES.md 2018-04-13 14:12:57 -07:00
Marsell Kukuljevic
6417595ba6 TRITON-167 Anti-affinity rules fail when no instances match the name
TRITON-168 Regex anti-affinity rules fail unexpectedly
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-04-11 12:56:38 +12:00
Trent Mick
d3d3216a38 TRITON-304 node-triton test crash in 'triton inst nic ...' tests (extra output in tests to help diagnose failure) 2018-04-10 16:30:43 -07:00
Trent Mick
91b4c23a52 TRITON-316 cut node-triton 5.10.0 release
Reviewed by: Cody Peter Mello <cody.mello@joyent.com>
Approved by: Cody Peter Mello <cody.mello@joyent.com>
2018-04-10 16:23:05 -07:00
Mike Zeller
96a5be8ce7 TRITON-301 TRITON-58 created a circular import dep between lib/cloudapi2.js and lib/common.js
Reviewed by: Trent Mick <trentm@gmail.com>
Reviewed by: Dave Eddy <dave.eddy@joyent.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-04-03 14:55:51 -07:00
Dave Eddy
06812c9cd4 TRITON-42 node-triton should support nics when creating an instance
Reviewed by: Michael Zeller <mike.zeller@joyent.com>
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-03-19 18:41:32 -04:00
Marsell Kukuljevic
8e6cf27121 TRITON-19 Triton equivalent to AWS' termination protection
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-03-14 01:51:43 +00:00
Dave Eddy
002171ea06 TRITON-33 node-triton use common functions to cut down on code duplication
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-03-06 15:15:06 -05:00
Mike Zeller
bf64899685 TRITON-58 node-triton should support nic operations
Reviewed by: Marsell Kukuljevic <marsell@joyent.com>
Approved by: Marsell Kukuljevic <marsell@joyent.com>
2018-03-05 14:04:47 -08:00
Josh Wilsdon
39635cd0a2 TRITON-190 remove node-triton support for passing --brand flag 2018-02-27 16:45:23 -08:00
be74f307e0 Merge branch 'master' of https://github.com/joyent/node-triton 2018-02-23 15:28:53 +02:00
Josh Wilsdon
26b97b5bed TRITON-124 add node-triton support for bhyve
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-02-19 17:28:42 -08:00
Todd Whiteman
3f243f8c8f TRITON-116 node-triton image sharing
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-02-14 11:52:29 -08:00
a3adf3f475 5.6.4 2018-02-10 21:10:36 +02:00
ed6cf329be 5.6.3 2018-02-10 21:10:29 +02:00
c623e7cad5 updated logo with absolute remote url 2018-02-10 21:09:30 +02:00
6b4b764b1e bumped version 2018-02-10 21:03:17 +02:00
4279ba591e 5.6.3 2018-02-10 21:02:43 +02:00
897af1951b 5.6.2 2018-02-10 21:02:33 +02:00
3bc3dc00d4 updated logo path 2018-02-10 20:59:16 +02:00
1e1e7a65bd updated url 2018-02-10 20:53:35 +02:00
fd510140ab added missing comman 2018-02-10 20:18:02 +02:00
6f9cd272b7 renaming to spearhead 2018-02-10 20:17:02 +02:00
6f4b5deb5e preparing for publish 2018-02-10 18:48:47 +02:00
71f5d9568e modified env vars and output texts 2018-02-10 18:36:28 +02:00
d589694608 changed env vars to SC 2018-02-10 18:29:30 +02:00
f7f86ae7d5 updated for windows 2018-02-10 18:10:47 +02:00
1caffe0000 changed config dir 2018-02-10 18:09:58 +02:00
10e3b9df19 renamed triton to spearhead 2018-02-10 18:02:17 +02:00
f9ef30c090 removed examples, we do not use them 2018-02-10 17:45:54 +02:00
5d72d087f6 .git to gitignore 2018-02-10 17:45:30 +02:00
658e1b951d updated readme 2018-02-10 17:42:58 +02:00
a9addf752c modified logo size 2018-02-10 17:19:06 +02:00
dcd2491410 updated readme with new logo 2018-02-10 17:10:26 +02:00
23ea5b27df updated logo 2018-02-10 17:08:40 +02:00
Julien Gilli
e7c02436df PUBAPI-1470 volume objects should expose their creation timestamp in a property named "created" instead of "create_timestamp"
Reviewed by: Mike Zeller <mike.zeller@joyent.com>
Approved by: Mike Zeller <mike.zeller@joyent.com>
2018-02-08 22:35:52 +00:00
Dave Eddy
68a889b8b7 TRITON-88 node-triton "env" doesn't call its callback
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2018-01-26 12:15:57 -05:00
Dave Eddy
225410b4fc TRITON-24 node-triton ListNetworkIPs has unordered results
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-12-22 18:38:33 -05:00
Dave Eddy
ab564177b5 TRITON-30 Add UpdateNetworkIP to node-triton
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-12-22 17:49:52 -05:00
Mike Zeller
da5f3bade8 PUBAPI-1452 Add ListNetwork IP and GetNetworkIP to node-triton
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-11-29 13:58:59 -08:00
Trent Mick
12f2794c39 joyent/node-triton#233 release 5.4.0 2017-09-29 12:15:46 -07:00
Trent Mick
69a4598458 joyent/node-triton#74 triton instance list filtering on tags
Reviewed by: Josh Wilsdon <josh@wilsdon.ca>
Approved by: Josh Wilsdon <josh@wilsdon.ca>
2017-09-29 12:12:53 -07:00
Trent Mick
e0b6c7e0bc joyent/node-triton#232 commit 8438f44 broke parseVolumeSize unit tests
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
Approved by: Julien Gilli <julien.gilli@joyent.com>
2017-09-26 15:58:45 -07:00
Brian Bennett
04420b5088 TOOLS-1863 node-triton DTraceProviderBindings errors on FreeBSD
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-09-26 15:09:14 -07:00
Josh Wilsdon
45ed8883ef PUBAPI-1420 Add ability to mount NFS volumes with CreateMachine endpoint
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-09-21 23:37:22 -07:00
Julien Gilli
8438f446cc joyent/node-triton#226 add new triton volume sizes subcommand
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-08-24 18:01:20 +00:00
Trent Mick
057a784dc3 joyent/node-triton#225 improve 'make test-in-parallel' 2017-08-09 14:26:25 -07:00
Trent Mick
3f24e06cf6 joyent/node-triton#223 version 5.3.1 2017-08-09 11:12:23 -07:00
Trent Mick
7728c02afe joyent/node-triton#222 use TRITON_ROLE instead of MANTA_ROLE env var
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2017-08-09 11:02:26 -07:00
Trent Mick
c7bb6b01e1 joyent/node-triton#221 cli-image-create.test.js crashes if image creation isn't work 2017-08-08 16:58:26 -07:00
Trent Mick
9d8681a21b joyent/node-triton#201 Assuming an RBAC role in triton CLI v5 throws error
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2017-08-08 16:52:42 -07:00
Julien Gilli
c56d0a25da joyent/node-triton#217 triton volume ls -l should output a RESOURCE column
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-07-31 23:57:19 +00:00
Trent Mick
a5ab0f686b joyent/node-triton#220 5.3.0 release 2017-07-31 16:34:04 -07:00
Trent Mick
c0ad6bb378 joyent/node-triton#219 test: Fix test suite to not use non-existant triton img rm -w wait option 2017-07-31 16:31:19 -07:00
Trent Mick
546abee318 joyent/node-triton#218 add 'allowVolumesTests' to test config and skip volumes tests by default
Reviewed by: Josh Wilsdon <josh@wilsdon.ca>
Approved by: Josh Wilsdon <josh@wilsdon.ca>
2017-07-28 16:57:55 -07:00
Julien Gilli
dc1b6c75b7 joyent/node-triton#173 Add support for listing and getting triton nfs volumes
joyent/node-triton#174 Add support for creating triton nfs volumes
joyent/node-triton#175 Add support for deleting triton NFS volumes
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-07-20 23:48:27 +00:00
Trent Mick
87b196ac31 joyent/node-triton#215 release 5.2.1 2017-06-29 10:55:12 -07:00
Alex Wilson
9f17d668c9 joyent/node-triton#213 commands fail unhelpfully when cliSetupTritonApi returns error
Reviewed by: Trent Mick <trent.mick@joyent.com>
Reviewed by: Sam Gwydir <sam.gwydir@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2017-06-29 00:25:32 +00:00
Trent Mick
8493c52be3 joyent/node-triton#199 cli-networks.test.js crash if there are no non-public networks
Reviewed by: Jason King <jason.king@joyent.com>
Approved by: Jason King <jason.king@joyent.com>
2017-04-13 12:30:50 -07:00
Trent Mick
b69832c227 joyent/node-triton#193 triton ssh ... can crash if the instance image has no tags
Reviewed by: Josh Wilsdon <josh@wilsdon.ca>
Approved by: Josh Wilsdon <josh@wilsdon.ca>
2017-04-11 15:09:25 -07:00
Jason King
17669f35ea joyent/node-triton#197 Create triton image export command 2017-04-10 16:59:16 -05:00
Trent Mick
33ff58c3d3 joyent/node-triton#195 test *kvm* image creation 2017-04-03 12:37:58 -07:00
Trent Mick
a8f4b57ba3 joyent/node-triton#194 v5.1.1 2017-03-17 12:14:20 -07:00
Marc-André Tremblay
f27d502d92 joyent/node-triton#190 Exception when using current docker client
Reviewed by: Kody A Kantor <kody.kantor@gmail.com>
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-03-17 19:09:04 +00:00
Trent Mick
a3071585aa joyent/node-triton#148 triton profile edit failes if EDITOR contains a space 2017-02-28 15:48:10 -08:00
Trent Mick
d14ac041f4 joyent/node-triton#183 triton profile create should not use ANSI code styling if stdout isn't a TTY
Reviewed by: Orlando Vazquez <orlando@joyent.com>
Approved by: Orlando Vazquez <orlando@joyent.com>
2017-02-24 13:13:27 -08:00
Trent Mick
b66939751d joyent/node-triton#182 'triton profile create' and 'triton profile docker-setup' need a non-interactive option
Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
2017-02-24 09:29:05 -08:00
Trent Mick
d91e0326c2 joyent/node-triton#181 v5.0.0 release 2017-02-22 19:10:51 -08:00
Trent Mick
2bad27fcbb joyent/node-triton#142 triton help create could give fuller help
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2017-02-21 11:29:50 -08:00
Trent Mick
46ded211f3 joyent/node-triton#180 triton profile docker-setup fails with a non-MD5 keyId
Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
2017-02-21 08:56:04 -08:00
Trent Mick
dba8915d4f joyent/node-triton#150 fallback to ~/.ssh during docker profile creation
Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
2017-02-20 19:39:16 -08:00
Trent Mick
86c689e809 joyent/node-triton#179 proposed UX tweaks to interactive triton profile create
Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
2017-02-20 15:46:08 -08:00
Trent Mick
98ba032b5d joyent/node-triton#177 drop unused node-uuid dep 2017-02-17 17:41:05 -08:00
Trent Mick
4ee67a5725 joyent/node-triton#176 triton --act-as option was broken by joyent/node-triton#156
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2017-02-17 17:17:51 -08:00
andrew
ec9b6cc5aa joyent/node-triton#157 support resizing of instances
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-02-16 17:00:32 -08:00
Trent Mick
3f4eff598c joyent/node-triton#170 Integration tests fail to change company name back to original value when it contains spaces
Reviewed by: Todd Whiteman <todd.whiteman@joyent.com>
Approved by: Todd Whiteman <todd.whiteman@joyent.com>
2017-02-10 16:49:43 -08:00
Trent Mick
3dd59ab109 joyent/node-triton#129 triton reboot --wait INST doesn't wait
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2017-02-08 16:49:00 -08:00
Trent Mick
1503088abd joyent/node-triton#166 see fix for joyent/node-sshpk#22 before release
Reviewed by: Alex Wilson <alex.wilson@joyent.com>
2017-02-02 14:41:23 -08:00
Chris Burroughs
75cb032f3a joyent/node-triton#156 allow all profile fields to be given on the cli
Reviewed by: Trent Mick <trent.mick@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2017-01-31 16:09:01 +00:00
Chris Burroughs
68e5b68583 PUBAPI-1171 improve boolean envvar error handling
PUBAPI-1205 mirror node-smartdc meaning for SDC_TESTING
PUBAPI-1351 label each group of env vars for clarity
Reviewed by: Trent Mick <trent.mick@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2017-01-25 18:26:14 +00:00
Yang Yong
c7c91aea83 joyent/node-triton#80 would be nice if triton network list public=false worked
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2017-01-17 15:39:43 -08:00
Trent Mick
41b5c506ce joyent/node-triton#167 test suite: use 'nodetritontest' in test key name, createTestInst never reports failure
Reviewed by: Brian Bennett <brian.bennett@joyent.com>
2017-01-17 14:48:40 -08:00
Julien Gilli
d99110553d ZAPI-757 VMAPI should dump core when an uncaught error is thrown
Reviewed by: Trent Mick <trentm@gmail.com>
2017-01-14 00:27:16 +00:00
Trent Mick
da80542222 joyent/node-triton#146 triton instance rename --wait (update changelog) 2017-01-13 16:09:04 -08:00
Yang Yong
5dab69c002 joyent/node-triton#146 triton instance rename --wait 2017-01-13 16:06:45 -08:00
Trent Mick
586b1924a0 joyent/node-triton#133 triton instance fwrule list and triton fwrules are not recognized (update changelog) 2017-01-13 15:07:29 -08:00
Yang Yong
16cff8d60a joyent/node-triton#133 triton instance fwrule list and triton fwrules` are not recognized
Reviewed by: Trent Mick <trent.mick@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2017-01-13 23:05:53 +00:00
Trent Mick
37d475ffd9 joyent/node-triton#164 change from joyent/node-triton#137 broke 'triton --version' test case 2017-01-12 11:48:47 -08:00
Trent Mick
afe1972b69 joyent/node-triton#163 "skipAffinityTests" option for test/config.json is not documented in test/config.json.sample
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
Approved by: Julien Gilli <julien.gilli@joyent.com>
2017-01-12 11:41:45 -08:00
Dillon Amburgey
76759e547e joyent/node-triton#3 triton ssh command not aware of "ubuntu" login for ubuntu-certified images 2017-01-05 19:37:35 -05:00
Chris Burroughs
04cc61ee1d joyent/node-triton#160 missing file from cr:1129 rebase
Reviewed by: Dillon Amburgey <dillona@dillona.com>
Approved by: Marsell Kukuljevic <marsell@joyent.com>
2016-12-28 15:00:48 +00:00
Chris Burroughs
3a369e4bb8 joyent/node-triton#137 triton CLI tool fails in default usecase
Reviewed by: Chris Burroughs <chris.burroughs@joyent.com>
Approved by: Chris Burroughs <chris.burroughs@joyent.com>
2016-12-21 19:27:46 +00:00
Dillon Amburgey
2453bc12e8 joyent/node-triton#158 tritonapi image cache never expires
Reviewed by: Chris Burroughs <chris.burroughs@joyet.com>
Approved by: Chris Burroughs <chris.burroughs@joyet.com>
2016-12-21 19:00:47 +00:00
Trent Mick
c7140609f0 joyent/node-triton#155 doc TritonApi method callback guidelines 2016-12-14 14:33:29 -08:00
Trent Mick
1486fa48f6 joyent/node-triton#153 upgrade restify-clients to a recent version with explicit transitive deps 2016-12-14 12:10:40 -08:00
Trent Mick
f7bbe5fcff joyent/node-triton#154 #108 changes broke triton cloudapi ... 2016-12-14 11:01:18 -08:00
Chris Burroughs
ad7d608011 joyent/node-triton#108 support passphrase protected keys
Reviewed by: Trent Mick <trent.mick@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2016-12-13 19:01:32 +00:00
Chris Burroughs
696439f1ae joyent/node-triton#149 resurrected example-get-account
Reviewed by: Trent Mick <trent.mick@joyent.com>
Approved by: Trent Mick <trent.mick@joyent.com>
2016-11-30 16:45:10 -05:00
Trent Mick
1e986ea733 joyent/node-triton#143 triton rbac key duplicate output (changelog) 2016-11-29 19:09:45 -08:00
Yang Yong
e721ebe865 joyent/node-triton#143 triton rbac key duplicate output 2016-11-30 10:40:34 +09:00
Trent Mick
152d12767c joyent/node-triton#147 4.15.0 2016-11-24 19:32:43 +00:00
Yang Yong
a2c83aab56 joyent/node-triton#64 support instance renaming
Reviewed by: Trent Mick <trentm@gmail.com>
Approved by: Trent Mick <trentm@gmail.com>
2016-11-24 16:47:32 +00:00
Trent Mick
6c7944f2c2 joyent/node-triton#144 update dashdash dep to get Bash completion fix for sort order of "no completions" completions 2016-11-22 14:40:57 -08:00
Trent Mick
84338a44b2 TOOLS-1592 triton CLI *using node 6* fails various commands with MD5 errors
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
2016-10-31 16:54:55 -07:00
Trent Mick
e7c321f3db TOOLS-1588 triton@14.4.1 2016-10-26 17:36:43 -07:00
Trent Mick
246810e342 TOOLS-1587 'triton profile docker-setup' fails when path to 'docker' has spaces
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
Reviewed by: Orlando Vazquez <orlando@joyent.com>
Approved by: Orlando Vazquez <orlando@joyent.com>
2016-10-26 11:11:27 -07:00
Trent Mick
0d4850405b joyent/node-triton#136 bash completion for 'triton profile create --copy <TAB>'
Reviewed by: Angela Fong <angela.fong@joyent.com>
Approved by: Angela Fong <angela.fong@joyent.com>
2016-09-30 15:43:51 -07:00
Trent Mick
bb9412c2bc joyent/node-triton#135 parameterize recent 'make cutarelease' task code
Reviewed by: Josh Wilsdon <josh@wilsdon.ca>
Approved by: Josh Wilsdon <josh@wilsdon.ca>
2016-09-30 14:41:50 -07:00
Trent Mick
d6e05e7a26 joyent/node-triton#131 update 'make cutarelease' and 'make versioncheck' to avoid commits for cutting a release (fix 'npm publish' command usage) 2016-09-18 00:50:12 +00:00
Trent Mick
b8b753a94d joyent/node-triton#131 update 'make cutarelease' and 'make versioncheck' to avoid commits for cutting a release
Reviewed by: Josh Wilsdon <josh@wilsdon.ca>
Approved by: Josh Wilsdon <josh@wilsdon.ca>
2016-09-17 17:43:45 -07:00
Trent Mick
21f2d29c86 joyent/node-triton#130 triton image caching should include disabled images
Reviewed by: Cody Peter Mello <cody.mello@joyent.com>
Approved by: Cody Peter Mello <cody.mello@joyent.com>
2016-09-16 16:01:38 -07:00
Trent Mick
c7f7f86126 TOOLS-1531 node-triton README should have the "use gerrit" blurb 2016-08-22 10:45:21 -07:00
Trent Mick
11cd220c3c attempted markup fix 2016-06-08 14:49:52 -07:00
Trent Mick
3cd11c941e bumpver for subsequent work 2016-06-08 14:47:27 -07:00
Trent Mick
48fa8fa03c 4.13.0 2016-06-08 14:47:20 -07:00
Trent Mick
0932365d46 joyent/node-triton#120 Ensure 'triton inst ls' only requires ListMachines RBAC access 2016-06-08 14:44:46 -07:00
Trent Mick
fabe0a0841 joyent/node-triton#113 error help for usage errors
This builds on cmdln 4.x's "errHelp" facilities.
2016-06-08 14:13:21 -07:00
Trent Mick
42d5382c7e bumpver for subsequent work 2016-06-07 14:19:57 -07:00
Trent Mick
9d4fb8d45f 4.12.0 2016-06-07 14:19:51 -07:00
Trent Mick
ad20360306 joyent/node-triton#120 triton -r,--role ROLE ... to be able to take up an RBAC role 2016-06-07 14:19:06 -07:00
Trent Mick
9b099a91e9 bumpver for subsequent work 2016-04-22 13:10:11 -07:00
Trent Mick
85375517cb 4.11.0 2016-04-22 13:09:54 -07:00
Trent Mick
8d8d9ed39f bash completion for 'triton env TAB' 2016-04-22 13:09:35 -07:00
Trent Mick
0a3c48d2ea joyent/node-triton#112 triton completion generation has been broken for a while 2016-04-22 13:06:54 -07:00
Trent Mick
a48d7629dd joyent/node-triton#111 want triton env --unset,-u option emit env to unset relevant envvars 2016-04-22 12:55:32 -07:00
Trent Mick
de391bf013 tweak 'triton create -h' output to help clarify/group related create options 2016-04-12 10:31:07 -07:00
Trent Mick
0c605c95c5 bumpver for subsequent work 2016-04-11 17:06:36 -07:00
Trent Mick
3b8215a8bb 4.10.0 2016-04-11 17:06:22 -07:00
Trent Mick
e068c7abcf joyent/node-triton#82 locality flag/property at creation (far/near as in sdc-createmachine)
a.k.a. "Affinity", `triton create -a,--affinity RULE ...`
2016-04-11 17:04:52 -07:00
Trent Mick
aeed6d9192 bumpver for subsequent work 2016-03-28 13:14:33 -07:00
Trent Mick
25e74f5c4e joyent/node-triton#46 Triton help for setting up and using Docker against a Triton DC (fix profile create test case) 2016-03-28 13:13:19 -07:00
Trent Mick
e6819a2fe7 4.9.0 2016-03-28 13:02:21 -07:00
Trent Mick
f178d9abce joyent/node-triton#46 Mention the --no-docker option 2016-03-28 13:02:12 -07:00
Trent Mick
d2cc677e46 fix markdown syntax 2016-03-18 16:54:42 -07:00
Trent Mick
8cd4dd80eb joyent/node-triton#46 Triton help for setting up and using Docker against a Triton DC
This implements all but step #4 (`triton docker ...`) in the proposal.
2016-03-18 16:51:22 -07:00
Trent Mick
5929632e08 some play ideas 2016-03-18 08:51:28 -07:00
Trent Mick
461320de8b bumpver for subsequent work 2016-03-11 15:26:36 -08:00
Trent Mick
9a6937eba8 4.8.0 2016-03-11 15:26:31 -08:00
Trent Mick
2c9c749efc verbump for new feature (triton ip) 2016-03-11 15:26:07 -08:00
Trent Mick
e1c977e623 Drop the '-M' option from 'triton ssh' added recently for #52.
A better alternative now is 'ssh $(triton ip <inst>)'.
2016-03-11 15:24:40 -08:00
Trent Mick
1e2b19c0e6 joyent/node-triton#103 triton ip <inst> to output the instance's primaryIp 2016-03-11 15:21:19 -08:00
Trent Mick
4f778f696e joyent/node-triton#52 Fix interactive 'triton ssh <inst>' sessions; add workaround for #52 2016-03-11 14:58:27 -08:00
Trent Mick
3824623404 PUBAPI-1266: Add instance disable-firewall/enable-firewall to node-triton (CR changes)
- Don't need confirmation/--force for undoable commands
- Drop duration from output message after wait. I don't feel the time
  for this should ever be long enough that the number is interesting
  enough to get top billing.
- If "shouldn't get here", then it should be an assert.
- Use '_' instead of '__' for unused options.
- Drop the res.instId hack. Instead use a partial faux instance
  in place where other endpoints return a machine object.
- Add 'tritoninstance' bash completion.
- Group together with 'fwrules' in 'triton inst' help output.
2016-03-11 11:24:44 -08:00
Trent Mick
c227e15ae3 Slightly more helpful message when 'triton profile ls' has no results 2016-03-11 10:24:55 -08:00
Marsell Kukuljevic
fb20159053 PUBAPI-1266 - add support for instance (en|dis)able-firewall. 2016-03-12 01:09:26 +11:00
Trent Mick
e8d1fb578b joyent/node-triton#97 triton profile set - to set the *last* profile as current 2016-03-10 11:42:23 -08:00
Trent Mick
1dc156d87c bumpver for subsequent work 2016-03-09 09:23:48 -08:00
Trent Mick
9ff74ed54b 4.7.0 2016-03-09 09:23:43 -08:00
Trent Mick
e3b5e6b016 joyent/node-triton#101 Bash completion for server-side data: instances, images, etc. 2016-03-09 09:22:33 -08:00
Marsell Kukuljevic
7d635fc81c PUBAPI-1268 - remove spinner (-ww) from snapshot create/delete. 2016-03-08 23:09:29 +11:00
Trent Mick
ba16f0d9ef joyent/node-triton#99 triton profile set ... alias for set-current
Also add 'triton profile set <TAB>' completion.
2016-03-02 22:53:32 -08:00
Trent Mick
f50797b4a4 bumpver for subsequent work 2016-03-02 00:07:22 -08:00
Trent Mick
2e09059945 4.6.0 2016-03-02 00:07:14 -08:00
Trent Mick
88c52d1610 node-triton#98 triton inst get ID fails obtusely on a destroyed instance 2016-03-02 00:05:06 -08:00
Trent Mick
1d0bafcc5e slightly improved bash complete install notes; fix 'make check' 2016-03-01 23:02:40 -08:00
Marsell Kukuljevic
634350018a PUBAPI-1233/PUBAPI-1234 Add firewalls and snapshots to node-triton
triton fwrule ...
triton instance snapshot ...

Reviewed-By: Trent Mick (with some contributions)
2016-03-01 22:38:21 -08:00
Trent Mick
a1df6e0fe7 node-triton#52 Fix 'triton ssh ...' stdout/stderr to fully flush with node >= 4.x
Fixes #52
2016-02-26 15:28:33 -08:00
Trent Mick
31ef581980 bumpver for subsequent work 2016-02-15 10:23:18 -08:00
Trent Mick
7a7c8c3044 4.5.2 2016-02-15 10:23:10 -08:00
Trent Mick
99a083e232 changelog for #93 #94 fix 2016-02-15 10:12:10 -08:00
Kris Shannon
bcf55f0a63 node-triton#94 Protect against cache lookup returning null
If the cache file does not exist _cacheGetJson will not pass any value to the callback.

In the getImage function the callback passed to _cacheGetJson assumes if there was no error value then the second argument must be an array.

Add some logic to guard against null values before iterating over the array.
2016-02-15 10:09:04 -08:00
Trent Mick
314a05b082 node-triton#95 Add test case for 'triton img create', changelog update 2016-02-15 10:03:08 -08:00
Kris Shannon
6703363934 node-triton#95 Patch missing rename from 4760defd05
The fix for joyent/node-triton#88 renamed the metadataandtags
function 'tagsFromOpts' to 'tagsFromCreateOpts' but didn't
rename all of it's uses.
2016-02-15 09:28:44 -08:00
Trent Mick
6397c8bade lovely time savings running tests in parallel via 'prove'. Thanks Perl land! 2016-02-12 15:04:41 -08:00
Trent Mick
c8f6c05bf4 bumpver for subsequent work 2016-02-12 11:17:36 -08:00
Trent Mick
3bc04b4c77 4.5.1 2016-02-12 11:17:07 -08:00
Trent Mick
70c7bbd434 node-triton#90 triton CLI should summarize err.body.errors from CloudAPI 2016-02-12 11:09:55 -08:00
Trent Mick
7f24f0235a cutarelease: add date to release tag comment 2016-02-11 21:33:39 -08:00
Trent Mick
cf9258aab7 bumpver for subsequent work 2016-02-11 09:57:00 -08:00
Trent Mick
d7bf3eced0 4.5.0 2016-02-11 09:56:55 -08:00
Trent Mick
ccef25767b node-triton#88 changelog update, bump minor ver 2016-02-11 09:56:32 -08:00
Trent Mick
4760defd05 joyent/node-triton#88: triton inst ... support for updating tags 2016-02-11 09:46:43 -08:00
Trent Mick
5b6980e490 bumpver for subsequent work 2016-02-10 14:45:53 -08:00
Trent Mick
fbee966b01 4.4.4 2016-02-10 14:45:34 -08:00
Trent Mick
c785a430c6 node-triton#90 Update sshpk and smartdc-auth to attempt to deal with multiple package inter-deps 2016-02-10 14:36:45 -08:00
Trent Mick
da9e80d94e Don't state that 'triton inst list' can filter by tags, it can't yet. That is #74. 2016-02-03 11:57:07 -08:00
Trent Mick
d89f03083e bumpver for subsequent work 2016-02-02 22:03:47 -08:00
Trent Mick
d00d468f51 4.4.3 2016-02-02 22:03:37 -08:00
Trent Mick
edb28b6791 node-triton#86 "triton profiles" should not require a current profile 2016-02-02 22:03:01 -08:00
Trent Mick
e5c666a509 bumpver for subsequent work 2016-02-02 11:06:05 -08:00
Trent Mick
67c48e0018 4.4.2 2016-02-02 11:05:50 -08:00
Trent Mick
9b6527a5d0 example script using TritonApi to list instances 2016-02-02 10:48:47 -08:00
Trent Mick
59b804fe6c support triton.createClient(...) without requiring a configDir 2016-02-02 10:47:34 -08:00
Trent Mick
e91f8f28da bumpver for subsequent work 2016-01-28 13:45:26 -08:00
Trent Mick
bd3f4068c5 4.4.1 2016-01-28 13:45:16 -08:00
Trent Mick
54923c9ae3 Windows path fixes (should basically run on windows now)
Fixes #83.
2016-01-28 13:41:22 -08:00
Trent Mick
666b541e42 bumpver for subsequent work 2016-01-25 23:48:21 -08:00
Trent Mick
839d7bfc40 4.4.0 2016-01-25 23:48:10 -08:00
Trent Mick
72bb22f07d fix test error if CNS is enabled in the DC 2016-01-25 23:36:05 -08:00
Trent Mick
810f0add56 node-triton#78 'triton image delete IMAGE' 2016-01-25 23:23:36 -08:00
Trent Mick
879e86efa3 node-triton#79 triton instance get NAME doesn't have dns_names CNS field 2016-01-25 16:12:14 -08:00
Trent Mick
b81b9b0e2d PUBAPI-1227: document the inconsistency that ListMachines shows *docker* instances, but ListImages does NOT show docker images 2016-01-25 13:26:57 -08:00
Trent Mick
2655a3a14e bumpver for subsequent work 2016-01-19 14:45:08 -08:00
Trent Mick
59e96a13a4 4.3.1 2016-01-19 14:44:56 -08:00
Trent Mick
77efb2a7a2 triton create error in v4.3.0
Fixes #77
2016-01-19 14:44:30 -08:00
Trent Mick
f062d6f206 bumpver for subsequent work 2016-01-19 12:32:37 -08:00
Trent Mick
aa84645c69 4.3.0 2016-01-19 12:32:28 -08:00
Trent Mick
8d235b8e28 'triton image create' et al
Fixes #76: `triton image create ...` and `triton image wait ...`
Fixes #72: want `triton image` to still return image details even when it is not in 'active' state
2016-01-19 12:30:46 -08:00
Trent Mick
d199ed8503 bumpver for subsequent work 2016-01-18 11:58:59 -08:00
Trent Mick
4a64e8cec4 4.2.0 2016-01-18 11:58:47 -08:00
Trent Mick
989a43f367 bash completion for 'triton account update <TAB>' 2016-01-18 11:58:01 -08:00
Trent Mick
5ee94b04af joyent/node-triton#75 'triton account update ...' 2016-01-14 22:56:38 -08:00
Trent Mick
6fe9de3adf bumpver for subsequent work 2016-01-14 12:19:54 -08:00
Trent Mick
f21c65ecf3 4.1.0 2016-01-14 12:19:29 -08:00
Trent Mick
80fc6b2b4e changelog style tweak 2016-01-14 12:18:59 -08:00
Trent Mick
fb8aec6229 unhide 'triton completion' 2016-01-14 09:22:39 -08:00
Trent Mick
01dc55e93f joyent/node-triton#73 triton instance list --credentials to include "metadata.credentials" in instance listing 2016-01-14 09:22:39 -08:00
Marsell Kukuljevic
09954b3f55 clistyle: some more tritonapi tests. 2016-01-14 09:15:58 +11:00
Trent Mick
b8d7194ee8 fix markdown rendering 2016-01-13 12:20:58 -08:00
Trent Mick
545d0a3a65 joyent/node-triton#35 More easily distinguish KVM and LX and Docker images and instances
Also support filtering insts on docker=true.
2016-01-13 12:18:05 -08:00
Trent Mick
5d6446775c bumpver for subsequent work 2016-01-12 10:28:11 -08:00
Trent Mick
77e9033cb2 4.0.1 2016-01-12 10:28:03 -08:00
Trent Mick
9fbb2006bc add 'triton env -t' 2016-01-12 10:27:46 -08:00
Trent Mick
91ca036fbe joyent/node-triton#66 update readme for CLI changes 2016-01-10 11:58:18 -08:00
Trent Mick
853e430b0c bumpver for subsequent work 2016-01-10 11:49:44 -08:00
Trent Mick
166a64705a 4.0.0 2016-01-10 11:49:33 -08:00
Trent Mick
38bc7c4037 add 'versioncheck' to 'make check' 2016-01-10 11:49:06 -08:00
Trent Mick
0b10fdb3b3 tidy up changelog 2016-01-10 11:46:07 -08:00
Trent Mick
3f427adff1 clistyle: updated bash completion (much improved, fixes limitation that subsubcmds weren't supported) 2016-01-09 16:55:12 -08:00
Trent Mick
922ca13816 clistyle: Much improved bash completions (importantly for the clistyle changes, it supports subsubcommands) 2016-01-08 11:08:07 -08:00
Marsell Kukuljevic
ea759565c7 clistyle: some account key UI fixes. 2016-01-05 23:14:42 +11:00
Trent Mick
88bbfdfba7 clistyle: networks should be hidden, no whoami 2016-01-04 22:57:24 -08:00
Trent Mick
105b7bf9a4 clistyle: 'triton account ...' 2016-01-04 22:51:04 -08:00
Trent Mick
13b525cca7 clistyle: 'triton network ...' 2016-01-04 21:53:52 -08:00
Trent Mick
d2c20a5161 clistyle: rest of 'triton instance ...' 2016-01-04 13:08:16 -08:00
Trent Mick
accf1f969e clistyle: Fix help output cli basic test for recent changes 2016-01-04 12:28:47 -08:00
Marsell Kukuljevic
fb80f734f6 clistyle: fix path problem with keys tests. 2016-01-05 07:15:38 +11:00
Trent Mick
83c6448202 clistyle: Some fixes to integration tests for 'triton inst' changes 2016-01-04 10:46:57 -08:00
Trent Mick
a05de11fb3 clistyle: some CR on marsell's commit 1dfac84 2016-01-04 10:46:57 -08:00
Trent Mick
e21635eaa3 clistyle: part of 'triton instance ...' 2016-01-04 10:46:56 -08:00
Marsell Kukuljevic
96216c6e61 clistyle: add support for account keys, expand subcommand tests,
some trivial bug fixes.
2016-01-04 10:46:24 -08:00
Trent Mick
f4246b5faf clistyle: triton profile ... 2016-01-04 10:46:24 -08:00
Trent Mick
632d5a6568 style: triton img ... 2016-01-04 10:45:50 -08:00
Trent Mick
559162896f clistyle: triton pkg ... 2016-01-04 10:45:50 -08:00
Trent Mick
36a979b1b1 bumpver for subsequent work 2015-12-31 10:06:01 -08:00
Trent Mick
99761889ce 3.6.0 2015-12-31 10:05:50 -08:00
Trent Mick
adb4c851b9 joyent/node-triton#68 Support specifying networks at instance create time 2015-12-31 09:55:31 -08:00
Trent Mick
04905ccc22 bumpver for subsequent work 2015-12-30 16:13:46 -08:00
Trent Mick
7b0446d9df 3.5.0 2015-12-30 16:13:38 -08:00
Trent Mick
81572f1d6d verbump to make previous bump in changelog 2015-12-30 16:13:01 -08:00
Trent Mick
8f666b7aca joyent/node-triton#67 triton create should support assigning tags
Fixes #67.
2015-12-30 16:12:17 -08:00
Trent Mick
5d1ef7dee0 bumpver for subsequent work 2015-12-29 15:14:35 -08:00
Trent Mick
4468711cdc 3.4.2 2015-12-29 15:14:25 -08:00
Trent Mick
b50f0c8aa1 joyent/node-triton#63 "triton images" with a filter should not be cached 2015-12-29 14:53:49 -08:00
Trent Mick
f66e50c770 joyent/node-triton#65 Fix 'triton profile(s)' handling when the user has no profiles yet 2015-12-29 14:32:27 -08:00
Trent Mick
baddfbf814 update 'triton pkgs -h' help output to mention support for field filters 2015-12-17 15:39:32 -08:00
Trent Mick
1c624f5a27 bumpver for subsequent work 2015-12-16 11:01:03 -08:00
Trent Mick
260579790a 3.4.1 2015-12-16 11:00:56 -08:00
Trent Mick
aa198b33c8 joyent/node-triton#60 display vcpus in triton packages output 2015-12-16 10:56:22 -08:00
Trent Mick
5ea3b1862a sorted stable fields for 'triton rbac role ROLE' output; use 'add' for added fields in 'triton rbac apply' resource updates 2015-12-16 10:47:59 -08:00
drew
432c962f82 swap datacenter to data center 2015-12-15 16:21:24 -08:00
drew
7ebcea48a8 Change link to datacenters page 2015-12-15 15:22:51 -08:00
Trent Mick
e009cda593 changelog for recent changes 2015-12-09 12:02:22 -08:00
Trent Mick
d15c5893b3 Fix triton rbac role NAME. 2015-12-09 12:01:24 -08:00
Trent Mick
d25df7c011 Add '-d,--data DATA' option to triton cloudapi ...
Also fix '-H,--header' option to `triton cloudapi`. It never worked.
2015-12-09 12:01:24 -08:00
Trent Mick
6e44318f00 bumpver for subsequent work 2015-12-08 13:56:01 -08:00
Trent Mick
f80370935a 3.4.0 2015-12-08 13:55:52 -08:00
Trent Mick
21164320c7 improvements for using node-triton as a module 2015-12-08 11:59:45 -08:00
Trent Mick
a40e0d6f8c bumpver for subsequent work 2015-12-07 11:47:55 -08:00
Trent Mick
80cf4414c6 3.3.0 2015-12-07 11:47:46 -08:00
Trent Mick
440d09f8b7 joyent/node-triton#59 triton create -m,--metadata etc. for adding metadata on instance creation 2015-12-07 11:28:59 -08:00
Trent Mick
dfacb92445 bumpver for subsequent work 2015-12-02 11:19:21 -08:00
Trent Mick
2870390144 3.2.0 2015-12-02 11:19:10 -08:00
Trent Mick
87808306de bump ver 2015-12-02 11:18:59 -08:00
Trent Mick
e1a5c406ba ignore all test config files 2015-12-02 10:59:48 -08:00
Trent Mick
4576918909 joyent/node-triton#58: triton --act-as=ACCOUNT ... for operator accounts 2015-12-02 10:52:47 -08:00
Trent Mick
5c3a4adee3 rbac: Fix bugs in 'triton rbac policy NAME' and updating a policy in 'triton rbac apply' 2015-12-02 10:05:21 -08:00
Trent Mick
3cd4b0d735 'triton --accept-version VER ...' developer option 2015-11-25 11:04:44 -08:00
Trent Mick
48b6be58f4 attempt to fix GFM markdown rendering to not have headers for the '- #NNN' bullets 2015-11-24 16:42:31 -08:00
Trent Mick
88826317f7 bumpver for subsequent work 2015-11-24 16:40:55 -08:00
Trent Mick
063b36ba61 3.1.0 2015-11-24 16:40:47 -08:00
Trent Mick
4a46310a8d experimental 'triton env [PROFILE]' 2015-11-24 16:40:17 -08:00
Trent Mick
212903922a is no longer hidden 2015-11-24 12:34:53 -08:00
Trent Mick
82443e2d67 joyent/node-triton#54 'triton rbac apply --dev-create-keys-and-profiles' 2015-11-23 16:57:58 -08:00
Trent Mick
6918fb93f7 joyent/node-triton#54 'triton rbac reset', 'triton [rbac] keys' default output changes, 'triton rbac apply' implicit usage of './rbac-user-keys' dir, drop shortIds for 'triton rbac ...' 2015-11-21 12:41:16 -08:00
Trent Mick
1160fe120b joyent/node-triton#54 'triton rbac info' improvements 2015-11-18 14:18:29 -08:00
Trent Mick
fe73063d16 bumpver for subsequent work 2015-11-18 13:54:20 -08:00
Trent Mick
6616c9d53b 3.0.0 2015-11-18 13:52:58 -08:00
Trent Mick
7c8554bf14 joyent/node-triton#54 'triton rbac apply' 2015-11-18 12:54:44 -08:00
Alex Wilson
00cdb81287 joyent/node-triton#55 PUBAPI-1166 Use http-signature for generating Authorization, not sprintf
Reviewed by: Trent Mick <trent.mick@joyent.com>
2015-11-13 16:46:09 -08:00
Trent Mick
dfbbf309e9 joyent/node-triton#54 'triton rbac {instance,image,network,package,}role-tags ...' 2015-11-12 16:13:23 -08:00
Trent Mick
cd611dafde joyent/node-triton#54 'triton rbac role-tags' and refactoring to make adding other *-role-tags easy 2015-11-12 16:04:12 -08:00
Trent Mick
4e45e4061f joyent/node-triton#54 a start at 'triton rbac info', add 'triton rbac instance-role-tags' 2015-11-09 15:09:37 -08:00
Trent Mick
74b8f3e42e joyent/node-triton#54 first pass at 'triton rbac key' and 'triton rbac keys' (with feeling, aka the new files actually added) 2015-11-05 15:21:19 -08:00
Trent Mick
dd0a70820b joyent/node-triton#54 first pass at 'triton rbac key' and 'triton rbac keys' 2015-11-05 15:13:14 -08:00
Trent Mick
4491a55093 Better help output for 'triton keys', also avoid double newlines between key output. 2015-11-05 12:41:56 -08:00
Trent Mick
c7daecc6f3 joyent/node-triton#54 first pass at 'triton rbac policy' and 'triton rbac policies' 2015-11-05 12:30:06 -08:00
Trent Mick
6b1065b24d Don't print 'user: undefined' for the env profile for 'triton profile' if not defined 2015-11-05 11:33:59 -08:00
Trent Mick
dd95ab5f4a joyent/node-triton#54 first pass at 'triton rbac role' and 'triton rbac roles' 2015-11-04 15:38:38 -08:00
Trent Mick
1652662e2c joyent/node-triton#54 Complete first pass at 'triton rbac user' and 'triton rbac users' 2015-11-04 00:11:19 -08:00
Trent Mick
8a46d23268 joyent/node-triton#54 A start at RBAC support (still very early) 2015-11-03 15:40:59 -08:00
Trent Mick
d4ba912955 Ensure CLI printing of usage info on UsageError doesn't blow up on a help *function* 2015-11-03 14:39:37 -08:00
Trent Mick
5bf78491ff Fix bug with 'CLI.tritonapi' getter creating a TritonApi client multiple times. 2015-11-03 14:39:18 -08:00
Trent Mick
a85391f023 minor fixes/tweaks to help output of a few commands 2015-10-29 15:24:37 -07:00
Trent Mick
80a8dde876 bumpver for subsequent work 2015-10-21 18:11:17 -07:00
Trent Mick
96b4bf5f28 2.1.4 2015-10-21 18:11:07 -07:00
Trent Mick
a8a275ceca joyent/node-triton#51 update deps to get a bunyan > dtrace-provider that works with node v4.2 2015-10-21 18:09:40 -07:00
Trent Mick
37dd71b8f8 update bunyan dep to get newer dtrace-provider to fix build against node 4.2.1 2015-10-21 16:49:39 -07:00
Trent Mick
59c41886d1 minor command help tweaks 2015-10-19 09:03:39 -07:00
Trent Mick
da447939d2 triton create ... --firewall for CreateMachine firewall_enabled
Fixes #49
2015-10-17 12:43:24 -07:00
Trent Mick
78f7d3c72e bumpver for subsequent work 2015-10-14 13:28:39 -07:00
Trent Mick
699a438921 2.1.3 2015-10-14 13:28:16 -07:00
Trent Mick
b1fcb6eab4 changelog for triton rm 2015-10-14 13:28:09 -07:00
Dave Eddy
08527b433c delete "rm" alias, fixes #44 2015-10-14 16:15:45 -04:00
Trent Mick
f1e90cdab8 triton profile ... doesn't use the profile from TRITON_PROFILE envvar
Fixes #43
2015-10-14 11:45:41 -07:00
Alex Wilson
2a0c7d9f55 bumpver for subsequent work 2015-10-13 14:41:22 -07:00
Alex Wilson
a5d15c7739 2.1.2 2015-10-13 14:40:20 -07:00
Alex Wilson
584a73ce0d joyent/node-triton#42 Tools using sshpk should lock in an exact version 2015-10-13 14:37:39 -07:00
Trent Mick
0fff92f9a8 changelog note for recent change 2015-10-07 23:04:09 -07:00
Brian Bennett
525952a663 Compatibility with ed25519 keys in ssh-agent 2015-10-07 22:28:11 -07:00
Trent Mick
8fb0b696f7 bumpver for subsequent work 2015-10-07 15:51:29 -07:00
Trent Mick
57310740eb 2.1.1 2015-10-07 15:51:20 -07:00
Trent Mick
f7d9dc0ba2 joyent/node-triton#40 Divorce wanted between profile keyId and keyId actually sent to server
fixes #40
2015-10-07 15:49:49 -07:00
Trent Mick
0ee966e1c8 joyent/node-triton#39 change the test config 'destructiveAllowed' var to 'writeActionsAllowed'
Fixes #39
2015-10-07 12:19:26 -07:00
Trent Mick
796f4d069e bumpver for subsequent work 2015-10-06 23:33:40 -07:00
Trent Mick
741abc968e 2.1.0 2015-10-06 23:33:29 -07:00
Trent Mick
a71a1ddba3 fix 'name' var usage; style nit on for-loop usage for early out 2015-10-06 23:33:18 -07:00
Trent Mick
b73766d399 style nits, no functional change 2015-10-06 23:28:25 -07:00
Trent Mick
8ece8d0024 Integration test config handling improvements. Add 'ResourceNotFound' error and fine tune exit status handling.
Fixes #37.
2015-10-06 23:24:42 -07:00
Dave Eddy
d79083b9a1 start/stop/reboot/delete take multiple arguments, fixes #38 2015-10-06 16:54:55 -04:00
Dave Eddy
3cbf85a121 show image name and version when UUID is specified, fixes #29 2015-10-05 16:34:24 -04:00
Trent Mick
a67341b1b0 'make test' now runs unit and integration tests.
'make test-unit' just the unit tests (pre-commit updated to just the latter
Fix one typo in destructiveAllowed.
2015-10-05 07:31:49 -07:00
Trent Mick
a26164f01f trying for "$version" message for the commit that is the release commit 2015-10-05 06:47:15 -07:00
Dave Eddy
b225896a5e place config require logic inside try/catch 2015-10-01 12:28:06 -04:00
Dave Eddy
3ba8f312f1 add TRITON_TEST_PROFILE and TRITON_TEST_DESTRUCTIVE_ALLOWED 2015-10-01 12:27:05 -04:00
Trent Mick
b9df8e6693 prep for future dev 2015-09-30 15:14:41 -07:00
Trent Mick
f3b8386c40 prepare for 2.0.0 release 2015-09-30 15:14:33 -07:00
Trent Mick
dced8d2256 not sure we need a 'make clean' before publishing 2015-09-30 15:14:05 -07:00
Trent Mick
c538670dd4 prepping for using 'make cutarelease' for quick release tagging and publishing 2015-09-30 15:08:26 -07:00
Trent Mick
82239010d3 ensure cruft doesn't get into npm published tarballs 2015-09-30 15:00:46 -07:00
Trent Mick
2853627e89 bump to 2.0.0 for new 'triton' npm package name 2015-09-30 14:37:28 -07:00
Dave Eddy
faca038854 exports main module 2015-09-29 18:15:08 -04:00
Dave Eddy
e7109071de cloudapi and tritonapi exports should match 2015-09-29 18:13:34 -04:00
Dave Eddy
464851843a add "list" and "ls" aliases for "instances", fixes #34 2015-09-29 18:01:18 -04:00
Dave Eddy
e3335c5dc2 add profiles tests 2015-09-29 14:45:52 -04:00
Dave Eddy
055c64efc5 move triton wrapper to h.safeTriton, add profile comment 2015-09-29 14:45:34 -04:00
Dave Eddy
1eefcccf38 more integration tests 2015-09-29 12:53:34 -04:00
Trent Mick
a01c7eede6 'triton profile -a' fix when invalid keyId 2015-09-28 12:27:58 -07:00
Trent Mick
daecb2979b export 'promptField', broken by refactor before commit 2015-09-28 12:24:50 -07:00
Trent Mick
aeebcf19f0 'triton profile -a' from stdin, JSON file or interactively 2015-09-28 12:20:21 -07:00
Trent Mick
a5ee77a48e profile(s) in cli-subcommands test 2015-09-25 14:16:16 -07:00
Trent Mick
1759b29f2f Add test/ to make check, and fix that. 2015-09-25 12:24:37 -07:00
Trent Mick
bf21ac467a 'triton profile{,s}' all except 'triton profile -a' 2015-09-25 12:19:29 -07:00
Dave Eddy
403e4bd204 opts.skip for destructive stuff 2015-09-25 13:45:16 -04:00
Dave Eddy
2eeb68cba5 document destructiveAllowed, use TRITON_* vars for tests 2015-09-25 13:24:12 -04:00
Trent Mick
f38bfb68f1 should support TRITON_KEY_ID as well 2015-09-25 10:22:58 -07:00
Dave Eddy
7169b06772 destructive => destructiveAllowed 2015-09-25 13:13:39 -04:00
Dave Eddy
e5c5f2d54c use env profile for integration tests 2015-09-25 13:10:39 -04:00
Dave Eddy
04c7b638d7 key_id => keyId 2015-09-25 13:08:00 -04:00
Dave Eddy
4bbb43fcc9 foundation for integration tests 2015-09-24 17:48:26 -04:00
Trent Mick
b238fcf52f improve on a DEPTH_ZERO_SELF_SIGNED_CERT error
from:
    triton account: error: DEPTH_ZERO_SELF_SIGNED_CERT
to this:
    triton account: error (SelfSignedCert): could not access CloudAPI https://10.88.88.3 because it uses a self-signed TLS certificate and your current profile is not configured for insecure access: DEPTH_ZERO_SELF_SIGNED_CERT
2015-09-23 21:10:52 -07:00
Trent Mick
ee07395eae joyent/node-triton#30 triton commands blow up obtusely if getting HTML content back from cloudapi endpoints
Fixes #30
2015-09-23 12:32:09 -07:00
Trent Mick
818a6b0afe no bigspinner by default: use '-ww' for a spinner, '-www' bigger, '-wwww' max 2015-09-23 09:30:06 -07:00
Dave Eddy
289d9389a4 bump tabula, fixes #21 2015-09-23 12:25:48 -04:00
Dave Eddy
6cc9fa3e70 integration tests for all sub commands usage 2015-09-22 13:55:42 -04:00
Dave Eddy
ee9f897f22 filter for name and version if supplied 2015-09-21 18:57:53 -04:00
Dave Eddy
46927aeed7 integration tests updates
- support Array or String when using execPlus
- pass SSH_AUTH_SOCK to support ssh-agent signing
- use current node binary with process.execPath
- config.insecure defaults to false if undefined
2015-09-21 18:48:59 -04:00
Dave Eddy
a6d9bad267 filter image version on the server 2015-09-21 17:16:47 -04:00
Dave Eddy
aaeb58730b fix create UUID bug 2015-09-21 17:12:33 -04:00
Trent Mick
0c2ade98ba Fix "insecure":true in a profile
This was broken in commit 99d9113eae
2015-09-21 14:07:07 -07:00
Dave Eddy
b4900ea626 remove UUID_RE in favor of isUUID 2015-09-21 17:02:10 -04:00
Dave Eddy
7ab6453b1f remove broken cache files if found, fixes #27 2015-09-21 17:00:58 -04:00
Dave Eddy
44ce942d97 triton create support image name@version format, fixes #25 2015-09-21 16:37:48 -04:00
Trent Mick
5ed72ea117 Fix 'make check' 2015-09-21 12:37:59 -07:00
Trent Mick
99d9113eae joyent/node-triton#28 profile opts (-i, -a, -k, -u) aren't being applied to profiles other than the env profile
Profile/CloudAPI top-level CLI options are now applied to the current
profile. Also clean up loading of the 'env' profile a bit so that
special casing of that is more hidden in "config.js".

Also add support for the TRITON_URL, TRITON_TLS_INSECURE and
TRITON_ACCOUNT envvars. (I didn't add TRITON_KEY_ID because a coming
change will do better than that.)

Fixes #28, #24.
2015-09-21 12:34:37 -07:00
Dave Eddy
5c89bd32c3 trition png 2015-09-21 14:05:22 -04:00
Dave Eddy
6159bf404d remove glob dep, fixes #20 2015-09-21 14:01:00 -04:00
Trent Mick
d0bb926584 joyent/node-triton#22 logo in README is white-on-white
Pick a different one. Also commit it to this repo to not get surprised
by joyent.com changes.

Fixes #22.
2015-09-21 10:51:39 -07:00
Trent Mick
94ebdb9f47 stop TritonApi.listImages passing internal options as query params to cloudapi
E.g.: `useCache` in the following:

    $ triton -v insts
    ...
    [2015-09-21T17:37:57.600Z] TRACE: triton/34623 on danger0.local (/Users/trentm/joy/node-triton/node_modules/restify-clients/lib/HttpClient.js:265 in rawRequest): request sent
        GET /trentm/images?useCache=true HTTP/1.1
        Host: 10.88.88.6
        date: Mon, 21 Sep 2015 17:37:57 GMT
        authorization: Signature keyId="/trentm/keys/de:e7:73:...
        accept: application/json
        user-agent: triton/1.0.0 (x64-darwin; node/0.10.40)
        accept-version: *
2015-09-21 10:41:13 -07:00
Trent Mick
c93b08dd68 joyent/node-triton#26 triton command crashes with self-signed cert
Refactor do_instances to not call `callback` twice. Also don't assume
if a `res` on an error callback from CloudApi._request.

Fixes #26.
2015-09-21 10:33:42 -07:00
Trent Mick
7aa59f148f joyent/node-triton#19 stricter checking of profile files when loading them 2015-09-09 21:53:38 -07:00
Trent Mick
5e75bff3fe -p profile option should be hidden until complete 2015-09-09 16:14:34 -07:00
Trent Mick
bc7750dc45 clean up whitespace 2015-09-09 16:09:44 -07:00
Trent Mick
d2e999916b joyent/node-triton#18 first cut of 'triton profiles' 2015-09-09 16:04:15 -07:00
Angela Fong
3699dd3a46 Fix typos and formatting in README 2015-09-08 18:30:42 -07:00
Dave Eddy
f476cc9168 audit cleanup 2015-09-08 17:30:08 -04:00
Angela Fong
c1dcef2f0f README update to include more examples and background info 2015-09-08 12:04:30 -07:00
Dave Eddy
9d314def3d add basic instance-audit support 2015-09-08 12:41:31 -04:00
Dave Eddy
219912beb1 docs 2015-09-05 19:43:40 -04:00
Dave Eddy
f28d7a079a sadly change package name 2015-09-05 19:20:38 -04:00
Dave Eddy
789fb4f7e5 slug asserts 2015-09-04 20:33:31 -04:00
Dave Eddy
072f0ec864 make triton cloudapi more like curl
- `-X method` to specify method
- `-H 'key: value'` to specify header
2015-09-04 16:27:56 -04:00
Dave Eddy
d8a5f247f2 add make test to npm test 2015-09-04 14:38:14 -04:00
Dave Eddy
da0831d523 TODO done 2015-09-04 14:12:52 -04:00
Dave Eddy
bdc3ea6651 mpl and copyright 2015-09-04 14:12:20 -04:00
Dave Eddy
0fee17fda0 getting ready for OSS
- s/triton/tritonapi/
- s/Triton/TritonApi/
- s/CloudAPI/CloudApi/
2015-09-04 14:05:36 -04:00
Trent Mick
17c7a84ace use latest published cmdln with bash completions 2015-09-04 10:55:10 -07:00
Dave Eddy
11543f23b8 info cleanup 2015-09-04 13:44:40 -04:00
Dave Eddy
7a7c204140 style fixes 2015-09-04 13:01:55 -04:00
Dave Eddy
3dd84362d0 completion handled by node-cmdln 2015-09-04 13:01:00 -04:00
Trent Mick
411659180f bash completion support 2015-09-04 00:09:19 -07:00
Trent Mick
f6f0843200 right alignment of numerical columns for 'triton pkgs' 2015-09-03 23:52:12 -07:00
Dave Eddy
de2a462f16 triton info cleanup, fixes #8 2015-09-03 18:24:39 -04:00
Dave Eddy
edcc2a52ed Merge pull request #10 from joyent/dave.eddy-1441318327
use mkdirp to create cache directories
2015-09-03 18:19:18 -04:00
Dave Eddy
af97077dc8 Merge pull request #9 from joyent/dave.eddy-1441318129
add common.getCliTableOptions for tabula
2015-09-03 18:19:06 -04:00
Dave Eddy
f3aad05fc9 use mkdirp to create cache directories 2015-09-03 18:12:08 -04:00
Dave Eddy
e48395d446 add common.getCliTableOptions for tabula
- common tabula options managed in "common"
- "datacenters" and "services" pass raw data with -j
2015-09-03 18:09:21 -04:00
Trent Mick
19ede6e489 joyent/node-triton#7 "triton create" could better handle missing package argument 2015-09-03 10:19:02 -07:00
Dave Eddy
804fe155b9 sprintf was a lie - use format everywhere 2015-09-03 00:02:40 -04:00
Dave Eddy
716976efa8 pass create data in post body 2015-09-03 00:02:09 -04:00
Dave Eddy
1cc87edb0f per-profile cache directory using account+url slug 2015-09-02 23:48:14 -04:00
Dave Eddy
da8e13b45e more assertions in common, ensure tests run with assertions 2015-09-02 23:30:07 -04:00
Dave Eddy
b6e4c06742 tabula changes
- "datacenters" and "services" both use tabula
- comman tabula options moved to common
2015-09-02 23:24:08 -04:00
Dave Eddy
538eb7612a fix git-hook directive in Makefile
- symlinks are relative to the destination file
- don't check if it exists first - just obliterate
2015-09-02 23:23:31 -04:00
Dave Eddy
782c82faa7 remove executable bits on non-executable files 2015-09-02 16:33:30 -04:00
Dave Eddy
c3a91411f9 lots of unit tests 2015-09-02 15:38:27 -04:00
Trent Mick
c028501d66 TODOne 2015-09-02 10:47:50 -07:00
Trent Mick
018c46ef43 'make check' clean 2015-09-02 10:47:06 -07:00
Trent Mick
db81ff9db2 TODO 2015-09-02 10:39:09 -07:00
Trent Mick
516cf29bc5 TODOne 2015-09-02 10:28:11 -07:00
Trent Mick
68a99fa81c drop cruft and debugging/commented code 2015-09-02 01:06:00 -07:00
Trent Mick
4064b93c8e joyent/node-triton#5 show usage info on usage errors 2015-09-02 01:04:20 -07:00
Trent Mick
58a7c9977b joyent/node-triton#4 triton networks/network to support shortId as well and to include fabric
flag
2015-09-02 00:03:17 -07:00
Dave Eddy
ae8a19d568 quick jsstyle cleanup 2015-09-01 15:03:52 -04:00
Dave Eddy
0d53e878e9 javascript lint cleanup 2015-09-01 14:51:02 -04:00
Dave Eddy
09052c0f80 silence make check file list 2015-09-01 14:45:56 -04:00
Trent Mick
d9b104de4b 'make git-hooks' to install hooks 2015-09-01 10:55:39 -07:00
Dave Eddy
d76535b4d2 negative sizes support, add more tests 2015-09-01 13:47:35 -04:00
Trent Mick
12c9cb64a6 Factor out spinner to prep for using it for 'triton wait'.
Also refactor 'triton wait' for debuggability and to avoid possible
multiple calls to the callback.
2015-09-01 10:44:34 -07:00
Trent Mick
9241f90ccf 'make check-jsl' clean 2015-09-01 10:44:34 -07:00
Dave Eddy
42f0ee5a41 fix bug in humanSizeFromBytes 2015-09-01 13:44:10 -04:00
Dave Eddy
c4f85db8d8 support for triton ssh user@id 2015-09-01 10:16:42 -04:00
Trent Mick
333c47ae26 a start at a test framework 2015-09-01 00:31:00 -07:00
Trent Mick
3c90b321fd a very small start at 'make check' clean 2015-08-31 16:56:26 -07:00
Trent Mick
f0f8062f8f TODO 2015-08-31 15:19:10 -07:00
Trent Mick
fd0fa369ab triton wait: change signature to take states as an arg, and multiple separate instance name/id args 2015-08-31 15:16:58 -07:00
Trent Mick
022471afa7 triton packages: make --human the default, use -p to override. Nicer, some what kludged sorting by group and memory 2015-08-31 15:16:58 -07:00
Dave Eddy
016e2d94f0 unnecessary call to common function 2015-08-31 17:14:48 -04:00
Trent Mick
dfc3e013b6 'triton insts' default output changes
Change default short and --long output of 'triton insts' somewhat
per feedback from Angela.

I also dropped validFields and turned on dottedLookup so you can do
stuff like:
    triton insts -o shortid,name,tags.foo,metadata.root_authorized_keys
2015-08-31 13:11:34 -07:00
Trent Mick
e145090c79 Fix handling of SDC_TESTING envvar (really really this time) 2015-08-31 12:31:06 -07:00
Trent Mick
1b2ed0758e Fix handling of SDC_TESTING envvar 2015-08-31 12:23:20 -07:00
Trent Mick
5e3efa02a6 'triton packages --human' for M/G/T units on sizes 2015-08-31 12:13:17 -07:00
Trent Mick
233e8ee784 reduce mkdir noise in log for every 'triton ...' run
E.g.:
    $ triton -v inst 1c7f40f6-a253-49f3-94d6-8f0656440696 2>&1 | bunyan
    [2015-08-31T17:57:02.682Z]  INFO: triton/24726 on danger0.local (/Users/trentm/joy/node-triton/lib/cli.js:188): failed to make dir /Users/trentm/.triton (err.code=EEXIST)
        Error: EEXIST, file already exists '/Users/trentm/.triton'
            at Object.fs.mkdirSync (fs.js:654:18)
            at /Users/trentm/joy/node-triton/lib/cli.js:186:24
            at Array.forEach (native)
            at CLI.triton (/Users/trentm/joy/node-triton/lib/cli.js:184:29)
            at CLI.do_instance (/Users/trentm/joy/node-triton/lib/do_instance.js:16:9)
            at CLI.dispatch (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:664:13)
            at /Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:425:18
            at CLI.init (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:477:5)
            at CLI.init (/Users/trentm/joy/node-triton/lib/cli.js:221:26)
            at CLI.main (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:404:10)
    [2015-08-31T17:57:02.684Z]  INFO: triton/24726 on danger0.local (/Users/trentm/joy/node-triton/lib/cli.js:188): failed to make dir /Users/trentm/.triton/cache (err.code=EEXIST)
        Error: EEXIST, file already exists '/Users/trentm/.triton/cache'
            at Object.fs.mkdirSync (fs.js:654:18)
            at /Users/trentm/joy/node-triton/lib/cli.js:186:24
            at Array.forEach (native)
            at CLI.triton (/Users/trentm/joy/node-triton/lib/cli.js:184:29)
            at CLI.do_instance (/Users/trentm/joy/node-triton/lib/do_instance.js:16:9)
            at CLI.dispatch (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:664:13)
            at /Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:425:18
            at CLI.init (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:477:5)
            at CLI.init (/Users/trentm/joy/node-triton/lib/cli.js:221:26)
            at CLI.main (/Users/trentm/joy/node-triton/node_modules/cmdln/lib/cmdln.js:404:10)
    ...
2015-08-31 11:16:58 -07:00
Trent Mick
786ec9771c fix error in using a full UUID for 'triton inst UUID' and others 2015-08-31 11:14:14 -07:00
Trent Mick
92ad1af94e shuffle cmd order in 'triton help' output. 'info' is a nice one to have first 2015-08-31 11:14:07 -07:00
Trent Mick
da566100ab some clarifying help docs on 'triton wait' 2015-08-31 11:13:39 -07:00
Trent Mick
c63eb29600 update smartdc-auth to get ssh-agent fix for node 0.12 2015-08-31 10:08:31 -07:00
Trent Mick
6b16530c99 TODO udpates, MPL-2 license 2015-08-26 18:06:12 -07:00
Trent Mick
9e3df02a5e shortid support for instances; --url,--account et al top-level options 2015-08-26 17:22:02 -07:00
Dave Eddy
5b60fffc04 whoops 2015-08-26 20:08:52 -04:00
Dave Eddy
0ccda0af10 add listDatacenters 2015-08-26 19:59:28 -04:00
Dave Eddy
769e9bbe2b listServices 2015-08-26 19:56:18 -04:00
Dave Eddy
f1e46a7b21 attempting a simple completion file 2015-08-26 18:10:15 -04:00
Trent Mick
16c3747605 smooth bigspinner 2015-08-26 15:01:54 -07:00
Dave Eddy
83b1cf188f do_network 2015-08-26 17:09:50 -04:00
Dave Eddy
f11bf0c247 jsonStream should actually stream 2015-08-26 16:53:23 -04:00
Dave Eddy
ffb0a935a3 use wrapper functions 2015-08-26 16:44:11 -04:00
Dave Eddy
d2ce855a69 do_networks 2015-08-26 16:40:50 -04:00
Dave Eddy
835e1895bf output state changes with "wait" 2015-08-26 16:15:31 -04:00
Dave Eddy
255fff221b add docs 2015-08-26 16:09:40 -04:00
Dave Eddy
9b57df6962 add wait-instance 2015-08-26 15:16:01 -04:00
Trent Mick
4a82869e2e TODO 2015-08-26 12:06:23 -07:00
Dave Eddy
945b631878 triton bin in package.json 2015-08-26 14:57:19 -04:00
Dave Eddy
532cead66f allow multiple filters to instances and packages 2015-08-26 14:03:22 -04:00
Trent Mick
0cfa83dedc fix 'triton create -w' that I broke recently 2015-08-26 10:13:09 -07:00
Dave Eddy
20aea1de10 whoops 2015-08-26 13:05:50 -04:00
Trent Mick
d5e5b81ac3 shortid by default for 'triton packages' 2015-08-26 10:02:01 -07:00
Dave Eddy
83fbcc129c weeks and minutes cleanup 2015-08-26 13:00:01 -04:00
Dave Eddy
a5213658fa config, cache images 2015-08-26 12:59:12 -04:00
Trent Mick
d6ac9fed33 triton create --dry-run 2015-08-26 09:36:28 -07:00
Dave Eddy
e61c6099b3 combine delete with start/stop/reboot 2015-08-26 12:18:40 -04:00
Dave Eddy
e2edbb3215 better bessages for start/stop/reboot/delete 2015-08-26 12:18:24 -04:00
Trent Mick
1d0fa26633 shortid by default for 'triton images', works for 'triton image SHORTID', 'triton create ...' 2015-08-26 09:15:17 -07:00
Trent Mick
d0ee7f4153 TODO update 2015-08-25 23:57:18 -07:00
Trent Mick
ade64f48c6 TODO udpate 2015-08-25 23:30:43 -07:00
Trent Mick
e355693d8e an early start at bash completion (up to and including the subcommand) 2015-08-25 23:28:35 -07:00
231 changed files with 37400 additions and 2227 deletions

7
.gitignore vendored
View File

@ -1,3 +1,8 @@
/node_modules
/tmp
/build
/test/*.json
/npm-debug.log
/triton-*.tgz
.DS_Store
.git
*.swp

12
.npmignore Normal file
View File

@ -0,0 +1,12 @@
/.gitmodules
/node_modules
/tmp
/test/*.json
/npm-debug.log
/deps
/examples
/tools
/Makefile
/TODO.txt
/test
/triton-*.tgz

View File

@ -1 +1,893 @@
# node-triton changelog
Known issues:
- `triton ssh ...` disables ssh ControlMaster to avoid issue #52.
## not yet released
(nothing)
## 7.0.0
- [Backward incompatible.] `triton image get NAME|SHORTID` will now *exclude*
inactive images by default. Before this change inactive images (e.g. those
with a state of "creating" or "unactivated" or "disabled") would be
included. Use the new `-a,--all` option to include inactive images. This
matches the behavior of `triton image list [-a,--all] ...`.
- [joyent/node-triton#258] `triton instance create IMAGE ...` will now exclude
inactive images when looking for an image with the given name.
## 6.3.0
- [joyent/node-triton#259] Added basic support for use of SSH bastion hosts
to access zones on private fabrics. If the `tritoncli.ssh.proxy` tag is set
on an instance, `triton ssh` will look up the name or UUID of the proxy
instance and use `ssh -o ProxyJump` to tunnel the connection to the target.
If the `tritoncli.ssh.ip` tag is set on an instance, `triton ssh` will use
that IP address instead of the `primaryIp` when making its connection.
## 6.2.0
- [joyent/node-triton#255, joyent/node-triton#257] Improved the interface
and documentation of `triton network create` and `triton vlan create`. In
particular, it is now possible to specify static routes and DNS resolvers.
## 6.1.2
- [joyent/node-triton#249] Error when creating or deleting profiles when
using node v10.
## 6.1.1
- [TRITON-598] Fix error handling for `triton network get-default` when
no default network is set on the account.
## 6.1.0
- [joyent/node-triton#250] Avoid an error from `triton profile list` if
only *some* of the minimal `TRITON_` or `SDC_` envvars are defined.
- [TRITON-401] Add `triton network` and `triton vlan` commands, for
creating/changing/removing network fabrics and VLANs.
- [TRITON-524] Add `triton inst get --credentials ...` option to match
`triton inst list --credentials ...` for including generated credentials
in instance metadata.
- [joyent/node-triton#245] `triton profile` now generates fresh new keys during
Docker setup and signs them with an account key, rather than copying (and
decrypting) the account key itself. This makes using Docker simpler with keys
in an SSH Agent.
- [TRITON-53] x-account image clone. A user can make a copy of a shared image
using the `triton image clone` command.
- [TRITON-53] A shared image (i.e. when the user is on the image.acl) is no
longer provisionable by default - you will need to explicitly add the
--allow-shared-images cli option when calling `triton create` command to
provision from a shared image (or clone the image then provision from the
clone).
- [TRITON-52] x-DC image copy. A user can copy an image that they own into
another datacenter within the same cloud using the `triton image copy` cli
command. Example:
```
triton -p us-east-1 image cp my-custom-image us-sw-1
```
## 6.0.0
This release containes some breaking changes with the --affinity flag to
`triton instance create`. It also does not work with cloudapi endpoints older
than 8.0.0 (mid 2016); for an older cloudapi endpoint, use node-triton 5.9.0.
- [TRITON-167, TRITON-168] update support for
`triton instance create --affinity=...`. It now fully supports regular
expressions, tags and globs, and works across a wider variety of situations.
Examples:
```
# regular expressions
triton instance create --affinity='instance!=/^production-db/' ...
# globs
triton instance create --affinity='instance!=production-db*' ...
# tags
triton instance create --affinity='role!=db'
```
See <https://apidocs.joyent.com/cloudapi/#affinity-rules> for more details
how affinities work.
However:
- Use of regular expressions requires a cloudapi version of 8.8.0 or later.
- 'inst' as a affinity shorthand no longer works. Use 'instance' instead.
E.g.: --affinity='instance==db1' instead of --affinity='inst==db1'
- The shorthand --affinity=<INST> no longer works. Use
--affinity='instance===<INST>' instead.
## 5.10.0
- [TRITON-19] add support for deletion protection on instances. An instance with
the deletion protection flag set true cannot be destroyed until the flag is
set false. It is exposed through
`triton instance create --deletion-protection ...`,
`triton instance enable-deletion-protection ...`, and
`triton instance disable-deletion-protection ...`. This flag is only supported
on cloudapi versions 8.7.0 or above.
- [TRITON-59] node-triton should support nic operations
`triton instance nic get ...`
`triton instance nic create ...`
`triton instance nic list ...`
`triton instance nic delete ...`
- [TRITON-42] node-triton should support nics when creating an instance, e.g.
`triton instance create --nic <Network Object> IMAGE PACKAGE`
## 5.9.0
- [TRITON-190] remove support for `triton instance create --brand=bhyve ...`.
The rest of bhyve support will remain, but selection of bhyve brand will
happen via images or packages that are bhyve-specific.
## 5.8.0
- [TRITON-124] add node-triton support for bhyve. This adds a `triton instance
create --brand=bhyve ...` option that can be used for zvol images that support
it. Note that bhyve support is alpha in TritonDC -- most datacenters won't yet
support this option.
## 5.7.0
- [TRITON-116] node-triton image sharing. Adds `triton image share` and
`triton image unshare` commands.
## 5.6.1
- [PUBAPI-1470] volume objects should expose their creation timestamp in a
property named "created" instead of "create_timestamp".
## 5.6.0
- [TRITON-30] Add UpdateNetworkIP to node-triton, e.g.
`triton network ip update`
- [TRITON-24] node-triton ListNetworkIPs has unordered results, e.g.
`triton network ip list NETWORK`
- [TRITON-88] node-triton "env" doesn't call its callback
## 5.5.0
- [PUBAPI-1452] Add ip subcommand to network, e.g.
`triton network ip`.
## 5.4.0
- [joyent/node-triton#74, TOOLS-1872] Filter instance list by tag, e.g.
`triton instance list tag.foo=bar`.
## 5.3.2
- [joyent/node-triton#187] DTraceProviderBindings errors on FreeBSD.
- [joyent/node-triton#226] added new `triton volume sizes` subcommand.
- [PUBAPI-1420] added support for mounting volumes in LX and SmartOS instances.
E.g., `triton instance create --volume VOLUME ...`.
## 5.3.1
- [joyent/node-triton#222] Fix the matching environment variable for the
`triton -r,--role ROLE ...` option to be `TRITON_ROLE` instead of
`MANTA_ROLE`.
- [joyent/node-triton#201] Fix `triton -r,--role ROLE ...` option for taking up
an RBAC role. This was introduced in v4.12.0 and was accidentally broken
in v5.0.0.
- [joyent/node-triton#217] `triton volume ls -l` should output a `RESOURCE`
column.
## 5.3.0
- [joyent/node-triton#173], [joyent/node-triton#174] and
[joyent/node-triton#175] Add support for creating and managing NFS shared
volumes. New `triton volume` commands are available:
* `triton volume create` to create NFS shared volumes
* `triton volume list` to list existing volumes
* `triton volume get` to get information about a given volume
* `triton volume delete` to delete one or more volumes
Use `triton volume --help` to get help on all of these commands.
Note that these commands are hidden for now. They will be made visible by
default once the server-side support for volumes is shipped in Triton.
## 5.2.1
- [joyent/node-triton#193] Fix possible CLI crash with `triton ssh ...` if the
instance's image doesn't have any tags.
- [joyent/node-triton#213] commands fail unhelpfully when `cliSetupTritonApi`
returns error (this includes e.g. supplying an incorrect key fingerprint,
which no longer results in a cryptic stack trace and crash)
## 5.2.0
- [joyent/node-triton#197] Create triton image export command
## 5.1.1
- [joyent/node-triton#190] Fix `triton profile create|docker-setup` breakage
with latest "17.03.\*" versions of `docker` installed.
- [joyent/node-triton#148] Fix `triton profile edit ...` to work with an
"EDITOR" environment variable with quotes and spaces.
- [joyent/node-triton#183] `triton profile create` will no longer use ANSI
codes for styling if stdout isn't a TTY.
## 5.1.0
- [joyent/node-triton#182] Add `-y, --yes` options to `triton profile create`
and `triton profile docker-setup` to allow non-interactive setup.
## 5.0.0
- [joyent/node-triton#108] Support for passphrase-protected private keys.
Before this work, an encrypted private SSH key (i.e. protected by a
passphrase) would have to be loaded in an ssh-agent for the `triton`
CLI to use it. Now `triton` will prompt for the passphrase to unlock
the private key (in memory), if needed. For example:
$ triton package list
Enter passphrase for id_rsa: <passphrase entered interactively here>
SHORTID NAME MEMORY SWAP DISK VCPUS
14ad9d54 g4-highcpu-128M 128M 512M 3G -
14ae2634 g4-highcpu-256M 256M 1G 5G -
...
- **BREAKING CHANGE for module usage of node-triton.**
To implement joyent/node-triton#108, the way a TritonApi client is
setup for use has changed from being (unrealistically) sync to async.
Client preparation is now a multi-step process:
1. create the client object;
2. initialize it (mainly involves finding the SSH key identified by the
`keyId`); and,
3. optionally unlock the SSH key (if it is passphrase-protected and not in
an ssh-agent).
`createClient` has changed to take a callback argument. It will create and
init the client (steps 1 and 2) and takes an optional `unlockKeyFn` parameter
to handle step 3. A new `mod_triton.promptPassphraseUnlockKey` export can be
used for `unlockKeyFn` for command-line tools to handle prompting for a
passphrase on stdin, if required. Therefore what used to be:
var mod_triton = require('triton');
try {
var client = mod_triton.createClient({ # No longer works.
profileName: 'env'
});
} catch (initErr) {
// handle err
}
// use `client`
is now:
var mod_triton = require('triton');
mod_triton.createClient({
profileName: 'env',
unlockKeyFn: mod_triton.promptPassphraseUnlockKey
}, function (err, client) {
if (err) {
// handle err
}
// use `client`
});
See [the examples/ directory](examples/) for more complete examples.
Low-level/raw handling of the three steps above is possible as follows
(error handling is elided):
var mod_bunyan = require('bunyan');
var mod_triton = require('triton');
// 1. create
var client = mod_triton.createTritonApiClient({
log: mod_bunyan.createLogger({name: 'my-tool'}),
config: {},
profile: mod_triton.loadProfile('env')
});
// 2. init
client.init(function (initErr) {
// 3. unlock key
// See top-comment in "lib/tritonapi.js".
});
- [joyent/node-triton#143] Fix duplicate output from 'triton rbac key ...'.
- [joyent/node-triton#157] Add `triton instance resize ...` command and
`TritonApi.resizeInstance` method.
- [joyent/node-triton#129] Fix `triton reboot --wait` to properly wait. Before
it would often return immediately, before the instance started rebooting.
Add `--wait-timeout N` option to `triton reboot`.
Also add `TritonApi#rebootInstance()` api method.
- [joyent/node-triton#166] Update sshpk to fix issue with the TLS client cert
created by `triton profile docker-setup` so that it doesn't create a cert that
Go's TLS library doesn't like.
- [joyent/node-triton#156] Providing all required profile options as command
line flags (account, url, keyId) no longer produces an incomplete profile
error.
- PUBAPI-1171/PUBAPI-1205/PUBAPI-1351 The handling of legacy `SDC_*`
environment variables has been cleaned up. These environment
variables are used for compatibility with the node-smartdc toolset.
* `SDC_TESTING` is now evaluated the same way as node-smartdc. Any
set value but the empty string is true.
* Errors on boolean environment variables will now identify the
variable at fault.
* `triton env` will emit additional comments grouping variables.
- [joyent/node-triton#80] Add `triton network list public=true|false`
filtering. Note that this filtering is client-side.
- [joyent/node-triton#146] Add `--wait` flag to `triton instance rename`.
- [joyent/node-triton#133] Add `triton inst fwrule list` and `triton fwrules`
shortcuts for the existing `triton inst fwrules` and `triton fwrule list`,
respectively.
- [joyent/node-triton#3] triton ssh command not aware of "ubuntu" login for
ubuntu-certified images.
- [joyent/node-triton#137] Improve the handling for the getting started case
when a user may not have envvars or a profile setup.
- [joyent/node-triton#158] tritonapi image cache never expires
- [joyent/node-triton#153] Bump restify-clients dep. Thanks, github.com/tomgco.
## 4.15.0
- [joyent/node-triton#64] Support 'triton instance rename ...' (by
github.com/YangYong3).
- [trentm/node-dashdash#30, joyent/node-triton#144] Change the output used by
Bash completion support to indicate "there are no completions for this
argument" to cope with different sorting rules on different Bash/platforms.
For example:
$ triton -p test2 package get <TAB> # before
##-no -tritonpackage- completions-##
$ triton -p test2 package get <TAB> # after
##-no-completion- -results-##
## 4.14.2
- TOOLS-1592 First workaround for a possible BadDigestError when using
node v6.
## 4.14.1
- TOOLS-1587 'triton profile docker-setup' fails when path to 'docker' has
spaces. This can help on Windows where Docker Toolbox installs docker.exe
to "C:\Program Files\Docker Toolbox".
- [#136] bash completion for `triton profile create --copy <TAB>`
## 4.14.0
- [#130] Include disabled images when using an image cache (e.g. for filling in
image name and version details in `triton ls` output.
## 4.13.0
- [#120] Don't fail `triton instance list` if the retrieval of *image* info
(retrieved to get image name and version, as a bonus) fails with an
authorization error -- in case it is an RBAC failure where a subuser can
ListMachines, but not ListImages.
- [#113] *Usage* errors now some "error help", including option or command
synopses. Some examples (the new thing is marked with `>`):
- Command synopses when argument errors:
```
$ triton create
triton instance create: error (Usage): incorrect number of args
> usage: triton instance create [OPTIONS] IMAGE PACKAGE
```
- Option synopsis with option errors:
```
$ triton image ls --bogus
triton image ls: error (Option): unknown option: "--bogus"
> usage: triton image ls [ --help | -h ] [ --all | -a ] [ -H ] [ -o field1,... ]
> [ --long | -l ] [ -s field1,... ] [ --json | -j ] ...
```
- Suggested command name misspellings:
```
$ triton in
triton: error (UnknownCommand): unknown command: "in"
> Did you mean this?
> info
> inst
```
## 4.12.0
- [#120] `triton -r,--role ROLE ...` option to take up an RBAC role(s).
## 4.11.0
- [#112] Fix `triton completion`, broke a while back.
- [#111] `triton env --unset,-u` option to emit environment commands to *unset*
relevant envvars.
- Unhide `triton env` from `triton --help` output.
## 4.10.0
- [#82] Affinity (a.k.a. locality hints) support for instance creation, e.g.:
# Use same server as instance 'db0':
triton create -a instance==db0 ...
triton create -a db0 ... # shortcut for same thing
# Use different server than instance 'db0':
triton create -a 'instance!=db0' ...
# *Attempt* to use same server as instance 'db0', but don't fail
# if cannot. This is called a "non-strict" or "soft" rule.
triton create -a instance==~db0 ...
# *Attempt* to use a different server than instance 'db0':
triton create -a 'instance!=~db0' ...
"Affinity" here refers to providing rules for deciding on which server
a new instance should by provisioned. Rules are in terms of existing
instances. As a shortcut, 'inst' can be used in place of 'instance'
above (e.g. `triton create -a 'inst!=db0' ...`).
## 4.9.0
- [#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
This profile setup for Docker requires making requests to the
CloudAPI endpoint which can fail (e.g. if CloudAPI is down, credentials
are invalid, etc.). You can use the `--no-docker` option to skip
the Docker setup part of profile creation.
- 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
- #103 `triton ip <inst>` to output the instance's primaryIp
- #52 Workaround for `triton ssh ...`. In version 4.6.0, `triton ssh ...`
interactive sessions were broken. This version reverts that change and adds
a workaround for #52 (by disabling ControlMaster when spawning `ssh`).
See <https://github.com/joyent/node-triton/issues/52> for details.
- #97 `triton profile set -` to set the *last* profile as current.
- PUBAPI-1266 Added `instance enable-firewall` and `instance disable-firewall`
## 4.7.0
**Known issue: `triton ssh` interactive sessions are broken.
Upgrade to v4.7.1.**
- #101 Bash completion for server-side data: instances, images, etc.
Bash completion on TAB should now work for things like the following:
`triton create <TAB to complete images> <TAB to complete packages`,
`triton inst tag ls <TAB to complete instances>`. Cached (with a 5 minute
TTL) completions for the following data are supported: instances, images,
packages, networks, fwrules, account keys.
See `triton completion --help` for adding/updating Bash completion.
- #99 `triton profile set ...` alias for `set-current`
## 4.6.0
**Known issue: `triton ssh` interactive sessions are broken.
Upgrade to v4.7.1.**
- #98 `triton inst get ID` for a deleted instance will now emit the instance
object and error less obtusely. This adds a new `InstanceDeleted` error code
from `TritonApi`.
- PUBAPI-1233 firewalls: `triton fwrule ...`
- PUBAPI-1234 instance snapshots: `triton inst snapshot ...`
- #52 Fix 'triton ssh ...' stdout/stderr to fully flush with node >= 4.x.
## 4.5.2
- #95 Fix breakage of `triton image create` in v4.5.0. (By Kris Shannon.)
- #94, #93 `triton inst create ...` is broken if "images.json" cache file
is missing. (By Kris Shannon.)
## 4.5.1
- #92 `triton` CLI should summarize `err.body.errors` from CloudAPI
Per <https://github.com/joyent/eng/blob/master/docs/index.md#error-handling>,
CloudAPI error response will sometimes have extra error details to show.
## 4.5.0
- #88 'triton inst tag ...' for managing instance tags.
## 4.4.4
- #90 Update sshpk and smartdc-auth to attempt to deal with multiple package
inter-deps.
## 4.4.3
- #86 Ensure `triton profile ls` and `triton profile set-current` work
when there is no current profile.
## 4.4.2
- Support `triton.createClient(...)` creation without requiring a
`configDir`. Basically this then fallsback to a `TritonApi` with the default
config.
## 4.4.1
- #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
- #78 `triton image delete IMAGE`
- #79 Fix `triton instance get NAME` to make sure it gets the `dns_names` CNS
field.
- PUBAPI-1227: Note that `triton image list` doesn't include Docker images, at
least currently.
## 4.3.1
- #77 triton create error in v4.3.0
## 4.3.0
**Bad release. Use >=4.3.1.**
- #76 `triton image create ...` and `triton image wait ...`
- #72 want `triton image` to still return image details even when it is not in 'active' state
## 4.2.0
- Bash completion: Add completion for *args* to `triton account update <TAB>`.
This isn't perfect because a space is added after completion of "FIELD=",
but hopefully is helpful.
- #75 `triton account update ...`
## 4.1.0
- Unhide `triton completion` so hopefully more find it and use it.
- node-triton#73 `triton instance list --credentials` to include
"metadata.credentials" in instance listing.
- node-triton#35 More easily distinguish KVM and LX and Docker images and
instances.
In PUBAPI-1161 CloudAPI (v8.0.0) started exposing IMG.type, INST.brand and
INST.docker. One of the main issues for users is that telling KVM ubuntu
from LX ubuntu is confusing (see also joyent/smartos-live#532).
tl;dr:
- `triton image list` default output now includes the `type` instead of
`state`. The `state` column is still in output with `-l`, `-j`,
`-o state`.
- `triton instance list` default output now includes a `flags` column
instead of `primaryIp`. The 'D' and 'K' flags identify Docker and KVM
instances.
- `triton instance list -l` includes the brand.
Default output examples showing the various cases (and the attempt to
stay within 80 columns):
```bash
$ triton imgs
SHORTID NAME VERSION FLAGS OS TYPE PUBDATE
1bd84670 minimal-64-lts 14.4.2 P smartos zone-dataset 2015-05-28
b67492c2 base-64-lts 14.4.2 P smartos zone-dataset 2015-05-28
ffe82a0a ubuntu-15.04 20151105 P linux lx-dataset 2015-11-05
8a1dbc62 centos-6 20160111 P linux zvol 2016-01-11
$ triton insts
SHORTID NAME IMG STATE FLAGS AGE
da7c6edd cocky_noyce 3d996aaa running DF 10m
deedeb42 ubu0 ubuntu-15.04@20151105 running - 9m
aa9ccfda mini2 minimal-64-lts@14.4.2 running - 9m
e8fc0b96 centi0 centos-6@20160111 running K 8m
```
- Filtering instances on `docker=true`:
```bash
$ triton insts docker=true
SHORTID NAME IMG STATE FLAGS AGE
da7c6edd cocky_noyce 3d996aaa running DF 13m
```
## 4.0.1
- Add `triton env -t` to be able to emit a shell environment to configure `triton` itself.
This allows one to have the following Bash function to select a Triton profile for
`triton` and node-smartdc tooling:
function triton-select { eval $(triton env $1); }
## 4.0.0
- [backwards incompat] #66 New consistent `triton` CLI style. See [the
issue](https://github.com/joyent/node-triton/issues/66) for discussion.
The major changes is that where some sub-commands used to be some
flavour of:
triton things # list all the things
triton thing ID # get a thing
triton thing -a ID # create a new thing
Now commands are consistently:
triton thing list # list all the things
triton thing get ID # get a thing
triton thing create ... # create a new thing
...
The most annoying incompatility is the need for "get" to
get a thing. E.g.:
BEFORE AFTER
triton img blah triton img get blah
triton inst web0 triton inst get web0
For *listing* things, there is typically a shortcut with
the old form, e.g. `triton images` is a shortcut for
`triton image list`.
Currently all of the CLI *except* the experimental `triton rbac ...`
is converted to the new consistent style.
- [backwards incompat] `triton whoami` was dropped. This used to be a shortcut
for `triton account get`. It could possibly come back.
- *Much* improved [Bash
completion](https://github.com/joyent/node-triton#bash-completion). See
`triton completion -h` for notes on how to install.
- Add the ability to create a profile copying from an existing profile,
via `triton profile create --copy NAME`.
- `triton key add` was added (<https://apidocs.joyent.com/cloudapi/#CreateKey>).
## 3.6.0
- #67 Add `triton create --network,-N NETWORK ...` option for specifying
networks for instance creation. "NETWORK" is a network id, name, or
short id; or a comma-separated array of networks.
## 3.5.0
- #67 Add `triton create --tag|-t ...` option for adding tags on instance creation.
E.g. `triton create -n NAME -t foo=bar -t @my-tags-file.json IMAGE PACKAGE`.
## 3.4.2
- #63 "triton images" with a filter should not be cached.
- #65 Fix `triton profile(s)` handling when the user has no profiles yet.
## 3.4.1
- #60 Display `vcpus` in `triton packages` output.
- Add `-d,--data <data>` option to `triton cloudapi`.
- Fix `triton rbac role ROLE`. Also get that command to have a stable order for the
displayed fields.
## 3.4.0
- Improvements for using node-triton as a module. E.g. a simple example:
var triton = require('triton');
var client = triton.createClient({profileName: 'env'});
client.listImages(function (err, imgs) {
console.log(err);
console.log(imgs);
});
See the README and "lib/index.js" for more info.
## 3.3.0
- #59 CLI options to `triton create` to add metadata on instance creation:
- `triton create -m,--metadata KEY=VALUE` to add a single value
- `triton create -m,--metadata @FILE` to add values from a JSON
or key/value-per-line file
- `triton create -M,--metadata-file KEY=FILE` to set a key from a file
- `triton create --script FILE` to set the special "user-script" key
from a file
## 3.2.0
- #58 `triton --act-as=ACCOUNT ...` for an operator account to auth as
themself, but operator on another account's resources. Note that operator
accesses like this are audited on the CloudAPI server side.
- `triton --accept-version VER` hidden top-level option for development. This
allows calling the target cloudapi with the given value for the
"Accept-Version" header -- which is how CloudAPI does API versioning.
By default `triton` is coded to a particular cloudapi version range, so
forcing a different version *could* result in breaking in the triton client
code that handles the response. IOW, this is just a tool for developers
of this Triton client and CloudAPI itself.
## 3.1.0
- New (hidden for now, i.e. experimental) `triton env ...` to dump
`eval`able shell commands for
[node-smartdc](https://github.com/joyent/node-smartdc) environment setup for
a given Triton CLI profile. E.g.:
eval $(triton env east1)
sdc-listmachines
I think this should grow to support setting up Docker env as well.
- #54 `triton rbac role-tags` for now can't be hidden (as long we have the
need to role-tag raw resource URLs like '/my/images').
- #54 `triton rbac apply --dev-create-keys-and-profiles` for
experimenting/dev/testing to quickly generate and add user keys and setup
Triton CLI profiles for all users in the RBAC config.
- #54 RBAC support, see <https://docs.joyent.com/public-cloud/rbac> to start.
- `triton rbac info` improvements: better help, use brackets to show
non-default roles.
- `triton rbac reset`
- change `triton rbac user USER` output a little for the 'keys' (show
the key fingerprint and name instead of the key content), 'roles',
and 'default_roles' fields.
- #54 *Drop* support for shortIds for `triton rbac {users,roles,policies}`
commands. They all have unique *`name`* fields, just use that.
- #54 `triton rbac apply` will implicitly look for a user key file at
"./rbac-user-keys/$login.pub" if no `keys` field is provided in the
"rbac.json" config file.
- Change default `triton keys` and `triton rbac keys` output to be tabular.
Otherwise it is a little obtuse to see fingerprints (which is what currently
must be included in a profile). `triton [rbac] keys -A` can be used to
get the old behaviour (just the key content, i.e. output appropriate
for "~/.ssh/authorized\_keys").
## 3.0.0
- #54 RBAC support, see <https://docs.joyent.com/public-cloud/rbac> to start.
- [Backward incompatible.] The `triton` CLI option for the cloudapi URL has
changed from `--url,-u` to **`--url,-U`**.
- Add `triton --user,-u USER` CLI option and `TRITON_USER` (or `SDC_USER`)
environment variable support for specifying the RBAC user.
- `triton profiles` now shows the optional `user` fields.
- A (currently experimental and hidden) `triton rbac ...` command to
house RBAC CLI functionality.
- `triton rbac users` to list all users.
- `triton rbac user ...` to show, create, edit and delete users.
- `triton rbac roles` to list all roles.
- `triton rbac role ...` to show, create, edit and delete roles.
- `triton rbac policies` to list all policies.
- `triton rbac policy ...` to show, create, edit and delete policies.
- `triton rbac keys` to list all RBAC user SSH keys.
- `triton rbac key ...` to show, create, edit and delete user keys.
- `triton rbac {instance,image,network,package,}role-tags ...` to list
and manage role tags on each of those resources.
- `triton rbac info` will dump a summary of the full current RBAC
state. This command is still in development.
- `triton rbac apply` will synchronize a local RBAC config (by default it
looks for "./rbac.json") to live RBAC state. Current the RBAC config
file format is undocumented. See "examples/rbac-\*" for examples.
- #55 Update of smartdc-auth/sshpk deps, removal of duplicated code for
composing Authorization headers
## 2.1.4
- #51: Update deps to get dtrace-provider 0.6 build fix for node v4.2.x.
- #49: `triton create ... --firewall` to enable [Cloud
Firewall](https://docs.joyent.com/public-cloud/network/firewall).
## 2.1.3
- #44 'triton rm' alias for delete
- #43 `triton profile ...` doesn't use the profile from `TRITON_PROFILE` envvar
## 2.1.2
- #41 Add compatibility with ed25519 keys in ssh-agent
- #42 Tools using sshpk should lock in an exact version
## 2.1.1
- #40 Update smartdc-auth so that newer OpenSSH `ssh-keygen` default
fingerprint formats for setting `keyId` work.
- #39 Test suite: Change the test config 'destructiveAllowed' var to
'writeActionsAllowed'.
## 2.1.0
- Errors and exit status: Change `Usage` errors to always have an exit status
of `2` (per common practice in at least some tooling). Add `ResourceNotFound`
error for `triton {instance,package,image,network}` with exit status `3`.
This can help tooling (e.g. the test suite uses this in one place). Add
`triton help` docs on exit status.
- Test suite: Integration tests always require a config file
(either `$TRITON_TEST_CONFIG` path or "test/config.json").
Drop the other `TRITON_TEST_*` envvars.
## 2.0.0
- Changed name to `triton` npm package, graciously given up by
[suguru](https://www.npmjs.com/~suguru) from his
<https://github.com/ameba-proteus/node-triton> project. <3
The latest previous release of the triton package was 1.0.7,
so we'll separate with a major version bump for *this* triton
package.
## 1.0.0
Initial release as `joyent-triton` npm package.

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2015, Joyent, Inc. All rights reserved.
# Copyright (c) 2015, Joyent, Inc.
#
# Makefile for node-triton
#
@ -8,7 +8,7 @@
# Vars, Tools, Files, Flags
#
JS_FILES := bin/triton \
$(shell find lib -name '*.js' | grep -v '/tmp/')
$(shell find lib test -name '*.js' | grep -v '/tmp/')
JSL_CONF_NODE = tools/jsl.node.conf
JSL_FILES_NODE = $(JS_FILES)
JSSTYLE_FILES = $(JS_FILES)
@ -25,8 +25,57 @@ all:
npm install
.PHONY: test
test:
./test/runtests
test: test-unit test-integration
.PHONY: test-unit
test-unit:
NODE_NDEBUG= ./node_modules/.bin/tape test/unit/*.test.js
.PHONY: test-integration
test-integration:
NODE_NDEBUG= ./node_modules/.bin/tape test/integration/*.test.js
.PHONY: test-in-parallel
test-in-parallel:
ls test/unit/*.test.js test/integration/*.test.js \
| parallel ./node_modules/.bin/tape \
| ./node_modules/.bin/tap-summary --no-ansi --no-progress
.PHONY: clean
clean::
rm -f triton-*.tgz
check:: versioncheck
# Ensure CHANGES.md and package.json have the same version.
.PHONY: versioncheck
versioncheck:
@echo version is: $(shell cat package.json | json version)
[[ `cat package.json | json version` == `grep '^## ' CHANGES.md | head -2 | tail -1 | awk '{print $$2}'` ]]
.PHONY: cutarelease
cutarelease: versioncheck
[[ -z `git status --short` ]] # If this fails, the working dir is dirty.
@which json 2>/dev/null 1>/dev/null && \
ver=$(shell json -f package.json version) && \
name=$(shell json -f package.json name) && \
publishedVer=$(shell npm view -j $(shell json -f package.json name)@$(shell json -f package.json version) version 2>/dev/null) && \
if [[ -n "$$publishedVer" ]]; then \
echo "error: $$name@$$ver is already published to npm"; \
exit 1; \
fi && \
echo "** Are you sure you want to tag and publish $$name@$$ver to npm?" && \
echo "** Enter to continue, Ctrl+C to abort." && \
read
ver=$(shell cat package.json | json version) && \
date=$(shell date -u "+%Y-%m-%d") && \
git tag -a "$$ver" -m "version $$ver ($$date)" && \
git push --tags origin && \
npm publish
.PHONY: git-hooks
git-hooks:
ln -sf ../../tools/pre-commit.sh .git/hooks/pre-commit
.PHONY: dumpvar
dumpvar:
@ -36,6 +85,6 @@ dumpvar:
fi
@echo "$(VAR) is '$($(VAR))'"
include ./tools/mk/Makefile.deps
include ./tools/mk/Makefile.targ
JSL_FLAGS += --nofilelist

View File

@ -1,52 +1,38 @@
`triton` is a tool for Joyent's Triton (a.k.a. SmartDataCenter), either for on-premises installations
of Triton or Joyent's Public Cloud (<https://my.joyent.com>,
<http://www.joyent.com/products/compute-service>).
![logo](https://code.spearhead.cloud/Spearhead/node-spearhead/raw/branch/master/tools/sphsp.png)
**This project is experimental and probably broken. For now, please look
at [node-smartdc](https://github.com/joyent/node-smartdc).**
# node-spearhead
# Installation
This repository holds the node-spearhead CLI tool to work with the Spearhead
Cloud. It is a fork of [node-triton](https://github.com/joyent/node-triton).
1. Install [node.js](http://nodejs.org/).
2. `npm install -g git://github.com/joyent/node-triton`
## Installation and configuration
Verify that installed and is on your PATH:
### Get a Spearhead Cloud account
$ triton --version
Triton client 1.0.0
Before you can used the CLI you'll need a Joyent account, an SSH key uploaded
and `triton` configured with those account details.
# Setup
TODO
# Getting Started
TODO
Create an account on the Spearhead Cloud and upload your SSH key. You can create an account
[here](https://spearhead.cloud/).
# node-triton differences with node-smartdc
### Data-centers
- There is a single `sdc` command instead of a number of `sdc-FOO` commands.
- The `SDC_USER` envvar is accepted in preference to `SDC_ACCOUNT`.
The list of available Spearhead Cloud data-centers is available
[here](https://spearhead.cloud/datacenters).
# cloudapi2.js differences with node-smartdc/lib/cloudapi.js
### Installation
The old node-smartdc module included an lib for talking directly to the SDC
Cloud API (node-smartdc/lib/cloudapi.js). Part of this module (node-sdc) is a
re-write of the Cloud API lib with some backward incompatibilities. The
differences and backward incompatibilities are discussed here.
Install [node.js](http://nodejs.org/), then:
- Currently no caching options in cloudapi2.js (this should be re-added in
some form). The `noCache` option to many of the cloudapi.js methods will not
be re-added, it was a wart.
- The leading `account` option to each cloudapi.js method has been dropped. It
was redundant for the constructor `account` option.
- "account" is now "user" in the CloudAPI constructor.
- All (all? at least at the time of this writing) methods in cloudapi2.js have
a signature of `function (options, callback)` instead of the sometimes
haphazard extra arguments.
npm install -g spearhead
Verify that it is installed and on your PATH:
$ spearhead --version
Spearhead CLI 6.1.4
https://code.spearhead.cloud/Spearhead/node-spearhead
Now you ca use `spearhead` to interact with our Public Cloud. More details
about installation and configuration are available
[here](https://docs.spearhead.cloud).
## License
MPL 2.0

View File

@ -1,68 +1,60 @@
bash completion
- The 'shortcut' commands use `handlerFromSubcmd(...).dispatch`. That
doesn't run the subcmd class's `.init()` method. node-cmdln should provide
a way to do this. ... basically want to call the *main()* but with preparsed
options. Perhaps the init/fini should move into dispatch?
triton delete VM|IMAGE # substring matching? too dangerous
triton delete --vm VM
triton delete --image IMAGE
triton create affinity support for tag matching, globs, regex
"shortid" instead of full UUID "id" in default output, and then allow lookup
by that shortid. Really nice for 80 columns.
note in README that full UUIDs is much faster in the API
image "name@version" in 'triton insts' table. Optionally?
*type*: cloudapi changes to clarify: LX, docker, smartos, kvm instances
# DONE
triton
triton -v # bunyan trace logging
triton images # list images
triton image ID|NAME # get image
triton packages # list packages
triton package ID|NAME
triton instances|insts # list machines
triton instance|inst ID|NAME # get machine
triton create # triton create-instance
triton cloudapi ...
triton ssh ...
triton info
# maybe today
# maybe next
PUBAPI-1117 triton create -c|--count N
Rate limiting. Testing with non-op accounts. I suspect PUBAPI-1117 and other
usage will lead to rate limiting errors from cloudapi. `triton` should
(a) retry reasonably on those error codes and (b) proactively control rate
of cloudapi requests (tunable).
triton images
Drop 'state' in default columns. Add type to be able to see lx or not
for 'linux' ones. That might hit that stupid naming problem.
# profiles
triton profile # list all profiles
triton profile NAME # show NAME profile
triton profile -a NAME # sets it as active
triton profile -n|--new # ???
For today: only the implicit 'env' profile.
# config
~/.triton/
config.json
{"currProfile": "east3b"}
east3b/ # profile
PROFILE2/
...
# another day
triton config get|set|list # see 'npm config'
triton --shell # or whatever, repl
$ triton shell
$profile> cd inst
$profile inst> ls
...
$profile inst> cd vm0
$profile inst/vm0> get
...
$profile inst/vm0> cd snapshot
$profile inst/vm0/snapshot> ls
...
extensible triton commands:
~/.triton/plugins.d/$plugin.json
Would be nice to not have to read/parse all these files for every run,
i.e. lazily. Is that a problem for `triton` showing commands list?
How do plugins in other node projects work? Is there an npm special thing
that would work?
Should plugins just be commands? Perhaps for starters, but don't hardcode
that.
$ cat build.json
{
"cmd": "build",
"desc": "Build a Triton image from a Tritonfile"
"require": "triton-plugin-build"
}

12
bin/spearhead Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*/
require('../lib/cli').main();

View File

@ -1,19 +0,0 @@
#!/usr/bin/env node
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* triton command
*/
var p = console.log;
var cmdln = require('cmdln');
var CLI = require('../lib/cli');
if (require.main === module) {
var cli = new CLI();
cmdln.main(cli, {
argv: process.argv,
showCode: true,
showNoCommandErr: false
});
}

View File

@ -1,3 +1,3 @@
{
"defaultProfile": "env"
"cacheDir": "cache"
}

View File

@ -0,0 +1,189 @@
# Functions for Bash completion of some 'triton' option/arg types.
function complete_tritonprofile {
local word="$1"
local candidates
candidates=$(ls -1 ~/.triton/profiles.d/*.json 2>/dev/null \
| sed -E 's/^.*\/([^\/]+)\.json$/\1/')
compgen $compgen_opts -W "$candidates" -- "$word"
}
#
# Get completions for a given type of Triton (server-side) data.
#
# Usage:
# _complete_tritondata $type # e.g. _complete_tritondata images
#
# The easiest/slowest thing to do to complete images would be to just call:
# triton [profile-related-args] images -Ho name
# or similar. Too slow.
#
# The next easiest would be this:
# candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
# where `triton` is setup to specially just handle completions if
# `TRITON_COMPLETE` is set. That special handling writes out a cache file to
# avoid hitting the server every time. This is still too slow because the
# node.js startup time for `triton` is too slow (around 1s on my laptop).
#
# The next choice is to (a) use the special `TRITON_COMPLETE` handling to
# fetch data from the server and write out a cache file, but (b) attempt to
# find and use that cache file without calling node.js code. The win is
# (at least in my usage) faster response time to a <TAB>. The cost is
# reproducing (imperfectly) in Bash the logic for determining the Triton profile
# info to find the cache.
#
function _complete_tritondata {
local type=$1
# First, find the Triton CLI profile.
local profile
profile=$(echo "$COMP_LINE" | grep -- '\s\+-p\s*\w\+\s\+' | sed -E 's/.* +-p *([^ ]+) +.*/\1/')
if [[ -z "$profile" ]]; then
profile=$TRITON_PROFILE
fi
if [[ -z "$profile" ]]; then
profile=$(grep '"profile":' ~/.triton/config.json | cut -d'"' -f4)
fi
if [[ -z "$profile" ]]; then
profile=env
fi
trace " profile: $profile"
# Then, determine the account and url that go into the cache dir.
# TODO: include -a/-U options that change from profile values
# TODO: subuser support
local url
local account
local profileFile
profileFile=$HOME/.triton/profiles.d/$profile.json
if [[ "$profile" == "env" ]]; then
url=$TRITON_URL
if [[ -z "$url" ]]; then
url=$SDC_URL
fi
account=$TRITON_ACCOUNT
if [[ -z "$account" ]]; then
account=$SDC_ACCOUNT
fi
elif [[ -f $profileFile ]]; then
url=$(grep '"url":' $profileFile | cut -d'"' -f4)
account=$(grep '"account":' $profileFile | cut -d'"' -f4)
fi
trace " url: $url"
trace " account: $account"
# Mimic node-triton/lib/common.js#profileSlug
local profileSlug
profileSlug="$(echo "$account" | sed -E 's/@/_/g')@$(echo "$url" | sed -E 's#^https?://##')"
profileSlug="$(echo "$profileSlug" | sed -E 's/[^a-zA-Z0-9_@-]/_/g')"
local cacheFile
cacheFile="$HOME/.triton/cache/$profileSlug/$type.completions"
trace " cacheFile: $cacheFile"
# If we have a cache file, remove it and regenerate if it is >5 minutes old.
#
# Dev Note: This 5min TTL should match what `lib/cli.js#_emitCompletions()`
# is using.
local candidates
if [[ ! -f "$cacheFile" ]]; then
candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
else
local mtime
mtime=$(stat -r "$cacheFile" | awk '{print $10}')
local ttl=300 # 5 minutes in seconds
local age
age=$(echo "$(date +%s) - $mtime" | bc)
if [[ $age -gt $ttl ]]; then
# Out of date. Regenerate the cache file.
trace " cacheFile out-of-date (mtime=$mtime, age=$age, ttl=$ttl)"
rm "$cacheFile"
candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
else
trace " cacheFile is in-date (mtime=$mtime, age=$age, ttl=$ttl)"
candidates=$(cat "$cacheFile")
fi
fi
echo "$candidates"
}
function complete_tritonpackage {
local word="$1"
candidates=$(_complete_tritondata packages)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonimage {
local word="$1"
candidates=$(_complete_tritondata images)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritoninstance {
local word="$1"
candidates=$(_complete_tritondata instances)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonnetwork {
local word="$1"
candidates=$(_complete_tritondata networks)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonvolume {
local word="$1"
candidates=$(_complete_tritondata volumes)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonfwrule {
local word="$1"
candidates=$(_complete_tritondata fwrules)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonkey {
local word="$1"
candidates=$(_complete_tritondata keys)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonaffinityrule {
local word="$1"
candidates=$(_complete_tritondata affinityrules)
# Triton affinity rules typically have '=' in them, e.g.:
# triton create -a inst==db0 ...
# This means we run afoul of the '=' in COMP_WORDBREAKS which results in
# triton create -a inst=<TAB>
# leading to:
# triton create -a inst=inst==
# The answer is to strip off at the last '=' in the returned completions.
if [[ "$word" == *"="* ]]; then
local uptolastequal
uptolastequal="${word%=*}="
compgen $compgen_opts -W "$candidates" -- "$word" \
| cut -c$(( ${#uptolastequal} + 1 ))-
else
compgen $compgen_opts -W "$candidates" -- "$word"
fi
}
function complete_tritonupdateaccountfield {
local word="$1"
local candidates
candidates="{{UPDATE_ACCOUNT_FIELDS}}"
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonupdatefwrulefield {
local word="$1"
local candidates
candidates="{{UPDATE_FWRULE_FIELDS}}"
compgen $compgen_opts -W "$candidates" -- "$word"
}

View File

@ -1,40 +0,0 @@
#!/usr/bin/env node
/**
* Example using cloudapi2.js to call cloudapi's GetAccount endpoint.
*
* Usage:
* ./example-get-account.js | bunyan
*/
var p = console.log;
var auth = require('smartdc-auth');
var bunyan = require('bunyan');
var cloudapi = require('../lib/cloudapi2');
var log = bunyan.createLogger({
name: 'test-get-account',
level: 'trace'
})
var USER = process.env.SDC_USER || process.env.SDC_ACCOUNT || 'bob';
var KEY_ID = process.env.SDC_KEY_ID || 'b4:f0:b4:6c:18:3b:44:63:b4:4e:58:22:74:43:d4:bc';
var sign = auth.cliSigner({
keyId: KEY_ID,
user: USER,
log: log
});
var client = cloudapi.createClient({
url: 'https://us-sw-1.api.joyent.com',
user: USER,
version: '*',
sign: sign,
agent: false, // don't want KeepAlive
log: log
});
log.info('start')
client.getAccount(function (err, account) {
p('getAccount: err', err)
p('getAccount: account', account)
});

235
lib/SaferJsonClient.js Normal file
View File

@ -0,0 +1,235 @@
/*
* Copyright 2012 Mark Cavage, Inc. All rights reserved.
* Copyright (c) 2015, Joyent, Inc.
*/
/*
* TODO: this should be a separate module. Both node-triton and
* node-docker-registry-client are using (slightly different versions of) this.
*
* Adapted from
* <github.com/mcavage/node-restify/blob/master/lib/clients/string_client.js>
* now at <https://github.com/restify/clients/blob/master/lib/StringClient.js>
*
* This subclasses the Restify StringClient to add the following features:
*
* 1. Extend the callback from
* callback(err, req, res, <JSON-parsed-body>);
* to:
* callback(err, req, res, <JSON-parsed-body>, <raw-body (Buffer)>);
* This allows one to work on the raw body for special case handling, if
* wanted. I'm not sure I'd propose this for restify core because it
* shouldn't add features that make it harder to go all streaming.
*
* 2. In restify.JsonClient, if the body is not parseable JSON, it log.trace's
* an error, and returns `{}` (see mcavage/restify#388). I don't particularly
* like that because it is ambiguous (and also disallows returning a JSON
* body that is false-y: `false`, `0`, `null`).
*
* Instead this client will do the following:
* (a) If the response is an error status (>=400), then return `undefined`
* for the body. This allows the caller to know if the body was parsed
* because `undefined` is not representable in JSON.
* (b) If the response is a success (<400), then return an
* InvalidContentError restify error.
*
* (TODO: I'd support this for restify code, but it *is* backward
* incompatible.)
*
* 3. `.write()` doesn't default a null `body` to `{}`.
* This change isn't because I came across the need for it, but because that
* just seems wrong.
*
* 4. Doesn't set `res.body` which restify's StringClient.parse seems to do
* ... as an accident of history I'm guessing?
*/
/* jsl:ignore */
'use strict';
/* jsl:end */
var assert = require('assert-plus');
var crypto = require('crypto');
var strsplit = require('strsplit').strsplit;
var util = require('util');
var zlib = require('zlib');
var errors = require('restify-errors');
var codeToHttpError = errors.codeToHttpError;
var RestError = errors.RestError;
var StringClient = require('restify-clients').StringClient;
// --- API
function SaferJsonClient(options) {
assert.object(options, 'options');
options.accept = 'application/json';
options.name = options.name || 'SaferJsonClient';
options.contentType = 'application/json';
StringClient.call(this, options);
this._super = StringClient.prototype;
}
util.inherits(SaferJsonClient, StringClient);
SaferJsonClient.prototype.write = function write(options, body, callback) {
assert.object(body, 'body');
// This is change #3.
var resBody = JSON.stringify(body);
return (this._super.write.call(this, options, resBody, callback));
};
SaferJsonClient.prototype.parse = function parse(req, callback) {
function parseResponse(err, res) {
var chunks = []; // gunzipped response chunks (Buffer objects)
var len = 0; // accumulated count of chunk lengths
var contentMd5;
var contentMd5Hash;
var gz;
var resErr = err;
function finish() {
var body = Buffer.concat(chunks, len);
/*
* Save the original response's body in case this is the best error
* message we can output to the user. The responsibility to use this
* property is left to the user of this custom JSON client.
*
* See lib/cli.js and joyent/node-triton#30 for a concrete use case.
*/
if (resErr) {
resErr.originalBody = body;
}
if (res.log.trace()) {
res.log.trace({body: body.toString(), len: len},
'body received');
}
// Content-Length check
var contentLength = Number(res.headers['content-length']);
if (req.method !== 'HEAD' &&
!isNaN(contentLength) && len !== contentLength)
{
resErr = new errors.InvalidContentError(util.format(
'Incomplete content: Content-Length:%s but got %s bytes',
contentLength, len));
callback(resErr, req, res);
return;
}
// Content-MD5 check.
if (contentMd5Hash &&
contentMd5 !== contentMd5Hash.digest('base64'))
{
resErr = new errors.BadDigestError('Content-MD5');
callback(resErr, req, res);
return;
}
// Parse the body as JSON, if we can.
// Note: This regex-based trim works on a buffer. `trim()` doesn't.
var obj;
if (len && !/^\s*$/.test(body)) { // Skip all-whitespace body.
try {
obj = JSON.parse(body);
} catch (jsonErr) {
res.log.trace(jsonErr, 'Invalid JSON in response');
if (!resErr) {
// TODO: Does this mask other error statuses?
resErr = new errors.InvalidContentError(
'Invalid JSON in response');
}
}
}
// Special error handling.
if (res && res.statusCode >= 400) {
// Upcast error to a RestError (if we can)
// Be nice and handle errors like
// { error: { code: '', message: '' } }
// in addition to { code: '', message: '' }.
if (obj && (obj.code || (obj.error && obj.error.code))) {
var _c = obj.code ||
(obj.error ? obj.error.code : '') ||
'';
var _m = obj.message ||
(obj.error ? obj.error.message : '') ||
'';
resErr = new RestError({
message: _m,
restCode: _c,
statusCode: res.statusCode
});
resErr.name = resErr.restCode;
if (!/Error$/.test(resErr.name)) {
resErr.name += 'Error';
}
} else if (!resErr) {
resErr = codeToHttpError(res.statusCode,
obj.message || '', body);
}
}
if (resErr) {
resErr.body = obj;
}
callback(resErr, req, res, obj, body);
}
if (!res) {
// Early out if we didn't even get a response.
callback(resErr, req);
return;
}
// Content-MD5 setup.
contentMd5 = res.headers['content-md5'];
if (contentMd5 && req.method !== 'HEAD' && res.statusCode !== 206) {
contentMd5Hash = crypto.createHash('md5');
}
if (res.headers['content-encoding'] === 'gzip') {
gz = zlib.createGunzip();
gz.on('data', function (chunk) {
chunks.push(chunk);
len += chunk.length;
});
gz.once('end', finish);
res.once('end', gz.end.bind(gz));
} else {
res.once('end', finish);
}
res.on('data', function onData(chunk) {
if (contentMd5Hash) {
contentMd5Hash.update(chunk.toString('utf8'), 'binary');
}
if (gz) {
gz.write(chunk);
} else {
chunks.push(chunk);
len += chunk.length;
}
});
}
return (parseResponse);
};
// --- Exports
module.exports = SaferJsonClient;

27
lib/bunyannoop.js Normal file
View File

@ -0,0 +1,27 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*/
/*
* A stub for a `bunyan.createLogger()` that does no logging.
*/
function BunyanNoopLogger() {}
BunyanNoopLogger.prototype.trace = function () {};
BunyanNoopLogger.prototype.debug = function () {};
BunyanNoopLogger.prototype.info = function () {};
BunyanNoopLogger.prototype.warn = function () {};
BunyanNoopLogger.prototype.error = function () {};
BunyanNoopLogger.prototype.fatal = function () {};
BunyanNoopLogger.prototype.child = function () { return this; };
BunyanNoopLogger.prototype.end = function () {};
module.exports = {
BunyanNoopLogger: BunyanNoopLogger
};

View File

@ -1,5 +1,11 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2017, Joyent, Inc.
*
* The `triton` CLI class.
*/
@ -12,27 +18,159 @@ var child_process = require('child_process'),
var cmdln = require('cmdln'),
Cmdln = cmdln.Cmdln;
var fs = require('fs');
var mkdirp = require('mkdirp');
var util = require('util'),
format = util.format;
var path = require('path');
var vasync = require('vasync');
var common = require('./common');
var constants = require('./constants');
var mod_config = require('./config');
var errors = require('./errors');
var Triton = require('./triton');
var lib_tritonapi = require('./tritonapi');
//---- globals
var p = console.log;
var packageJson = require('../package.json');
var pkg = require('../package.json');
var name = 'triton';
var log = bunyan.createLogger({
name: name,
serializers: bunyan.stdSerializers,
stream: process.stderr,
level: 'warn'
var OPTIONS = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
name: 'version',
type: 'bool',
help: 'Print version and exit.'
},
{
names: ['verbose', 'v'],
type: 'bool',
help: 'Verbose/debug output.'
},
{
names: ['profile', 'p'],
type: 'string',
completionType: 'tritonprofile',
env: 'SC_PROFILE',
helpArg: 'NAME',
help: 'Spearhead Cloud client profile to use.'
},
{
group: 'CloudAPI Options'
},
/*
* Environment variable integration.
*
* While dashdash supports integrated envvar parsing with options
* we don't use that with `triton` because (a) we want to apply *option*
* usage (but not envvars) to profiles other than the default 'env'
* profile, and (b) we want to support `TRITON_*` *and* `SDC_*` envvars,
* which dashdash doesn't support.
*
* See <https://github.com/joyent/node-triton/issues/28> for some details.
*/
{
names: ['account', 'a'],
type: 'string',
help: 'Account (login name). Environment: SC_ACCOUNT=ACCOUNT ',
helpArg: 'ACCOUNT'
},
{
names: ['act-as'],
type: 'string',
help: 'Masquerade as the given account login name. This can only ' +
'succeed for operator accounts. Note that accesses like these ' +
'are audited on the CloudAPI server side.',
helpArg: 'ACCOUNT',
hidden: true
},
{
names: ['user', 'u'],
type: 'string',
help: 'RBAC user (login name). Environment: SC_USER=USER',
helpArg: 'USER'
},
{
names: ['role', 'r'],
type: 'arrayOfCommaSepString',
env: 'SC_ROLE',
help: 'Assume an RBAC role. Use multiple times or once with a list',
helpArg: 'ROLE,...'
},
{
names: ['keyId', 'k'],
type: 'string',
help: 'SSH key fingerprint. Environment: SC_KEY_ID=FINGERPRINT.',
helpArg: 'FP'
},
{
names: ['url', 'U'],
type: 'string',
help: 'Spearhead Cloud Datacenter URL. Environment: SC_URL=URL.',
helpArg: 'URL'
},
{
names: ['J'],
type: 'string',
hidden: true,
help: 'Spearhead Cloud (SC) datacenter name. This is ' +
'a shortcut to the "https://$dc.api.spearhead.cloud" ' +
'cloudapi URL.'
},
{
names: ['insecure', 'i'],
type: 'bool',
help: 'Do not validate the SSL certificate. Environment: ' +
'SC_TLS_INSECURE=1 (or the deprecated SC_TESTING=1).',
'default': false
},
{
names: ['accept-version'],
type: 'string',
helpArg: 'VER',
help: 'A cloudapi API version, or semver range, to attempt to use. ' +
'This is passed in the "Accept-Version" header. ' +
'See `spearhead cloudapi /--ping` to list supported versions. ' +
'The default is "' + lib_tritonapi.CLOUDAPI_ACCEPT_VERSION + '". ' +
'*This is intended for development use only. It could cause ' +
'`spearhead` processing of responses to break.*',
hidden: true
}
];
// ---- other support stuff
function parseCommaSepStringNoEmpties(option, optstr, arg) {
// JSSTYLED
return arg.trim().split(/\s*,\s*/g)
.filter(function (part) { return part; });
}
cmdln.dashdash.addOptionType({
name: 'commaSepString',
takesArg: true,
helpArg: 'STRING',
parseArg: parseCommaSepStringNoEmpties
});
cmdln.dashdash.addOptionType({
name: 'arrayOfCommaSepString',
takesArg: true,
helpArg: 'STRING',
parseArg: parseCommaSepStringNoEmpties,
array: true,
arrayFlatten: true
});
@ -41,82 +179,505 @@ var log = bunyan.createLogger({
function CLI() {
Cmdln.call(this, {
name: pkg.name,
desc: pkg.description,
options: [
{names: ['help', 'h'], type: 'bool', help: 'Print help and exit.'},
{name: 'version', type: 'bool', help: 'Print version and exit.'},
{names: ['verbose', 'v'], type: 'bool',
help: 'Verbose/debug output.'},
// XXX disable profile selection for now
//{names: ['profile', 'p'], type: 'string', env: 'TRITON_PROFILE',
// helpArg: 'NAME', help: 'Triton client profile to use.'}
],
name: 'spearhead',
desc: packageJson.description,
options: OPTIONS,
helpOpts: {
includeEnv: true,
minHelpCol: 30
},
helpSubcmds: [
'help',
{ group: 'Operator Commands' },
'account',
'info',
'keys',
'profile',
'env',
'completion',
{ group: 'Instances (aka VMs/Machines/Containers)' },
'create-instance',
'instances',
'instance',
'instance-audit',
'start-instance',
'stop-instance',
'reboot-instance',
'delete-instance',
'instances',
'create',
'delete',
'start',
'stop',
'reboot',
'ssh',
{ group: 'Images' },
'images',
'ip',
{ group: 'Images, Packages, Networks, Firewall Rules' },
'image',
{ group: 'Packages' },
'packages',
'package'
]
'package',
'network',
'fwrule',
'vlan',
{ group: 'Other Commands' },
'info',
'account',
'key',
'services',
'datacenters'
],
helpBody: [
/* BEGIN JSSTYLED */
'Exit Status:',
' 0 Successful completion.',
' 1 An error occurred.',
' 2 Usage error.',
' 3 "ResourceNotFound" error (when an instance, image, etc. with',
' the given name or id is not found) or "InstanceDeleted" error.'
/* END JSSTYLED */
].join('\n')
});
}
util.inherits(CLI, Cmdln);
CLI.prototype.init = function (opts, args, callback) {
var self = this;
this.opts = opts;
this.log = bunyan.createLogger({
name: this.name,
serializers: bunyan.stdSerializers,
stream: process.stderr,
level: 'warn'
});
if (opts.verbose) {
this.log.level('trace');
this.log.src = true;
this.showErrStack = true;
}
if (opts.version) {
p(this.name, pkg.version);
console.log('Spearhead CLI', packageJson.version);
console.log(packageJson.homepage);
callback(false);
return;
}
this.opts = opts;
if (opts.verbose) {
log.level('trace');
log.src = true;
if (opts.url && opts.J) {
callback(new errors.UsageError(
'cannot use both "--url" and "-J" options'));
} else if (opts.J) {
opts.url = format('https://%s.api.spearhead.cloud', opts.J);
}
this.__defineGetter__('triton', function () {
if (self._triton === undefined) {
self._triton = new Triton({log: log, profile: opts.profile});
this.configDir = constants.CLI_CONFIG_DIR;
this.__defineGetter__('config', function getConfig() {
if (self._config === undefined) {
self._config = mod_config.loadConfig({
configDir: self.configDir
});
self.log.trace({config: self._config}, 'loaded config');
}
return self._triton;
return self._config;
});
// Cmdln class handles `opts.help`.
Cmdln.prototype.init.apply(this, arguments);
this.__defineGetter__('profileName', function getProfileName() {
return (opts.profile || self.config.profile || 'env');
});
this.__defineGetter__('profile', function getProfile() {
if (self._profile === undefined) {
try {
self._profile = mod_config.loadProfile({
configDir: self.configDir,
name: self.profileName,
profileOverrides: self._cliOptsAsProfile()
});
} catch (pErr) {
/*
* Let's be nice for the getting started use case where we
* defaulted to 'env' profile (e.g. the user has never created
* one) and the minimal envvars aren't set. I.e. The user just
* installed and ran `triton ls` or some other command.
*/
if (pErr.code === 'Config' && self.profileName === 'env' &&
!opts.profile && !self.config.profile)
{
/* BEGIN JSSTYLED */
pErr.message += '\n'
+ ' No profile information could be loaded.\n'
+ ' Use "spearhead profile create" to create a profile or provide\n'
+ ' the required "CloudAPI options" described in "spearhead --help".';
/* END JSSTYLED */
}
throw pErr;
}
self.log.trace({profile: self._profile}, 'loaded profile');
}
return self._profile;
});
this.__defineGetter__('tritonapi', function getTritonapi() {
if (self._tritonapi === undefined) {
self._tritonapi = lib_tritonapi.createClient({
log: self.log,
profile: self.profile,
config: self.config
});
self.log.trace('created tritonapi');
}
return self._tritonapi;
});
if (process.env.SC_COMPLETE) {
/*
* If `SC_COMPLETE=<type>` is set (typically only in the
* Triton CLI bash completion driver, see
* "etc/triton-bash-completion-types.sh"), then Bash completions are
* fetched and printed, instead of the usual subcommand handling.
*
* Completion results are typically cached (under "~/.triton/cache")
* to avoid hitting the server for data everytime.
*
* Example usage:
* SC_COMPLETE=images triton -p my-profile create
*/
self._emitCompletions(process.env.SC_COMPLETE, function (err) {
callback(err || false);
});
} else {
// Cmdln class handles `opts.help`.
Cmdln.prototype.init.call(self, opts, args, callback);
}
};
CLI.prototype.fini = function fini(subcmd, err, cb) {
this.log.trace({err: err, subcmd: subcmd}, 'cli fini');
if (this._tritonapi) {
this._tritonapi.close();
delete this._tritonapi;
}
cb();
};
//CLI.prototype.do_profile = require('./do_profile');
// Operator
/*
* Fetch and display Bash completions (one completion per line) for the given
* Triton data type (e.g. 'images', 'instances', 'packages', ...).
* This caches results (per profile) with a 5 minute TTL.
*
* Dev Note: If the cache path logic changes, then the *Bash* implementation
* of the same logic in "etc/triton-bash-completion-types.sh" must be updated
* to match.
*/
CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
assert.string(type, 'type');
assert.func(cb, 'cb');
var cacheFile = path.join(this.tritonapi.cacheDir, type + '.completions');
var ttl = 5 * 60 * 1000; // timeout of cache file info (ms)
var tritonapi = this.tritonapi;
vasync.pipeline({arg: {}, funcs: [
function tryCacheFile(arg, next) {
fs.stat(cacheFile, function (err, stats) {
if (!err &&
stats.mtime.getTime() + ttl >= (new Date()).getTime()) {
process.stdout.write(fs.readFileSync(cacheFile));
next(true); // early abort
} else if (err && err.code !== 'ENOENT') {
next(err);
} else {
next();
}
});
},
function initAuth(args, next) {
tritonapi.init(function (initErr) {
if (initErr) {
next(initErr);
}
if (tritonapi.keyPair.isLocked()) {
next(new errors.TritonError(
'cannot unlock keys during completion'));
}
next();
});
},
function gather(arg, next) {
var completions;
switch (type) {
case 'packages':
tritonapi.cloudapi.listPackages({}, function (err, pkgs) {
if (err) {
next(err);
return;
}
completions = [];
pkgs.forEach(function (pkg) {
if (pkg.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(pkg.name);
}
completions.push(pkg.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'images':
tritonapi.cloudapi.listImages({}, function (err, imgs) {
if (err) {
next(err);
return;
}
completions = [];
imgs.forEach(function (img) {
// Cannot bash complete results with spaces, so
// skip them here.
if (img.name.indexOf(' ') === -1) {
completions.push(img.name);
if (img.version.indexOf(' ') === -1) {
completions.push(img.name + '@' + img.version);
}
}
completions.push(img.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'instances':
tritonapi.cloudapi.listMachines({}, function (err, insts) {
if (err) {
next(err);
return;
}
completions = [];
insts.forEach(function (inst) {
if (inst.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(inst.name);
}
completions.push(inst.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'volumes':
tritonapi.cloudapi.listVolumes({}, function (err, vols) {
if (err) {
next(err);
return;
}
completions = [];
vols.forEach(function (vol) {
completions.push(vol.name);
completions.push(vol.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'affinityrules':
/*
* We exclude ids, in favour of just inst names here. The only
* justification for differing from other completion types
* on that is that with the additional prefixes, there would
* be too many.
*/
tritonapi.cloudapi.listMachines({}, function (err, insts) {
if (err) {
next(err);
return;
}
completions = [];
insts.forEach(function (inst) {
if (inst.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push('inst==' + inst.name);
completions.push('inst!=' + inst.name);
completions.push('inst==~' + inst.name);
completions.push('inst!=~' + inst.name);
}
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'networks':
tritonapi.cloudapi.listNetworks({}, function (err, nets) {
if (err) {
next(err);
return;
}
completions = [];
nets.forEach(function (net) {
if (net.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(net.name);
}
completions.push(net.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'fwrules':
tritonapi.cloudapi.listFirewallRules({}, function (err,
fwrules) {
if (err) {
next(err);
return;
}
completions = [];
fwrules.forEach(function (fwrule) {
completions.push(fwrule.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'keys':
tritonapi.cloudapi.listKeys({}, function (err, keys) {
if (err) {
next(err);
return;
}
completions = [];
keys.forEach(function (key) {
if (key.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(key.name);
}
completions.push(key.fingerprint);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
default:
process.stderr.write('warning: unknown spearhead completion type: '
+ type + '\n');
next();
break;
}
},
function saveCache(arg, next) {
if (!arg.completions) {
next();
return;
}
fs.writeFile(cacheFile, arg.completions, next);
},
function emit(arg, next) {
if (arg.completions) {
console.log(arg.completions);
}
next();
}
]}, function (err) {
if (err === true) { // early abort signal
err = null;
}
cb(err);
});
};
/*
* Apply overrides from CLI options to the given profile object *in place*.
*/
CLI.prototype._applyProfileOverrides =
function _applyProfileOverrides(profile) {
var optProfile = this._cliOptsAsProfile();
for (var attr in optProfile) {
profile[attr] = optProfile[attr];
}
};
/*
* Create a profile dict from any cli override options specified.
* Unless all profile flags are specified on the cli, this profile
* will be incomplete and will need to be combined with another
* configuration source.
*/
CLI.prototype._cliOptsAsProfile = function _cliOptsAsProfile() {
var self = this;
var profile = {};
[
{oname: 'account', pname: 'account'},
{oname: 'user', pname: 'user'},
{oname: 'role', pname: 'roles'},
{oname: 'url', pname: 'url'},
{oname: 'keyId', pname: 'keyId'},
{oname: 'insecure', pname: 'insecure'},
{oname: 'accept_version', pname: 'acceptVersion'},
{oname: 'act_as', pname: 'actAsAccount'}
].forEach(function (field) {
// We need to check `opts._order` to know if boolean opts
// were specified.
var specified = self.opts._order.filter(
function (opt) { return opt.key === field.oname; }).length > 0;
if (specified) {
profile[field.pname] = self.opts[field.oname];
}
});
return profile;
};
/*
* 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 lib_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');
CLI.prototype.do_profile = require('./do_profile');
CLI.prototype.do_env = require('./do_env');
// Other
CLI.prototype.do_account = require('./do_account');
CLI.prototype.do_services = require('./do_services');
CLI.prototype.do_datacenters = require('./do_datacenters');
CLI.prototype.do_info = require('./do_info');
// Account keys
CLI.prototype.do_key = require('./do_key');
CLI.prototype.do_keys = require('./do_keys');
// Firewall rules
CLI.prototype.do_fwrule = require('./do_fwrule');
CLI.prototype.do_fwrules = require('./do_fwrules');
// Images
CLI.prototype.do_images = require('./do_images');
CLI.prototype.do_image = require('./do_image');
@ -124,33 +685,126 @@ CLI.prototype.do_image = require('./do_image');
// Instances (aka VMs/containers/machines)
CLI.prototype.do_instance = require('./do_instance');
CLI.prototype.do_instances = require('./do_instances');
CLI.prototype.do_create_instance = require('./do_create_instance');
CLI.prototype.do_instance_audit = require('./do_instance_audit');
CLI.prototype.do_stop_instance = require('./do_startstop_instance')('stop');
CLI.prototype.do_start_instance = require('./do_startstop_instance')('start');
CLI.prototype.do_reboot_instance = require('./do_startstop_instance')('reboot');
CLI.prototype.do_delete_instance = require('./do_delete_instance');
CLI.prototype.do_create = require('./do_create');
CLI.prototype.do_delete = require('./do_delete');
CLI.prototype.do_start = require('./do_start');
CLI.prototype.do_stop = require('./do_stop');
CLI.prototype.do_reboot = require('./do_reboot');
CLI.prototype.do_ssh = require('./do_ssh');
CLI.prototype.do_ip = require('./do_ip');
// Packages
CLI.prototype.do_packages = require('./do_packages');
CLI.prototype.do_package = require('./do_package');
// Networks
CLI.prototype.do_networks = require('./do_networks');
CLI.prototype.do_network = require('./do_network');
// VLANs
CLI.prototype.do_vlan = require('./do_vlan');
// Hidden commands
CLI.prototype.do_cloudapi = require('./do_cloudapi');
CLI.prototype.do_badger = require('./do_badger');
CLI.prototype.do_rbac = require('./do_rbac');
// Volumes
CLI.prototype.do_volumes = require('./do_volumes');
CLI.prototype.do_volume = require('./do_volume');
//---- mainline
if (require.main === module) {
function main(argv) {
if (!argv) {
argv = process.argv;
}
var cli = new CLI();
cmdln.main(cli, {showNoCommandErr: false});
cli.main(argv, function (err) {
var exitStatus = (err ? err.exitStatus || 1 : 0);
var showErr = (cli.showErr !== undefined ? cli.showErr : true);
var errHelp;
var errMessage;
if (err && showErr) {
var code = (err.body ? err.body.code : err.code) || err.restCode;
if (code === 'NoCommand') {
/* jsl:pass */
} else if (err.name === 'InternalServerError') {
/*
* Internal server error, we want to provide a useful error
* message without exposing internals.
*/
console.error('%s: internal error. Please try again later, ' +
'and contact support in case the error persists.',
cmdln.nameFromErr(err));
} else {
/*
* If the err has `body.errors`, as some Triton/SDC APIs do per
* // JSSTYLED
* https://github.com/joyent/eng/blob/master/docs/index.md#error-handling
* then append a one-line summary for each error object.
*/
var bodyErrors = '';
if (err.body && err.body.errors) {
err.body.errors.forEach(function (e) {
bodyErrors += format('\n %s: %s', e.field, e.code);
if (e.message) {
bodyErrors += ': ' + e.message;
}
});
}
/*
* Try to find the most descriptive message to output.
*
* 1. If there's a message property on the error object, we
* assume this is suitable to output to the user.
*
* 2. Otherwise, if there's an "orignalBody" property, we output
* its content per joyent/node-triton#30.
*
* 3. We fall back to using the error's name as the error
* message.
*/
if (typeof (err.message) === 'string' && err.message !== '') {
errMessage = err.message;
} else if (err.originalBody !== undefined) {
errMessage = err.originalBody.toString();
} else {
errMessage = err.name;
}
console.error('%s: error%s: %s%s',
cmdln.nameFromErr(err),
(code ? format(' (%s)', code) : ''),
(cli.showErrStack ? err.stack : errMessage),
bodyErrors);
}
errHelp = cmdln.errHelpFromErr(err);
if (errHelp) {
console.error(errHelp);
}
}
/*
* We'd like to NOT use `process.exit` because that doesn't always
* allow std handles to flush (e.g. all logging to complete). However
* I don't know of another way to exit non-zero.
*/
if (exitStatus !== 0) {
process.exit(exitStatus);
}
});
}
//---- exports
module.exports = CLI;
module.exports = {
CLI: CLI,
main: main
};

File diff suppressed because it is too large Load Diff

1404
lib/common.js Executable file → Normal file

File diff suppressed because it is too large Load Diff

436
lib/config.js Executable file → Normal file
View File

@ -1,45 +1,125 @@
#!/usr/bin/env node
/**
* Copyright (c) 2014 Joyent Inc. All rights reserved.
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*/
/*
* This module provides functions to read and write (a) a TritonApi config
* and (b) TritonApi profiles.
*
* The config is a JSON object loaded from "etc/defaults.json" (shipped with
* node-triton) plus possibly overrides from "$configDir/config.json" --
* which is "~/.triton/config.json" for the `triton` CLI. The config has
* a strict set of allowed keys.
*
* A profile is a small object that includes the necessary info for talking
* to a CloudAPI. E.g.:
* {
* "name": "east1",
* "account": "billy.bob",
* "keyId": "de:e7:73:9a:aa:91:bb:3e:72:8d:cc:62:ca:58:a2:ec",
* "url": "https://us-east-1.api.joyent.com"
* }
*
* Profiles are stored as separate JSON files in
* "$configDir/profiles.d/$name.json". Typically `triton profiles ...` is
* used to manage them. In addition there is the special "env" profile that
* is constructed from the "SDC_*" environment variables.
*/
var p = console.log;
var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var mkdirp = require('mkdirp');
var path = require('path');
var sprintf = require('extsprintf').sprintf;
var vasync = require('vasync');
var common = require('./common');
var errors = require('./errors');
var CONFIG_PATH = path.resolve(process.env.HOME, '.triton', 'config.json');
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
var OVERRIDE_NAMES = []; // config object keys to do a one-level deep override
// TODO: use this const to create the "Configuration" docs table.
var CONFIG_VAR_NAMES = [
'profile',
// Intentionally exclude 'oldProfile' so that it isn't manually set.
// 'oldProfile',
'cacheDir'
];
// TODO: use this to create a profile doc table?
var PROFILE_FIELDS = {
name: true,
url: true,
account: true,
keyId: true,
insecure: true,
user: true,
roles: true,
actAsAccount: true
};
// --- internal support stuff
function configPathFromDir(configDir) {
return path.resolve(configDir, 'config.json');
}
// --- Config
/**
* Load the Triton client config. This is a merge of the built-in "defaults" (at
* etc/defaults.json) and the "user" config (at ~/.triton/config.json if it
* exists).
* Load the TritonApi config. This is a merge of the built-in "defaults" (at
* etc/defaults.json) and the "user" config (at "$configDir/config.json",
* typically "~/.triton/config.json", if it exists).
*
* This includes some internal data on keys with a leading underscore.
* This includes some internal data on keys with a leading underscore:
* _defaults the defaults.json object
* _configDir the user config dir (if one is provided)
* _user the "user" config.json object (if exists)
*
* @param opts.configDir {String} Optional. A base dir for TritonApi config.
* @returns {Object} The loaded config.
*/
function loadConfigSync() {
function loadConfig(opts) {
assert.object(opts, 'opts');
assert.optionalString(opts.configDir, 'opts.configDir');
var configDir;
var configPath;
if (opts.configDir) {
configDir = common.tildeSync(opts.configDir);
configPath = configPathFromDir(configDir);
}
var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
var _defaults = JSON.parse(c);
var config = JSON.parse(c);
if (fs.existsSync(CONFIG_PATH)) {
c = fs.readFileSync(CONFIG_PATH, 'utf8');
var _user = JSON.parse(c);
var userConfig = JSON.parse(c);
if (typeof(userConfig) !== 'object' || Array.isArray(userConfig)) {
if (configPath && fs.existsSync(configPath)) {
c = fs.readFileSync(configPath, 'utf8');
try {
var _user = JSON.parse(c);
var userConfig = JSON.parse(c);
} catch (userConfigParseErr) {
throw new errors.ConfigError(
sprintf('"%s" is not an object', CONFIG_PATH));
format('"%s" is invalid JSON', configPath));
}
if (typeof (userConfig) !== 'object' || Array.isArray(userConfig)) {
throw new errors.ConfigError(
format('"%s" is not an object', configPath));
}
// These special keys are merged into the key of the same name in the
// base "defaults.json".
Object.keys(userConfig).forEach(function (key) {
if (~OVERRIDE_KEYS.indexOf(key) && config[key] !== undefined) {
if (~OVERRIDE_NAMES.indexOf(key) && config[key] !== undefined) {
Object.keys(userConfig[key]).forEach(function (subKey) {
if (userConfig[key][subKey] === null) {
delete config[key][subKey];
@ -55,40 +135,318 @@ function loadConfigSync() {
config._user = _user;
}
config._defaults = _defaults;
// Add 'env' profile.
if (!config.profiles) {
config.profiles = [];
if (configDir) {
config._configDir = configDir;
}
//XXX Add TRITON_* envvars.
config.profiles.push({
name: 'env',
account: process.env.SDC_USER || process.env.SDC_ACCOUNT,
url: process.env.SDC_URL,
keyId: process.env.SDC_KEY_ID,
insecure: common.boolFromString(process.env.SDC_TESTING)
});
return config;
}
function setConfigVars(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.configDir, 'opts.configDir');
assert.object(opts.vars, 'opts.vars');
Object.keys(opts.vars).forEach(function (name) {
assert.ok(name.indexOf('.') === -1,
'dotted config name not yet supported');
assert.ok(CONFIG_VAR_NAMES.indexOf(name) !== -1,
'unknown config var name: ' + name);
});
var configPath = configPathFromDir(opts.configDir);
var config;
vasync.pipeline({funcs: [
function loadExisting(_, next) {
fs.exists(configPath, function (exists) {
if (!exists) {
config = {};
return next();
}
fs.readFile(configPath, function (err, data) {
if (err) {
return next(err);
}
try {
config = JSON.parse(data);
} catch (e) {
return next(e);
}
next();
});
});
},
function mkConfigDir(_, next) {
fs.exists(opts.configDir, function (exists) {
if (!exists) {
mkdirp(opts.configDir, next);
} else {
next();
}
});
},
/*
* To support `triton profile set -` to set profile to the *last*
* one used, we special case the setting of the "profile" config var
* to *also* then set "oldProfile" to the old value. (We are copying
* the "OLDPWD" naming used by the shell for `cd -`.)
*/
function specialCaseOldProfile(_, next) {
if (opts.vars.hasOwnProperty('profile') && config.profile) {
opts.vars['oldProfile'] = config.profile;
}
next();
},
function updateAndSave(_, next) {
Object.keys(opts.vars).forEach(function (name) {
config[name] = opts.vars[name];
});
fs.writeFile(configPath, JSON.stringify(config, null, 4), next);
}
]}, cb);
}
// --- Profiles
function validateProfile(profile, profilePath) {
assert.object(profile, 'profile');
assert.optionalString(profilePath, 'profilePath');
try {
assert.string(profile.name, 'profile.name');
assert.string(profile.url,
profile.name === 'env' ? 'SC_URL' : 'profile.url');
assert.string(profile.account,
profile.name === 'env' ? 'SC_ACCOUNT'
: 'profile.account');
assert.string(profile.keyId,
profile.name === 'env' ? 'SC_KEY_ID'
: 'profile.keyId');
assert.optionalBool(profile.insecure,
profile.name === 'env' ? 'SC_INSECURE'
: 'profile.insecure');
assert.optionalString(profile.user,
profile.name === 'env' ? 'SC_USER'
: 'profile.user');
assert.optionalString(profile.actAsAccount, 'profile.actAsAccount');
assert.optionalArrayOfString(profile.roles, 'profile.roles');
} catch (err) {
var msg = format('invalid %sprofile%s: %s',
profile.name ? '"' + profile.name + '" ' : '',
profilePath ? ' from ' + profilePath: '',
err.message);
throw new errors.ConfigError(msg);
}
var bogusFields = [];
Object.keys(profile).forEach(function (field) {
if (!PROFILE_FIELDS[field]) {
bogusFields.push(field);
}
});
if (bogusFields.length) {
throw new errors.ConfigError(format(
'extraneous fields in "%s" profile: %s%s', profile.name,
(profilePath ? profilePath + ': ' : ''), bogusFields.join(', ')));
}
}
/**
* Apply the given key:value updates to the user config and save it out.
* Load the special 'env' profile, which handles details of getting
* values from envvars. Typically we'd piggyback on dashdash's env support
* <https://github.com/trentm/node-dashdash#environment-variable-integration>.
* However, per the "Environment variable integration" comment in cli.js, we
* do that manually.
*
* @param config {Object} The loaded config, as from `loadConfigSync`.
* @param updates {Object} key/value pairs to update.
* @returns {Object} The 'env' profile. If no relevant envvars are set, then
* this returns null.
* @throws {errors.ConfigError} If the profile defined by the environment is
* invalid.
*/
function updateUserConfigSync(config, updates) {
XXX
///XXX START HERE: to implement for 'sdc dcs add foo bar'
function _loadEnvProfile(profileOverrides) {
var envProfile = {
name: 'env'
};
envProfile.account = process.env.SC_ACCOUNT;
var user = process.env.SC_USER;
if (user) {
envProfile.user = user;
}
envProfile.url = process.env.SC_URL;
envProfile.keyId = process.env.SC_KEY_ID;
if (process.env.SC_TLS_INSECURE) {
envProfile.insecure = common.boolFromString(
process.env.SC_TLS_INSECURE, undefined, 'SC_TLS_INSECURE');
} else if (process.env.SC_TESTING) {
// For compatibility with the legacy behavior of the smartdc
// tools, *any* set value but the empty string is considered true.
envProfile.insecure = true;
}
for (var attr in profileOverrides) {
envProfile[attr] = profileOverrides[attr];
}
/*
* If missing any of the required vars, then there is no env profile.
*/
if (!envProfile.account || !envProfile.url || !envProfile.keyId) {
return null;
}
validateProfile(envProfile, 'environment variables');
return envProfile;
}
function _profileFromPath(profilePath, name, profileOverrides) {
if (! fs.existsSync(profilePath)) {
throw new errors.ConfigError('no such profile: ' + name);
}
var profile;
try {
profile = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
} catch (e) {
throw new errors.ConfigError(e, format(
'error in "%s" profile: %s: %s', name,
profilePath, e.message));
}
if (profile.name) {
throw new errors.ConfigError(format(
'error in "%s" profile: %s: file must not include "name" field',
name, profilePath));
}
profile.name = name;
for (var attr in profileOverrides) {
profile[attr] = profileOverrides[attr];
}
validateProfile(profile, profilePath);
return profile;
}
function loadProfile(opts) {
assert.string(opts.name, 'opts.name');
assert.optionalString(opts.configDir, 'opts.configDir');
assert.optionalObject(opts.profileOverrides, 'opts.profileOverrides');
if (opts.name === 'env') {
var envProfile = _loadEnvProfile(opts.profileOverrides);
if (!envProfile) {
throw new errors.ConfigError('could not load "env" profile '
+ '(missing SC_* environment variables)');
}
return envProfile;
} else if (!opts.configDir) {
throw new errors.ConfigError(
'cannot load profiles (other than "env") without `opts.configDir`');
} else {
var profilePath = path.resolve(
common.tildeSync(opts.configDir), 'profiles.d',
opts.name + '.json');
return _profileFromPath(profilePath, opts.name, opts.profileOverrides);
}
}
function loadAllProfiles(opts) {
assert.string(opts.configDir, 'opts.configDir');
assert.object(opts.log, 'opts.log');
assert.optionalObject(opts.profileOverrides, 'opts.profileOverrides');
var profiles = [];
var envProfile = _loadEnvProfile(opts.profileOverrides);
if (envProfile) {
profiles.push(envProfile);
}
var d = path.join(common.tildeSync(opts.configDir), 'profiles.d');
if (fs.existsSync(d)) {
var files = fs.readdirSync(d);
files.forEach(function (file) {
file = path.join(d, file);
var ext = path.extname(file);
if (ext !== '.json')
return;
var name = path.basename(file).slice(
0, - path.extname(file).length);
if (name.toLowerCase() === 'env') {
// Skip the special 'env'.
opts.log.warn({profilePath: file},
'invalid "env" profile; skipping');
return;
}
try {
profiles.push(_profileFromPath(file, name));
} catch (e) {
opts.log.warn({err: e, profilePath: file},
'error loading profile; skipping');
}
});
}
return profiles;
}
function deleteProfile(opts) {
assert.string(opts.configDir, 'opts.configDir');
assert.string(opts.name, 'opts.name');
if (opts.name === 'env') {
throw new Error('cannot delete "env" profile');
}
var profilePath = path.resolve(opts.configDir, 'profiles.d',
opts.name + '.json');
fs.unlinkSync(profilePath);
}
function saveProfileSync(opts) {
assert.string(opts.configDir, 'opts.configDir');
assert.object(opts.profile, 'opts.profile');
var name = opts.profile.name;
if (name === 'env') {
throw new Error('cannot save "env" profile');
}
validateProfile(opts.profile);
var toSave = common.objCopy(opts.profile);
delete toSave.name;
var profilePath = path.resolve(opts.configDir, 'profiles.d',
name + '.json');
if (!fs.existsSync(path.dirname(profilePath))) {
mkdirp.sync(path.dirname(profilePath));
}
fs.writeFileSync(profilePath, JSON.stringify(toSave, null, 4), 'utf8');
console.log('Saved profile "%s".', name);
}
//---- exports
module.exports = {
CONFIG_PATH: CONFIG_PATH,
loadConfigSync: loadConfigSync
loadConfig: loadConfig,
setConfigVars: setConfigVars,
validateProfile: validateProfile,
loadProfile: loadProfile,
loadAllProfiles: loadAllProfiles,
deleteProfile: deleteProfile,
saveProfileSync: saveProfileSync
};
// vim: set softtabstop=4 shiftwidth=4:

121
lib/constants.js Normal file
View File

@ -0,0 +1,121 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*/
/*
* node-triton constants.
*
* CLI usage:
* $ node lib/constants.js
* ... emits all the constants as a JSON object ...
* $ node lib/constants.js KEY
* ... emits the value of KEY (in json-y form, i.e. quotes removed from a
* string) ...
*/
var mod_path = require('path');
// ---- determining constants
/*
* The `triton` CLI's config dir.
*
* For *testing* only, we allow override of this dir.
*/
var CLI_CONFIG_DIR;
if (process.env.SCTEST_CLI_CONFIG_DIR) {
CLI_CONFIG_DIR = process.env.SCTEST_CLI_CONFIG_DIR;
} else 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.
*/
CLI_CONFIG_DIR = mod_path.resolve(process.env.APPDATA, 'Spearhead', 'sc');
} else {
CLI_CONFIG_DIR = mod_path.resolve(process.env.HOME, '.spearhead');
}
// <Network Object Key> -> <expected typeof>
var NETWORK_OBJECT_FIELDS = {
ipv4_uuid: 'string',
ipv4_ips: 'string'
};
// ---- exports
module.exports = {
CLI_CONFIG_DIR: CLI_CONFIG_DIR,
NETWORK_OBJECT_FIELDS: NETWORK_OBJECT_FIELDS
};
// ---- mainline
function main(argv) {
var assert = require('assert-plus');
var dashdash = require('cmdln').dashdash;
assert.arrayOfString(argv, 'argv');
var options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
}
];
var parser = dashdash.createParser({options: options});
try {
var opts = parser.parse(argv);
} catch (e) {
console.error('lib/constants.js: error: %s', e.message);
process.exit(1);
}
if (opts.help) {
console.log([
'usage: node .../lib/constants.js [OPTIONS] [KEY]',
'options:',
parser.help().trimRight()
].join('\n'));
process.exit(0);
}
var key;
if (opts._args.length === 1) {
key = opts._args[0];
} else if (opts._args.length === 0) {
key = null;
} else {
console.error('lib/constants.js: error: too many args: %s',
opts._args.join(' '));
process.exit(1);
}
if (key) {
var val = module.exports[key];
if (typeof (val) === 'string') {
console.log(val);
} else {
console.log(JSON.stringify(val, null, 4));
}
} else {
console.log(JSON.stringify(module.exports, null, 4));
}
}
if (require.main === module) {
main(process.argv);
}

54
lib/distractions.js Normal file
View File

@ -0,0 +1,54 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* A CLI distraction during a long process (e.g. waiting for
* create).
*
* Usage:
* var distractions = require('./distractions');
* var distraction = distractions.createDistraction([num]);
* setTimeout(function () {
* distraction.destroy();
* }, 5000);
*/
var assert = require('assert-plus');
var bigspinner = require('bigspinner');
function createDistraction(num) {
assert.optionalNumber(num, 'num');
var height, width;
if (num <= 2) {
height = Math.min(5, process.stdout.rows - 2);
width = Math.min(5*2, process.stdout.columns - 2);
} else if (num === 3) {
height = Math.min(10, process.stdout.rows - 2);
width = Math.min(10*2, process.stdout.columns - 2);
} else {
var BORDER = 10;
height = Math.max(2, process.stdout.rows - 2 - BORDER);
width = Math.max(2, process.stdout.columns - 1 - BORDER);
}
return bigspinner.createSpinner({
delay: 50,
positions: 40,
stream: process.stderr,
height: height,
width: width,
hideCursor: true,
//fontChar: '\u2588' // '\x1b[7m \x1b[m'
fontChar: '#'
});
}
module.exports = {
createDistraction: createDistraction
};

View File

@ -1,66 +0,0 @@
/*
* Copyright 2015 Joyent Inc.
*
* `triton account ...`
*/
var common = require('./common');
function do_account(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 0) {
callback(new Error('invalid args: ' + args));
return;
}
this.triton.cloudapi.getAccount(function (err, account) {
if (err) {
callback(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(account));
} else {
// pretty print
var dates = ['updated', 'created'];
Object.keys(account).forEach(function (key) {
var val = account[key];
if (dates.indexOf(key) >= 0) {
console.log('%s: %s (%s)', key, val,
common.longAgo(new Date(val)));
} else {
console.log('%s: %s', key, val);
}
});
}
callback();
});
}
do_account.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_account.help = (
'Show account information\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} account\n'
+ '\n'
+ '{{options}}'
);
do_account.aliases = ['whoami'];
module.exports = do_account;

79
lib/do_account/do_get.js Normal file
View File

@ -0,0 +1,79 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton account get ...`
*/
var common = require('../common');
function do_get(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 0) {
callback(new Error('invalid args: ' + args));
return;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
tritonapi.cloudapi.getAccount(function (err, account) {
if (err) {
callback(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(account));
} else {
// pretty print
var dates = ['updated', 'created'];
Object.keys(account).forEach(function (key) {
var val = account[key];
if (dates.indexOf(key) >= 0) {
console.log('%s: %s (%s)', key, val,
common.longAgo(new Date(val)));
} else {
console.log('%s: %s', key, val);
}
});
}
callback();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_get.synopses = ['{{name}} {{cmd}}'];
do_get.help = [
'Show account information',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
module.exports = do_get;

158
lib/do_account/do_update.js Normal file
View File

@ -0,0 +1,158 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton account update ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var UPDATE_ACCOUNT_FIELDS
= require('../cloudapi2').CloudApi.prototype.UPDATE_ACCOUNT_FIELDS;
function do_update(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
var log = this.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function gatherDataArgs(ctx, next) {
if (opts.file) {
next();
return;
}
try {
ctx.data = common.objFromKeyValueArgs(args, {
disableDotted: true,
typeHintFromKey: UPDATE_ACCOUNT_FIELDS
});
} catch (err) {
next(err);
return;
}
next();
},
function gatherDataFile(ctx, next) {
if (!opts.file || opts.file === '-') {
next();
return;
}
var input = fs.readFileSync(opts.file, 'utf8');
try {
ctx.data = JSON.parse(input);
} catch (err) {
next(new errors.TritonError(format(
'invalid JSON for account update in "%s": %s',
opts.file, err)));
return;
}
next();
},
function gatherDataStdin(ctx, next) {
if (opts.file !== '-') {
next();
return;
}
common.readStdin(function gotStdin(stdin) {
try {
ctx.data = JSON.parse(stdin);
} catch (err) {
log.trace({stdin: stdin},
'invalid account update JSON on stdin');
next(new errors.TritonError(format(
'invalid JSON for account update on stdin: %s', err)));
return;
}
next();
});
},
function validateIt(ctx, next) {
try {
common.validateObject(ctx.data, UPDATE_ACCOUNT_FIELDS);
} catch (e) {
next(e);
return;
}
next();
},
function updateAway(ctx, next) {
var keys = Object.keys(ctx.data);
tritonapi.cloudapi.updateAccount(ctx.data, function (err) {
if (err) {
next(err);
return;
}
console.log('Updated account "%s" (fields: %s)',
tritonapi.profile.account, keys.join(', '));
next();
});
}
]}, callback);
}
do_update.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['file', 'f'],
type: 'string',
helpArg: 'FILE',
help: 'A file holding a JSON file of updates, or "-" to read ' +
'JSON from stdin.'
}
];
do_update.synopses = [
'{{name}} {{cmd}} [FIELD=VALUE ...]',
'{{name}} {{cmd}} -f JSON-FILE'
];
do_update.help = [
/* BEGIN JSSTYLED */
'Update account information',
'',
'{{usage}}',
'',
'{{options}}',
'Updateable fields:',
' ' + Object.keys(UPDATE_ACCOUNT_FIELDS).sort().map(function (field) {
return field + ' (' + UPDATE_ACCOUNT_FIELDS[field] + ')';
}).join('\n '),
'',
'Note that because of cross-data center replication of account information, ',
'an update might not be immediately reflected in a get.'
/* END JSSTYLED */
].join('\n');
do_update.completionArgtypes = ['tritonupdateaccountfield'];
module.exports = do_update;

50
lib/do_account/index.js Normal file
View File

@ -0,0 +1,50 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton account ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function AccountCLI(top) {
this.top = top;
Cmdln.call(this, {
name: top.name + ' account',
/* BEGIN JSSTYLED */
desc: [
'Get and update your Spearhead account.'
].join('\n'),
/* END JSSTYLED */
helpOpts: {
minHelpCol: 24 /* line up with option help */
},
helpSubcmds: [
'help',
'get',
'update'
]
});
}
util.inherits(AccountCLI, Cmdln);
AccountCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
AccountCLI.prototype.do_get = require('./do_get');
AccountCLI.prototype.do_update = require('./do_update');
module.exports = AccountCLI;

View File

@ -1,5 +1,11 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton package ...`
*/
@ -12,7 +18,7 @@ var path = require('path');
function do_badger(subcmd, opts, args, callback) {
var callbackOnce = once(callback);
var badger = path.resolve(__dirname, '../etc/badger');
var input = fs.createReadStream(badger)
var input = fs.createReadStream(badger);
input.pipe(process.stdout);
input.on('error', function (err) {
callbackOnce(err);
@ -20,10 +26,10 @@ function do_badger(subcmd, opts, args, callback) {
input.on('end', function () {
callbackOnce();
});
};
}
do_badger.options = [];
do_badger.help = 'Rawr!';
do_badger.help = 'Badger don\'t care.';
do_badger.hidden = true;
module.exports = do_badger;

View File

@ -1,53 +1,97 @@
/*
* Copyright 2015 Joyent Inc.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton cloudapi ...`
*/
var http = require('http');
var vasync = require('vasync');
function do_cloudapi (subcmd, opts, args, callback) {
var common = require('./common');
var errors = require('./errors');
function do_cloudapi(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length < 1 || args.length > 2) {
} else if (args.length < 1) {
callback(new Error('invalid arguments'));
return;
}
var method = args[0];
var path = args[1];
if (path === undefined) {
path = method;
method = 'GET';
// Get `reqOpts` from given options.
var method = opts.method;
if (!method) {
if (opts.data) {
method = 'PUT';
} else {
method = 'GET';
}
}
var reqopts = {
var reqOpts = {
method: method.toLowerCase(),
path: path
headers: {},
path: args[0]
};
this.triton.cloudapi._request(reqopts, function (err, req, res, body) {
if (err) {
callback(err);
if (opts.header) {
for (var i = 0; i < opts.header.length; i++) {
var raw = opts.header[i];
var j = raw.indexOf(':');
if (j < 0) {
callback(new errors.TritonError(
'failed to parse header: ' + raw));
return;
}
var header = raw.substr(0, j);
var value = raw.substr(j + 1).trimLeft();
reqOpts.headers[header] = value;
}
}
if (opts.data) {
try {
reqOpts.data = JSON.parse(opts.data);
} catch (parseErr) {
callback(new errors.TritonError(parseErr,
'given DATA is not valid JSON: ' + parseErr.message));
return;
}
if (opts.headers || reqopts.method === 'head') {
console.error('%s/%s %d %s',
req.connection.encrypted ? 'HTTPS' : 'HTTP',
res.httpVersion,
res.statusCode,
http.STATUS_CODES[res.statusCode]);
Object.keys(res.headers).forEach(function (key) {
console.error('%s: %s', key, res.headers[key]);
});
console.error();
}
}
if (reqopts.method !== 'head')
console.log(JSON.stringify(body, null, 4));
callback();
});
vasync.pipeline({arg: {cli: this}, funcs: [
common.cliSetupTritonApi,
function callCloudapi(ctx, next) {
var cloudapi = ctx.cli.tritonapi.cloudapi;
cloudapi._request(reqOpts, function (err, req, res, body) {
if (err) {
next(err);
return;
}
if (opts.headers || reqOpts.method === 'head') {
console.error('%s/%s %d %s',
req.connection.encrypted ? 'HTTPS' : 'HTTP',
res.httpVersion,
res.statusCode,
http.STATUS_CODES[res.statusCode]);
Object.keys(res.headers).forEach(function (key) {
console.error('%s: %s', key, res.headers[key]);
});
console.error();
}
if (reqOpts.method !== 'head')
console.log(JSON.stringify(body, null, 4));
next();
});
}
]}, callback);
}
do_cloudapi.options = [
@ -56,20 +100,45 @@ do_cloudapi.options = [
type: 'bool',
help: 'Show this help.'
},
{
names: ['method', 'X'],
type: 'string',
helpArg: 'METHOD',
help: 'Request method to use. Default is "GET".'
},
{
names: ['header', 'H'],
type: 'arrayOfString',
helpArg: 'HEADER',
help: 'Headers to send with request.'
},
{
names: ['headers', 'i'],
type: 'bool',
help: 'Print response headers to stderr.'
},
{
names: ['data', 'd'],
type: 'string',
helpArg: 'DATA',
help: 'Add POST data. This must be valid JSON.'
}
];
do_cloudapi.help = (
'Raw cloudapi request.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} <method> <endpoint>\n'
+ '\n'
+ '{{options}}'
);
do_cloudapi.synopses = [
'{{name}} {{cmd}} [-X METHOD] [-H HEADER=VAL] [-d DATA] ENDPOINT'
];
do_cloudapi.help = [
'Raw cloudapi request.',
'',
'{{usage}}',
'',
'{{options}}',
'Examples:',
' {{name}} {{cmd}} /--ping',
' {{name}} {{cmd}} /my/machines'
].join('\n');
do_cloudapi.hidden = true;

84
lib/do_completion.js Normal file
View File

@ -0,0 +1,84 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton completion ...`
*/
var fs = require('fs');
var path = require('path');
var CloudApi = require('./cloudapi2').CloudApi;
var UPDATE_ACCOUNT_FIELDS = CloudApi.prototype.UPDATE_ACCOUNT_FIELDS;
var UPDATE_FWRULE_FIELDS = CloudApi.prototype.UPDATE_FWRULE_FIELDS;
// Replace {{variable}} in `s` with the template data in `d`.
function renderTemplate(s, d) {
return s.replace(/{{([a-zA-Z_]+)}}/g, function (match, key) {
return d.hasOwnProperty(key) ? d[key] : match;
});
}
function do_completion(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (opts.raw) {
console.log(this.bashCompletionSpec());
} else {
var specExtraIn = fs.readFileSync(
path.join(__dirname, '../etc/triton-bash-completion-types.sh'),
'utf8');
var specExtra = renderTemplate(specExtraIn, {
UPDATE_ACCOUNT_FIELDS: Object.keys(UPDATE_ACCOUNT_FIELDS).sort()
.map(function (field) { return field + '='; }).join(' '),
UPDATE_FWRULE_FIELDS: Object.keys(UPDATE_FWRULE_FIELDS).sort()
.map(function (field) { return field + '='; }).join(' ')
});
console.log(this.bashCompletion({specExtra: specExtra}));
}
cb();
}
do_completion.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['raw'],
type: 'bool',
hidden: true,
help: 'Only output the Bash completion "spec". ' +
'This is only useful for debugging.'
}
];
do_completion.help = [
'Emit bash completion. See help for installation.',
'',
'Installation (Mac):',
' {{name}} completion > /usr/local/etc/bash_completion.d/{{name}} \\',
' && source /usr/local/etc/bash_completion.d/{{name}}',
'',
'Installation (Linux):',
' sudo {{name}} completion > /etc/bash_completion.d/{{name}} \\',
' && source /etc/bash_completion.d/{{name}}',
'',
'Alternative installation:',
' {{name}} completion > ~/.{{name}}.completion # or to whatever path',
' echo "source ~/.{{name}}.completion" >> ~/.bashrc',
'',
'{{options}}'
].join('\n');
module.exports = do_completion;

View File

@ -1,106 +1,29 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* `triton images ...`
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var format = require('util').format;
var tabula = require('tabula');
var errors = require('./errors');
/*
* Copyright 2017 Joyent, Inc.
*
* `triton create ...` bwcompat shortcut for `triton instance create ...`.
*/
var targ = require('./do_instance/do_create');
function do_create(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
return callback(new Error('too many args: ' + args));
}
var triton = this.triton;
this.handlerFromSubcmd('instance').dispatch({
subcmd: 'create',
opts: opts,
args: args
}, callback);
}
// XXX The smarts here should move to Triton class.
assert.string(opts.image, '--image <img>');
assert.string(opts['package'], '--package <pkg>');
assert.number(opts.count)
// XXX
/*
* Should all this move into sdc.createMachine? yes
*
* - lookup image, package, networks from args
* - assign names
* - start provisions (slight stagger, max N at a time)
* - return immediately, or '-w|--wait'
*/
async.series([
function lookups(next) {
async.parallel([
//XXX
//sdc.lookup(image)
])
},
function provisions(next) {
},
function wait(next) {
next();
}
], function (err) {
callback(err);
});
};
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['dc', 'd'],
type: 'string',
helpArg: '<dc>',
help: 'The datacenter in which to provision. Required if the current'
+ ' profile includes more than one datacenter. Use `sdc profile`'
+ ' to list profiles and `sdc dcs` to list available datacenters.'
},
{
names: ['image', 'i'],
type: 'string',
helpArg: '<img>',
help: 'The machine image with which to provision. Required.'
},
{
names: ['package', 'p'],
type: 'string',
helpArg: '<pkg>',
help: 'The package or instance type for the new machine(s). Required.'
},
{
names: ['name', 'n'],
type: 'string',
helpArg: '<name>',
help: 'A name for the machine. If not specified, a short random name'
+ ' will be generated.',
// TODO: for count>1 support '%d' code in name: foo0, foo1, ...
},
{
names: ['count', 'c'],
type: 'positiveInteger',
'default': 1,
helpArg: '<n>',
help: 'The number of machines to provision. Default is 1.'
},
];
do_create.help = (
'Create a new instance.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} create <options>\n'
+ '\n'
+ '{{options}}'
);
do_create.aliases = ['create-inst'];
do_create.help = 'A shortcut for "spearhead instance create".\n' + targ.help;
do_create.helpOpts = targ.helpOpts;
do_create.synopses = targ.synopses;
do_create.options = targ.options;
do_create.completionArgtypes = targ.completionArgtypes;
module.exports = do_create;

View File

@ -1,197 +0,0 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* `triton create ...`
*/
var bigspinner = require('bigspinner');
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('./common');
var errors = require('./errors');
function do_create_instance(subcmd, opts, args, callback) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length < 1 || args.length > 2) {
return callback(new errors.UsageError(format(
'incorrect number of args (%d): %s', args.length, args.join(' '))));
}
var log = this.triton.log;
var cloudapi = this.triton.cloudapi;
var cOpts = {};
vasync.pipeline({arg: {}, funcs: [
function getImg(ctx, next) {
// XXX don't get the image object if it is a UUID, waste of time
self.triton.getImage(args[0], function (err, img) {
if (err) {
return next(err);
}
ctx.img = img;
log.trace({img: img}, 'create-instance img');
next();
});
},
function getPkg(ctx, next) {
if (args.length < 2) {
return next();
}
// XXX don't get the package object if it is a UUID, waste of time
self.triton.getPackage(args[1], function (err, pkg) {
if (err) {
return next(err);
}
log.trace({pkg: pkg}, 'create-instance pkg');
ctx.pkg = pkg;
next();
});
},
function getNets(ctx, next) {
if (!opts.networks) {
return next();
}
self.triton.getNetworks(opts.networks, function (err, nets) {
if (err) {
return next(err);
}
ctx.nets = nets;
next();
});
},
function createInst(ctx, next) {
var createOpts = {
name: opts.name,
image: ctx.img.id,
'package': ctx.pkg && ctx.pkg.id,
networks: ctx.nets && ctx.nets.map(
function (net) { return net.id; })
};
log.trace({createOpts: createOpts}, 'create-instance createOpts');
ctx.start = Date.now();
cloudapi.createMachine(createOpts, function (err, inst) {
if (err) {
return next(err);
}
ctx.inst = inst;
if (opts.json) {
console.log(JSON.stringify(inst));
} else {
console.log('Creating instance %s (%s, %s@%s, %s)',
inst.name, inst.id, ctx.img.name, ctx.img.version,
inst.package);
}
next();
});
},
function maybeWait(ctx, next) {
if (!opts.wait) {
return next();
}
var spinner;
if (!opts.quiet && process.stderr.isTTY) {
spinner = bigspinner.createSpinner({
delay: 250,
stream: process.stderr,
height: process.stdout.rows - 2,
width: process.stdout.columns - 1,
hideCursor: true,
fontChar: '#'
});
}
cloudapi.waitForMachineStates({
id: ctx.inst.id,
states: ['running', 'failed']
}, function (err, inst) {
if (spinner) {
spinner.destroy();
}
if (err) {
return next(err);
}
if (opts.json) {
console.log(JSON.stringify(inst));
} else if (inst.state === 'running') {
var dur = Date.now() - ctx.start;
console.log('Created instance %s (%s) in %s',
inst.name, inst.id, common.humanDurationFromMs(dur));
}
if (inst.state !== 'running') {
next(new Error(format('failed to create instance %s (%s)',
inst.name, inst.id)));
} else {
next();
}
});
}
]}, function (err) {
callback(err);
});
};
do_create_instance.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Create options'
},
{
names: ['name', 'n'],
type: 'string',
help: 'Instance name. If not given, a random one will be created.'
},
// XXX arrayOfCommaSepString dashdash type
//{
// names: ['networks', 'nets'],
// type: 'arrayOfCommaSepString',
// help: 'One or more (comma-separated) networks IDs.'
//},
// XXX enable-firewall
// XXX locality: near, far
// XXX metadata, metadata-file
// XXX script (user-script)
// XXX tag
{
group: 'Other options'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the creation to complete.'
},
{
names: ['quiet', 'q'],
type: 'bool',
help: 'No progress spinner while waiting.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_create_instance.help = (
/* BEGIN JSSTYLED */
'Create a new instance.\n' +
'\n' +
'Usage:\n' +
' {{name}} create-instance [<options>] IMAGE [PACKAGE]\n' +
'\n' +
'{{options}}'
/* END JSSTYLED */
);
do_create_instance.aliases = ['create'];
module.exports = do_create_instance;

97
lib/do_datacenters.js Normal file
View File

@ -0,0 +1,97 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton datacenters ...`
*/
var tabula = require('tabula');
var common = require('./common');
// columns default without -o
var columnsDefault = 'name,url';
// sort default with -s
var sortDefault = 'name';
function do_datacenters(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 0) {
callback(new Error('invalid args: ' + args));
return;
}
var columns = opts.o.split(',');
var sort = opts.s.split(',');
var tritonapi = this.tritonapi;
common.cliSetupTritonApi({cli: this}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
tritonapi.cloudapi.listDatacenters(function (err, datacenters) {
if (err) {
callback(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(datacenters));
} else {
/*
* datacenters are returned in the form of:
* {name: 'url', name2: 'url2', ...}
* we "normalize" them for use by tabula by making them an array
*/
var dcs = [];
Object.keys(datacenters).forEach(function (key) {
dcs.push({
name: key,
url: datacenters[key]
});
});
tabula(dcs, {
skipHeader: opts.H,
columns: columns,
sort: sort,
dottedLookup: true
});
}
callback();
});
});
}
do_datacenters.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
columnsDefault: columnsDefault,
sortDefault: sortDefault
}));
do_datacenters.synopses = ['{{name}} {{cmd}}'];
do_datacenters.help = [
'Show datacenters in this cloud.',
'A "cloud" is a set of related datacenters that share account',
'information.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
module.exports = do_datacenters;

30
lib/do_delete.js Normal file
View File

@ -0,0 +1,30 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton delete ...` bwcompat shortcut for `triton instance delete ...`.
*/
var targ = require('./do_instance/do_delete');
function do_delete(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({
subcmd: 'delete',
opts: opts,
args: args
}, callback);
}
do_delete.help = 'A shortcut for "spearhead instance delete".\n' + targ.help;
do_delete.synopses = targ.synopses;
do_delete.options = targ.options;
do_delete.completionArgtypes = targ.completionArgtypes;
do_delete.aliases = ['rm'];
module.exports = do_delete;

View File

@ -1,98 +0,0 @@
/*
* Copyright 2015 Joyent Inc.
*
* `triton delete ...`
*/
var common = require('./common');
function do_delete_instance(subcmd, opts, args, callback) {
var self = this;
var now = Date.now();
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 1) {
callback(new Error('invalid args: ' + args));
return;
}
var arg = args[0];
var uuid;
if (common.isUUID(arg)) {
uuid = arg;
go1();
} else {
self.triton.getMachineByAlias(arg, function (err, machine) {
if (err) {
callback(err);
return;
}
uuid = machine.id;
go1();
});
}
function go1() {
// called when "uuid" is set
self.triton.cloudapi.deleteMachine(uuid, function (err, body, res) {
if (err) {
callback(err);
return;
}
if (!opts.wait) {
console.log('Deleted (async) instance %s (id %s, %s)',
arg, uuid, common.humanDurationFromMs(Date.now() - now));
callback();
return;
}
self.triton.cloudapi.waitForMachineStates({
id: uuid,
states: ['deleted']
}, function (err, machine, res) {
if (res && res.statusCode === 410) {
// gone... success!
console.log('Deleted instance %s (id %s, %s)',
arg, uuid, common.humanDurationFromMs(Date.now() - now));
callback();
return;
} else if (err) {
callback(err);
return;
}
callback(new Error('unknown state'));
});
});
}
}
do_delete_instance.aliases = ['delete'];
do_delete_instance.help = [
'delete a single instance.',
'',
'Usage:',
' {{name}} delete <alias|id>',
'',
'{{options}}'
].join('\n');
do_delete_instance.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for machine to be deleted.'
}
];
module.exports = do_delete_instance;

237
lib/do_env.js Normal file
View File

@ -0,0 +1,237 @@
/*
* 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');
var common = require('./common');
var errors = require('./errors');
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;
}
if (args.length > 1) {
return cb(new errors.UsageError('too many arguments'));
}
var profileName = args[0] || this.tritonapi.profile.name;
var allClientTypes = ['triton', 'docker', 'smartdc'];
var clientTypes = [];
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 (opts.unset) {
shortOpts += 'u';
}
if (clientTypes.length === 0) {
explicit = false;
clientTypes = allClientTypes;
} else {
explicit = true;
}
try {
var profile = mod_config.loadProfile({
configDir: this.configDir,
name: profileName
});
} catch (err) {
return cb(err);
}
if (profile.name === this.tritonapi.profile.name) {
this._applyProfileOverrides(profile);
}
var p = console.log;
clientTypes.forEach(function (clientType) {
switch (clientType) {
case 'triton':
p('# triton');
if (opts.unset) {
[
'SC_PROFILE',
'SC_URL',
'SC_ACCOUNT',
'SC_USER',
'SC_KEY_ID',
'SC_TLS_INSECURE'
].forEach(function (key) {
p('unset %s', key);
});
} else {
p('export SC_PROFILE="%s"', profile.name);
}
break;
case 'docker':
p('# 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 (opts.unset || val === null) {
p('unset %s', key);
} else {
p('export %s=%s', key, val);
}
});
} else if (opts.unset) {
[
'DOCKER_HOST',
'DOCKER_CERT_PATH',
'DOCKER_TLS_VERIFY',
'COMPOSE_HTTP_TIMEOUT'
].forEach(function (key) {
p('unset %s', key);
});
} 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':
p('# smartdc');
if (opts.unset) {
[
'SDC_URL',
'SDC_ACCOUNT',
'SDC_USER',
'SDC_KEY_ID',
'SDC_TESTING'
].forEach(function (key) {
p('unset %s', key);
});
} else {
p('export SDC_URL="%s"', profile.url);
p('export SDC_ACCOUNT="%s"', profile.account);
if (profile.user) {
p('export SDC_USER="%s"', profile.user);
} else {
p('unset SDC_USER');
}
p('export SDC_KEY_ID="%s"', profile.keyId);
if (profile.insecure) {
p('export SDC_TESTING="%s"', profile.insecure);
} else {
p('unset SDC_TESTING');
}
}
break;
default:
return cb(new errors.InternalError(
'unknown clientType: ' + clientType));
}
});
p('# Run this command to configure your shell:');
p('# eval "$(spearhead env%s%s)"',
(shortOpts ? ' -'+shortOpts : ''),
(profile.name === this.tritonapi.profile.name
? '' : ' ' + profile.name));
cb();
}
do_env.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: ''
},
{
names: ['triton', 't'],
type: 'bool',
help: 'Emit environment commands for node-triton itself (i.e. the ' +
'"SC_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 legacy ' +
'"SDC_*" variables).'
},
{
group: ''
},
{
names: ['unset', 'u'],
type: 'bool',
help: 'Emit environment to *unset* the relevant environment variables.'
}
];
do_env.synopses = ['{{name}} {{cmd}} [PROFILE]'];
do_env.help = [
/* BEGIN JSSTYLED */
'Emit shell commands to setup environment.',
'',
'Supported "clients" here are: node-smartdc (i.e. the `sdc-*` tools),',
'node-triton and spearhead-node. By default this emits the environment ',
'for all supported tools. Use options to be specific.',
'',
'{{usage}}',
'',
'{{options}}',
'If no options are given, environment variables are emitted for all ',
'clients. If PROFILE is not given, the current profile is used.',
'',
'The following Bash function can be added to one\'s "~/.bashrc" to quickly',
'change between Spearhead profiles:',
' triton-select () { eval "$(triton env $1)"; }',
'for example:',
' $ triton-select west1',
' $ triton profile get | grep name',
' name: west1',
' $ triton-select east1',
' $ triton profile get | grep name',
' name: east1'
/* END JSSTYLED */
].join('\n');
do_env.completionArgtypes = ['tritonprofile', 'none'];
module.exports = do_env;

120
lib/do_fwrule/do_create.js Normal file
View File

@ -0,0 +1,120 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_create(subcmd, opts, args, cb) {
assert.optionalString(opts.description, 'opts.description');
assert.optionalBool(opts.disabled, 'opts.disabled');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var createOpts = {
rule: args[0]
};
if (!opts.disabled) {
createOpts.enabled = true;
}
if (opts.description) {
createOpts.description = opts.description;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
tritonapi.cloudapi.createFirewallRule(
createOpts, function (err, fwrule) {
if (err) {
cb(err);
return;
}
console.log('Created firewall rule %s%s', fwrule.id,
(!fwrule.enabled ? ' (disabled)' : ''));
cb();
});
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['disabled', 'd'],
type: 'bool',
help: 'Disable the created firewall rule. By default a created '
+ 'firewall rule is enabled. Use "spearhead fwrule enable" '
+ 'to enable it later.'
},
{
names: ['description', 'D'],
type: 'string',
helpArg: 'DESC',
help: 'Description of the firewall rule.'
}
];
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] RULE-TEXT'];
do_create.help = [
/* BEGIN JSSTYLED */
'Create a firewall rule.',
'',
'{{usage}}',
'',
'{{options}}',
'Examples:',
' # Allow SSH access from any IP to all instances in a datacenter.',
' spearhead fwrule create -D "ssh" "FROM any TO all vms ALLOW tcp PORT 22"',
'',
' # Allow SSH access to a specific instance.',
' spearhead fwrule create \\',
' "FROM any TO vm ba2c95e9-1cdf-4295-8253-3fee371374d9 ALLOW tcp PORT 22"'
// TODO: link to
// https://github.com/joyent/sdc-fwrule/blob/master/docs/examples.md
// or docs.jo Cloud Firewall examples? What link? Ditto in parent.
/* END JSSTYLED */
].join('\n');
do_create.helpOpts = {
helpCol: 25
};
module.exports = do_create;

116
lib/do_fwrule/do_delete.js Normal file
View File

@ -0,0 +1,116 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton snapshot delete ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_delete(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 1) {
cb(new errors.UsageError('missing FWRULE argument(s)'));
return;
}
var tritonapi = this.top.tritonapi;
var ruleIds = args;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function confirm(_, next) {
if (opts.force) {
return next();
}
var msg;
if (ruleIds.length === 1) {
msg = 'Delete firewall rule "' + ruleIds[0] + '"? [y/n] ';
} else {
msg = format('Delete %d firewall rules (%s)? [y/n] ',
ruleIds.length, ruleIds.join(', '));
}
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
} else {
next();
}
});
},
function deleteThem(_, next) {
vasync.forEachParallel({
inputs: ruleIds,
func: function deleteOne(id, nextId) {
tritonapi.deleteFirewallRule({
id: id
}, function (err) {
if (err) {
nextId(err);
return;
}
console.log('Deleted rule %s', id);
nextId();
});
}
}, next);
}
]}, function (err) {
if (err === true) {
err = null;
}
cb(err);
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation of delete.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} FWRULE [FWRULE ...]'];
do_delete.help = [
'Remove a firewall rule.',
'',
'{{usage}}',
'',
'{{options}}',
'Where FWRULE is a firewall rule id (full UUID) or short id.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritonfwrule'];
module.exports = do_delete;

View File

@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule disable ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_disable(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument(s)'));
return;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: function disableOne(id, nextId) {
tritonapi.disableFirewallRule({ id: id }, function (err) {
if (err) {
nextId(err);
return;
}
console.log('Disabled firewall rule %s', id);
nextId();
});
}
}, cb);
});
}
do_disable.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_disable.synopses = ['{{name}} {{cmd}} FWRULE [FWRULE ...]'];
do_disable.help = [
'Disable a specific firewall rule.',
'',
'{{usage}}',
'',
'{{options}}',
'Where FWRULE is a firewall rule id (full UUID) or short id.'
].join('\n');
do_disable.completionArgtypes = ['tritonfwrule'];
module.exports = do_disable;

View File

@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule enable ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_enable(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument(s)'));
return;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: function enableOne(id, nextId) {
tritonapi.enableFirewallRule({ id: id }, function (err) {
if (err) {
nextId(err);
return;
}
console.log('Enabled firewall rule %s', id);
nextId();
});
}
}, cb);
});
}
do_enable.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_enable.synopses = ['{{name}} {{cmd}} FWRULE [FWRULE ...]'];
do_enable.help = [
'Enable a specific firewall rule.',
'',
'{{usage}}',
'',
'{{options}}',
'Where FWRULE is a firewall rule id (full UUID) or short id.'
].join('\n');
do_enable.completionArgtypes = ['tritonfwrule'];
module.exports = do_enable;

87
lib/do_fwrule/do_get.js Normal file
View File

@ -0,0 +1,87 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule get ...`
*/
var assert = require('assert-plus');
var common = require('../common');
var errors = require('../errors');
function do_get(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var id = args[0];
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
tritonapi.getFirewallRule(id, function onRule(err, fwrule) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(fwrule));
} else {
console.log(JSON.stringify(fwrule, null, 4));
}
cb();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} FWRULE'];
do_get.help = [
'Show a specific firewall rule.',
'',
'{{usage}}',
'',
'{{options}}',
'Where FWRULE is a firewall rule id (full UUID) or short id.'
].join('\n');
do_get.completionArgtypes = ['tritonfwrule', 'none'];
module.exports = do_get;

View File

@ -0,0 +1,179 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton fwrule instances ...`
*/
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var COLUMNS_DEFAULT = 'shortid,name,img,state,flags,age';
var COLUMNS_LONG = 'id,name,img,brand,package,state,flags,primaryIp,created';
var SORT_DEFAULT = 'created';
function do_instances(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var id = args[0];
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_LONG;
}
columns = columns.split(',');
var sort = opts.s.split(',');
var imgs;
var insts;
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.parallel({funcs: [
function getTheImages(next) {
tritonapi.listImages({
useCache: true,
state: 'all'
}, function (err, _imgs) {
if (err) {
next(err);
} else {
imgs = _imgs;
next();
}
});
},
function getTheMachines(next) {
tritonapi.listFirewallRuleInstances({
id: id
}, function (err, _insts) {
if (err) {
next(err);
} else {
insts = _insts;
next();
}
});
}
]}, function (err, results) {
/*
* Error handling: vasync.parallel's `err` is always a
* MultiError. We want to prefer the `getTheMachines` err,
* e.g. if both get a self-signed cert error.
*/
if (err) {
err = results.operations[1].err || err;
return cb(err);
}
// map "uuid" => "image_name"
var imgmap = {};
imgs.forEach(function (img) {
imgmap[img.id] = format('%s@%s', img.name, img.version);
});
// Add extra fields for nice output.
var now = new Date();
insts.forEach(function (inst) {
var created = new Date(inst.created);
inst.age = common.longAgo(created, now);
inst.img = imgmap[inst.image] ||
common.uuidToShortId(inst.image);
inst.shortid = inst.id.split('-', 1)[0];
var flags = [];
if (inst.brand === 'bhyve') flags.push('B');
if (inst.docker) flags.push('D');
if (inst.firewall_enabled) flags.push('F');
if (inst.brand === 'kvm') flags.push('K');
if (inst.deletion_protection) flags.push('P');
inst.flags = flags.length ? flags.join('') : undefined;
});
if (opts.json) {
common.jsonStream(insts);
} else {
tabula(insts, {
skipHeader: opts.H,
columns: columns,
sort: sort,
dottedLookup: true
});
}
cb();
});
});
}
do_instances.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_instances.synopses = ['{{name}} {{cmd}} [OPTIONS] FWRULE'];
do_instances.help = [
/* BEGIN JSSTYLED */
'List instances to which a firewall rule applies',
'',
'{{usage}}',
'',
'{{options}}',
'Where FWRULE is a firewall rule id (full UUID) or short id.',
'',
'Fields (most are self explanatory, "*" indicates a field added client-side',
'for convenience):',
' shortid* A short ID prefix.',
' flags* Single letter flags summarizing some fields:',
' "B" the brand is "bhyve"',
' "D" docker instance',
' "F" firewall is enabled',
' "K" the brand is "kvm"',
' "P" deletion protected',
' age* Approximate time since created, e.g. 1y, 2w.',
' img* The image "name@version", if available, else its',
' "shortid".'
/* END JSSTYLED */
].join('\n');
do_instances.aliases = ['insts'];
do_instances.completionArgtypes = ['tritonfwrule', 'none'];
module.exports = do_instances;

105
lib/do_fwrule/do_list.js Normal file
View File

@ -0,0 +1,105 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule list ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../common');
var errors = require('../errors');
var COLUMNS_DEFAULT = 'shortid,enabled,global,rule';
var COLUMNS_LONG = 'id,enabled,global,rule,description';
var SORT_DEFAULT = 'rule';
function do_list(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length > 0) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
tritonapi.cloudapi.listFirewallRules({}, function onRules(err, rules) {
if (err) {
cb(err);
return;
}
if (opts.json) {
common.jsonStream(rules);
} else {
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_LONG;
}
columns = columns.toLowerCase().split(',');
var sort = opts.s.toLowerCase().split(',');
if (columns.indexOf('shortid') !== -1) {
rules.forEach(function (rule) {
rule.shortid = common.uuidToShortId(rule.id);
});
}
tabula(rules, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
do_list.help = [
'Show all firewall rules.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
do_list.aliases = ['ls'];
module.exports = do_list;

169
lib/do_fwrule/do_update.js Normal file
View File

@ -0,0 +1,169 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule update ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var UPDATE_FWRULE_FIELDS
= require('../cloudapi2').CloudApi.prototype.UPDATE_FWRULE_FIELDS;
function do_update(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
var log = this.log;
var tritonapi = this.top.tritonapi;
if (args.length === 0) {
cb(new errors.UsageError('missing FWRULE argument'));
return;
}
var id = args.shift();
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function gatherDataArgs(ctx, next) {
if (opts.file) {
next();
return;
}
try {
ctx.data = common.objFromKeyValueArgs(args, {
disableDotted: true,
typeHintFromKey: UPDATE_FWRULE_FIELDS
});
} catch (err) {
next(err);
return;
}
next();
},
function gatherDataFile(ctx, next) {
if (!opts.file || opts.file === '-') {
next();
return;
}
var input = fs.readFileSync(opts.file, 'utf8');
try {
ctx.data = JSON.parse(input);
} catch (err) {
next(new errors.TritonError(format(
'invalid JSON for firewall rule update in "%s": %s',
opts.file, err)));
return;
}
next();
},
function gatherDataStdin(ctx, next) {
if (opts.file !== '-') {
next();
return;
}
common.readStdin(function gotStdin(stdin) {
try {
ctx.data = JSON.parse(stdin);
} catch (err) {
log.trace({stdin: stdin},
'invalid firewall rule update JSON on stdin');
next(new errors.TritonError(format(
'invalid JSON for firewall rule update on stdin: %s',
err)));
return;
}
next();
});
},
function validateIt(ctx, next) {
try {
common.validateObject(ctx.data, UPDATE_FWRULE_FIELDS);
} catch (e) {
next(e);
return;
}
next();
},
function updateAway(ctx, next) {
var data = ctx.data;
data.id = id;
tritonapi.updateFirewallRule(data, function (err) {
if (err) {
next(err);
return;
}
delete data.id;
console.log('Updated firewall rule %s (fields: %s)', id,
Object.keys(data).join(', '));
next();
});
}
]}, cb);
}
do_update.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['file', 'f'],
type: 'string',
helpArg: 'JSON-FILE',
help: 'A file holding a JSON file of updates, or "-" to read ' +
'JSON from stdin.'
}
];
do_update.synopses = [
'{{name}} {{cmd}} FWRULE [FIELD=VALUE ...]',
'{{name}} {{cmd}} -f JSON-FILE FWRULE'
];
do_update.help = [
'Update a firewall rule',
'',
'{{usage}}',
'',
'{{options}}',
'Updateable fields:',
' ' + Object.keys(UPDATE_FWRULE_FIELDS).sort().map(function (f) {
return f + ' (' + UPDATE_FWRULE_FIELDS[f] + ')';
}).join('\n '),
'',
'Where FWRULE is a firewall rule id (full UUID) or short id.'
].join('\n');
do_update.completionArgtypes = ['tritonfwrule', 'tritonupdatefwrulefield'];
module.exports = do_update;

59
lib/do_fwrule/index.js Normal file
View File

@ -0,0 +1,59 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton fwrule ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function FirewallRuleCLI(top) {
this.top = top;
Cmdln.call(this, {
name: top.name + ' fwrule',
desc: 'List and manage Spearhead firewall rules.',
helpSubcmds: [
'help',
'list',
'get',
'create',
'update',
'delete',
{ group: '' },
'enable',
'disable',
'instances'
],
helpOpts: {
minHelpCol: 23
}
});
}
util.inherits(FirewallRuleCLI, Cmdln);
FirewallRuleCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
FirewallRuleCLI.prototype.do_list = require('./do_list');
FirewallRuleCLI.prototype.do_create = require('./do_create');
FirewallRuleCLI.prototype.do_get = require('./do_get');
FirewallRuleCLI.prototype.do_update = require('./do_update');
FirewallRuleCLI.prototype.do_delete = require('./do_delete');
FirewallRuleCLI.prototype.do_enable = require('./do_enable');
FirewallRuleCLI.prototype.do_disable = require('./do_disable');
FirewallRuleCLI.prototype.do_instances = require('./do_instances');
module.exports = FirewallRuleCLI;

30
lib/do_fwrules.js Normal file
View File

@ -0,0 +1,30 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton fwrules ...` shortcut for `triton fwrule list ...`.
*/
var targ = require('./do_fwrule/do_list');
function do_fwrules(subcmd, opts, args, callback) {
this.handlerFromSubcmd('fwrule').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
do_fwrules.help = 'A shortcut for "spearhead fwrule list".\n' + targ.help;
do_fwrules.synopses = targ.synopses;
do_fwrules.options = targ.options;
do_fwrules.completionArgtypes = targ.completionArgtypes;
do_fwrules.hidden = true;
module.exports = do_fwrules;

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* `triton image ...`
*/
var format = require('util').format;
var errors = require('./errors');
function do_image(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 1) {
return callback(new errors.UsageError(format(
'incorrect number of args (%d): %s', args.length, args.join(' '))));
}
this.triton.getImage(args[0], function onRes(err, img) {
if (err) {
return callback(err);
}
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log(JSON.stringify(img, null, 4));
}
callback();
});
};
do_image.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_image.help = (
/* BEGIN JSSTYLED */
'Get an image.\n' +
'\n' +
'Usage:\n' +
' {{name}} image [<options>] ID|NAME\n' +
'\n' +
'{{options}}' +
'\n' +
'If there are more than one image with the given "NAME", the latest\n' +
'image (by "published_at") is returned.\n' +
'\n' +
'Note: Currently this dumps prettified JSON by default. That might change\n' +
'in the future. Use "-j" to explicitly get JSON output.\n'
/* END JSSTYLED */
);
do_image.aliases = ['img'];
module.exports = do_image;

107
lib/do_image/do_clone.js Normal file
View File

@ -0,0 +1,107 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton image clone ...`
*/
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_clone(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 1) {
cb(new errors.UsageError(
'incorrect number of args: expected 1, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function cloneImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account},
'image clone account');
if (opts.dry_run) {
next();
return;
}
tritonapi.cloneImage({image: args[0]}, function _cloneCb(err, img) {
if (err) {
next(new errors.TritonError(err, 'error cloning image'));
return;
}
log.trace({img: img}, 'image clone result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Cloned image %s to %s',
args[0], common.imageRepr(img));
}
next();
});
}
]}, cb);
}
do_clone.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually cloning.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_clone.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE'
];
do_clone.help = [
/* BEGIN JSSTYLED */
'Clone a shared image.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Note: Only shared images can be cloned.'
/* END JSSTYLED */
].join('\n');
do_clone.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_clone;

119
lib/do_image/do_copy.js Normal file
View File

@ -0,0 +1,119 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton image copy ...`
*/
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_copy(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expected 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function copyImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account, args: args},
'image copy');
if (opts.dry_run) {
next();
return;
}
tritonapi.copyImageToDatacenter(
{image: args[0], datacenter: args[1]},
function (err, img) {
if (err) {
next(new errors.TritonError(err, 'error copying image'));
return;
}
log.trace({img: img}, 'image copy result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Copied image %s to datacenter %s',
common.imageRepr(img), args[1]);
}
next();
});
}
]}, function (err) {
cb(err);
});
}
do_copy.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually copying.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_copy.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE DATACENTER'
];
do_copy.help = [
/* BEGIN JSSTYLED */
'Copy image to another datacenter.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'You must be the owner of the image to copy it. (You can use `triton image',
'clone` to get your own image clone of an image shared to you.)',
'',
'"DATACENTER" is the name of the datacenter to which to copy your image.',
'Use `triton datacenters` to show the available datacenter names.'
/* END JSSTYLED */
].join('\n');
do_copy.aliases = ['cp'];
// TODO: tritonimage should really be 'tritonownedimage' or something to
// limit to images owned by this account
// TODO: tritondatacenter bash completion
do_copy.completionArgtypes = ['tritonimage', 'tritondatacenter', 'none'];
module.exports = do_copy;

276
lib/do_image/do_create.js Normal file
View File

@ -0,0 +1,276 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton image create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var strsplit = require('strsplit');
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
var distractions = require('../distractions');
var errors = require('../errors');
var mat = require('../metadataandtags');
// ---- the command
function do_create(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 3) {
cb(new errors.UsageError(
'incorrect number of args: expect 3, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function loadTags(ctx, next) {
mat.tagsFromCreateOpts(opts, log, function (err, tags) {
if (err) {
next(err);
return;
}
if (tags) {
log.trace({tags: tags}, 'tags loaded from opts');
ctx.tags = tags;
}
next();
});
},
function loadAcl(ctx, next) {
if (!opts.acl) {
next();
return;
}
for (var i = 0; i < opts.acl.length; i++) {
if (!common.isUUID(opts.acl[i])) {
next(new errors.UsageError(format(
'invalid --acl: "%s" is not a UUID', opts.acl[i])));
return;
}
}
ctx.acl = opts.acl;
next();
},
function getInst(ctx, next) {
var id = args[0];
if (common.isUUID(id)) {
ctx.inst = {id: id};
next();
return;
}
tritonapi.getInstance(id, function (err, inst) {
if (err) {
next(err);
return;
}
log.trace({inst: inst}, 'image create: inst');
ctx.inst = inst;
next();
});
},
function createImg(ctx, next) {
var createOpts = {
machine: ctx.inst.id,
name: args[1],
version: args[2],
description: opts.description,
homepage: opts.homepage,
eula: opts.eula,
acl: ctx.acl,
tags: ctx.tags
};
log.trace({dryRun: opts.dry_run, createOpts: createOpts},
'image create createOpts');
ctx.start = Date.now();
if (opts.dry_run) {
ctx.inst = {
id: 'cafecafe-4c0e-11e5-86cd-a7fd38d2a50b',
name: 'this-is-a-dry-run'
};
console.log('Creating image %s@%s from instance %s%s',
createOpts.name, createOpts.version, ctx.inst.id,
(ctx.inst.name ? ' ('+ctx.inst.name+')' : ''));
next();
return;
}
tritonapi.cloudapi.createImageFromMachine(
createOpts, function (err, img) {
if (err) {
next(new errors.TritonError(err,
'error creating image'));
return;
}
ctx.img = img;
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Creating image %s@%s (%s)',
img.name, img.version, img.id);
}
next();
});
},
function maybeWait(ctx, next) {
if (!opts.wait) {
return next();
}
// 1 'wait': no distraction.
// >1 'wait': distraction, pass in the N.
var distraction;
if (process.stderr.isTTY && opts.wait.length > 1) {
distraction = distractions.createDistraction(opts.wait.length);
}
// Dry-run: fake wait for a few seconds.
var waiter = (opts.dry_run ?
function dryWait(waitOpts, waitCb) {
setTimeout(function () {
ctx.img.state = 'running';
waitCb(null, ctx.img);
}, 5000);
} : tritonapi.cloudapi.waitForImageStates.bind(
tritonapi.cloudapi));
waiter({
id: ctx.img.id,
states: ['active', 'failed']
}, function (err, img) {
if (distraction) {
distraction.destroy();
}
if (err) {
return next(err);
}
if (opts.json) {
console.log(JSON.stringify(img));
} else if (img.state === 'active') {
var dur = Date.now() - ctx.start;
console.log('Created image %s (%s@%s) in %s',
img.id, img.name, img.version,
common.humanDurationFromMs(dur));
}
if (img.state !== 'active') {
next(new Error(format('failed to create image %s (%s@%s)%s',
img.id, img.name, img.version,
(img.error ? format(': (%s) %s',
img.error.code, img.error.message): ''))));
} else {
next();
}
});
}
]}, function (err) {
cb(err);
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Create options'
},
{
names: ['description', 'd'],
type: 'string',
helpArg: 'DESC',
help: 'A short description of the image.'
},
{
names: ['homepage'],
type: 'string',
helpArg: 'URL',
help: 'A homepage URL for the image.'
},
{
names: ['eula'],
type: 'string',
helpArg: 'DESC',
help: 'A URL for an End User License Agreement (EULA) for the image.'
},
{
names: ['acl'],
type: 'arrayOfString',
helpArg: 'ID',
help: 'Access Control List. The ID of an account to which to give ' +
'access to this private image. This option can be used multiple ' +
'times to give access to multiple accounts.'
},
{
names: ['tag', 't'],
type: 'arrayOfString',
helpArg: 'TAG',
help: 'Add a tag when creating the image. Tags are ' +
'key/value pairs available on the image API object as the ' +
'"tags" field. TAG is one of: a "key=value" string (bool and ' +
'numeric "value" are converted to that type), a JSON object ' +
'(if first char is "{"), or a "@FILE" to have tags be ' +
'loaded from FILE. This option can be used multiple times.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually creating.'
},
{
names: ['wait', 'w'],
type: 'arrayOfBool',
help: 'Wait for the creation to complete. Use multiple times for a ' +
'spinner.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_create.synopses = [
'{{name}} {{cmd}} [OPTIONS] INST IMAGE-NAME IMAGE-VERSION'
];
do_create.help = [
/* BEGIN JSSTYLED */
'Create a new instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
/* END JSSTYLED */
].join('\n');
do_create.helpOpts = {
maxHelpCol: 20
};
do_create.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_create;

160
lib/do_image/do_delete.js Normal file
View File

@ -0,0 +1,160 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton image delete ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_delete(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
return this.do_help('help', {}, [subcmd], cb);
} else if (args.length < 1) {
return cb(new errors.UsageError('missing IMAGE arg(s)'));
}
var ids = args;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
/*
* Lookup images, if not given UUIDs: we'll need to do it anyway
* for the DeleteImage call(s), and doing so explicitly here allows
* us to emit better output.
*/
function getImgs(ctx, next) {
ctx.imgFromId = {};
ctx.missingIds = [];
// TODO: this should have a concurrency
vasync.forEachParallel({
inputs: ids,
func: function getImg(id, nextImg) {
if (common.isUUID(id)) {
// TODO: get info from cache if we have it
ctx.imgFromId[id] = {
id: id,
_repr: id
};
nextImg();
return;
}
// TODO: allow use of cache here
self.top.tritonapi.getImage(id, function (err, img) {
if (err) {
if (err.statusCode === 404) {
ctx.missingIds.push(id);
nextImg();
} else {
nextImg(err);
}
} else {
ctx.imgFromId[img.id] = img;
img._repr = format('%s (%s@%s)', img.id,
img.name, img.version);
nextImg();
}
});
}
}, next);
},
function errOnMissingIds(ctx, next) {
if (ctx.missingIds.length === 1) {
next(new errors.TritonError('no such image: '
+ ctx.missingIds[0]));
} else if (ctx.missingIds.length > 1) {
next(new errors.TritonError('no such images: '
+ ctx.missingIds.join(', ')));
} else {
next();
}
},
function confirm(ctx, next) {
if (opts.force) {
next();
return;
}
var keys = Object.keys(ctx.imgFromId);
var msg;
if (keys.length === 1) {
msg = format('Delete image %s? [y/n] ',
ctx.imgFromId[keys[0]]._repr);
} else {
msg = format('Delete %d images? [y/n] ', keys.length);
}
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
} else {
next();
}
});
},
function deleteThem(ctx, next) {
// TODO: forEachParallel with concurrency
vasync.forEachPipeline({
inputs: Object.keys(ctx.imgFromId),
func: function deleteOne(id, nextOne) {
self.top.tritonapi.cloudapi.deleteImage(id, function (err) {
if (!err) {
console.log('Deleted image %s',
ctx.imgFromId[id]._repr);
}
nextOne(err);
});
}
}, next);
}
]}, function (err) {
cb(err);
});
}
do_delete.synopses = ['{{name}} {{cmd}} [OPTIONS] IMAGE [IMAGE ...]'];
do_delete.help = [
/* BEGIN JSSTYLED */
'Delete one or more images.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).'
/* END JSSTYLED */
].join('\n');
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation of delete.'
}
];
do_delete.completionArgtypes = ['tritonimage'];
do_delete.aliases = ['rm'];
module.exports = do_delete;

120
lib/do_image/do_export.js Normal file
View File

@ -0,0 +1,120 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton image export ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_export(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expect 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function exportImage(ctx, next) {
log.trace({dryRun: opts.dry_run, manta_path: ctx.manta_path},
'image export path');
console.log('Exporting image %s to %s', args[0], args[1]);
if (opts.dry_run) {
next();
return;
}
tritonapi.exportImage({
image: args[0],
manta_path: args[1]
}, function (err, exportInfo) {
if (err) {
next(new errors.TritonError(err,
'error exporting image to manta'));
return;
}
log.trace({exportInfo: exportInfo}, 'image export: exportInfo');
ctx.exportInfo = exportInfo;
next();
});
},
function outputResults(ctx, next) {
if (opts.json) {
console.log(JSON.stringify(ctx.exportInfo));
} else {
console.log(' Manta URL: %s', ctx.exportInfo.manta_url);
console.log('Manifest path: %s', ctx.exportInfo.manifest_path);
console.log(' Image path: %s', ctx.exportInfo.image_path);
}
next();
}
]}, function (err) {
cb(err);
});
}
do_export.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually exporting.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_export.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE MANTA_PATH'
];
do_export.help = [
/* BEGIN JSSTYLED */
'Export an image.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Note: Only images that are owned by the account can be exported.'
/* END JSSTYLED */
].join('\n');
do_export.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_export;

95
lib/do_image/do_get.js Normal file
View File

@ -0,0 +1,95 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton image get ...`
*/
var format = require('util').format;
var common = require('../common');
var errors = require('../errors');
function do_get(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 1) {
return callback(new errors.UsageError(format(
'incorrect number of args (%d)', args.length)));
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
var getOpts = {
name: args[0],
excludeInactive: !opts.all
};
tritonapi.getImage(getOpts, function onRes(err, img) {
if (err) {
return callback(err);
}
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log(JSON.stringify(img, null, 4));
}
callback();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
group: 'Filtering options'
},
{
names: ['all', 'a'],
type: 'bool',
help: 'Include all images when matching by name or short ID, not ' +
'just "active" ones. By default only active images are included.'
}
];
do_get.synopses = ['{{name}} {{cmd}} [OPTIONS] IMAGE'];
do_get.help = [
/* BEGIN JSSTYLED */
'Get an image.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Note: Currently this dumps prettified JSON by default. That might change',
'in the future. Use "-j" to explicitly get JSON output.'
/* END JSSTYLED */
].join('\n');
do_get.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_get;

218
lib/do_image/do_list.js Normal file
View File

@ -0,0 +1,218 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton image list ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// filters to pass triton.listImages
var validFilters = [
'name',
'os',
'version',
'public',
'state',
'owner',
'type'
];
// columns default without -o
var columnsDefault = 'shortid,name,version,flags,os,type,pubdate';
// columns default with -l
var columnsDefaultLong = 'id,name,version,state,flags,os,type,pubdate';
// sort default with -s
var sortDefault = 'published_at';
function do_list(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
var columns = columnsDefault;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
var listOpts;
try {
listOpts = common.objFromKeyValueArgs(args, {
disableDotted: true,
validKeys: validFilters,
disableTypeConversions: true
});
} catch (e) {
callback(e);
return;
}
if (opts.all) {
listOpts.state = 'all';
}
var self = this;
var tritonapi = this.top.tritonapi;
vasync.pipeline({ arg: {}, funcs: [
function setupTritonApi(_, next) {
common.cliSetupTritonApi({cli: self.top}, next);
},
function getImages(ctx, next) {
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
if (err) {
next(err);
return;
}
ctx.imgs = imgs;
next();
});
},
function getUserAccount(ctx, next) {
// If using json output, or when there are no images that use an ACL
// - we don't need to fetch the account, as the account is only used
// to check if the image is shared (i.e. the account is in the image
// ACL) so it can output image flags in non-json mode.
if (opts.json || ctx.imgs.every(function _checkAcl(img) {
return !Array.isArray(img.acl) || img.acl.length === 0;
})) {
next();
return;
}
tritonapi.cloudapi.getAccount(function _accountCb(err, account) {
if (err) {
next(err);
return;
}
ctx.account = account;
next();
});
},
function formatImages(ctx, next) {
var imgs = ctx.imgs;
if (opts.json) {
common.jsonStream(imgs);
} else {
// Add some convenience fields
// Added fields taken from imgapi-cli.git.
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
img.shortid = img.id.split('-', 1)[0];
if (img.published_at) {
// Just the date.
img.pubdate = img.published_at.slice(0, 10);
// Normalize on no milliseconds.
img.pub = img.published_at.replace(/\.\d+Z$/, 'Z');
}
if (img.files && img.files[0]) {
img.size = img.files[0].size;
}
var flags = [];
if (img.origin) flags.push('I');
if (img['public']) flags.push('P');
if (img.state !== 'active') flags.push('X');
// Add image sharing flags.
if (Array.isArray(img.acl) && img.acl.length > 0) {
assert.string(ctx.account.id, 'ctx.account.id');
if (img.owner === ctx.account.id) {
// This image has been shared with other accounts.
flags.push('+');
}
if (img.acl.indexOf(ctx.account.id) !== -1) {
// This image has been shared with this account.
flags.push('S');
}
}
img.flags = flags.length ? flags.join('') : undefined;
}
tabula(imgs, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
next();
}
]}, callback);
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Filtering options'
},
{
names: ['all', 'a'],
type: 'bool',
help: 'List all images, not just "active" ones. This ' +
'is a shortcut for the "state=all" filter.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: sortDefault
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
do_list.help = [
/* BEGIN JSSTYLED */
'List images.',
'',
'Note: Currently, *docker* images are not included in this endpoint\'s responses.',
'You must use `docker images` against the Docker service for this data center.',
'',
'{{usage}}',
'',
'{{options}}',
'Filters:',
' FIELD=VALUE Equality filter. Supported fields: account, owner,',
' state, name, os, and type.',
' FIELD=true|false Boolean filter. Supported fields: public.',
' FIELD=~SUBSTRING Substring filter. Supported fields: name',
'',
'Fields (most are self explanatory, "*" indicates a field added client-side',
'for convenience):',
' shortid* A short ID prefix.',
' flags* Single letter flags summarizing some fields:',
' "P" image is public',
' "+" you are sharing this image with others',
' "S" this image has been shared with you',
' "I" an incremental image (i.e. has an origin)',
' "X" has a state *other* than "active"',
' pubdate* Short form of "published_at" with just the date',
' pub* Short form of "published_at" elliding milliseconds.',
' size* The number of bytes of the image file (files.0.size)',
' type The image type. As of CloudAPI 8.0 this is defined by',
' <https://images.joyent.com/docs/#manifest-type>. Before',
' that it was one of "smartmachine" or "virtualmachine".'
/* END JSSTYLED */
].join('\n');
do_list.aliases = ['ls'];
module.exports = do_list;

115
lib/do_image/do_share.js Normal file
View File

@ -0,0 +1,115 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton image share ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_share(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expect 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function shareImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account},
'image share account');
if (opts.dry_run) {
next();
return;
}
tritonapi.shareImage({
image: args[0],
account: args[1]
}, function (err, img) {
if (err) {
next(new errors.TritonError(err, 'error sharing image'));
return;
}
log.trace({img: img}, 'image share result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Shared image %s with account %s',
args[0], args[1]);
}
next();
});
}
]}, function (err) {
cb(err);
});
}
do_share.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually sharing.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_share.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
];
do_share.help = [
/* BEGIN JSSTYLED */
'Share an image with another account.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Where "ACCOUNT" is the full account UUID.',
'',
'Note: Only images that are owned by the account can be shared.'
/* END JSSTYLED */
].join('\n');
do_share.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_share;

115
lib/do_image/do_unshare.js Normal file
View File

@ -0,0 +1,115 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton image unshare ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_unshare(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expect 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function unshareImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account},
'image unshare account');
if (opts.dry_run) {
next();
return;
}
tritonapi.unshareImage({
image: args[0],
account: args[1]
}, function (err, img) {
if (err) {
next(new errors.TritonError(err, 'error unsharing image'));
return;
}
log.trace({img: img}, 'image unshare result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Unshared image %s with account %s',
args[0], args[1]);
}
next();
});
}
]}, function (err) {
cb(err);
});
}
do_unshare.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually sharing.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_unshare.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
];
do_unshare.help = [
/* BEGIN JSSTYLED */
'Unshare an image with another account.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Where "ACCOUNT" is the full account UUID.',
'',
'Note: Only images that are owned by the account can be unshared.'
/* END JSSTYLED */
].join('\n');
do_unshare.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_unshare;

148
lib/do_image/do_wait.js Normal file
View File

@ -0,0 +1,148 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton image wait ...`
*/
var vasync = require('vasync');
var common = require('../common');
var distractions = require('../distractions');
var errors = require('../errors');
function do_wait(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
return this.do_help('help', {}, [subcmd], cb);
} else if (args.length < 1) {
return cb(new errors.UsageError('missing IMAGE arg(s)'));
}
var ids = args;
var states = [];
opts.states.forEach(function (s) {
/* JSSTYLED */
states = states.concat(s.trim().split(/\s*,\s*/g));
});
var distraction;
var done = 0;
var imgFromId = {};
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function getImgs(_, next) {
vasync.forEachParallel({
inputs: ids,
func: function getImg(id, nextImg) {
self.top.tritonapi.getImage(id, function (err, img) {
if (err) {
return nextImg(err);
}
if (states.indexOf(img.state) !== -1) {
console.log('%d/%d: Image %s (%s@%s) already %s',
++done, ids.length, img.id, img.name,
img.version, img.state);
} else {
imgFromId[img.id] = img;
}
nextImg();
});
}
}, next);
},
function waitForImgs(_, next) {
var idsToWaitFor = Object.keys(imgFromId);
if (idsToWaitFor.length === 0) {
return next();
}
if (idsToWaitFor.length === 1) {
var img2 = imgFromId[idsToWaitFor[0]];
console.log(
'Waiting for image %s (%s@%s) to enter state (states: %s)',
img2.id, img2.name, img2.version, states.join(', '));
} else {
console.log(
'Waiting for %d images to enter state (states: %s)',
idsToWaitFor.length, states.join(', '));
}
/*
* TODO: need BigSpinner.log first.
* TODO: Also when adding a spinner, we need an equiv option to
* `triton create -wwww` to trigger the spinner (and size). By
* default: no spinner.
*/
if (false &&
process.stderr.isTTY)
{
distraction = distractions.createDistraction();
}
vasync.forEachParallel({
inputs: idsToWaitFor,
func: function waitForImg(id, nextImg) {
self.top.tritonapi.cloudapi.waitForImageStates({
id: id,
states: states
}, function (err, img, res) {
if (err) {
return nextImg(err);
}
console.log('%d/%d: Image %s (%s@%s) moved to state %s',
++done, ids.length, img.id, img.name,
img.version, img.state);
nextImg();
});
}
}, next);
}
]}, function (err) {
if (distraction) {
distraction.destroy();
}
cb(err);
});
}
do_wait.synopses = ['{{name}} {{cmd}} [-s STATES] IMAGE [IMAGE ...]'];
do_wait.help = [
'Wait for images to change to a particular state.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "states" is a comma-separated list of target instance states,',
'by default "active,failed". In other words, "spearhead img wait foo0" will',
'wait for image "foo0" to complete creation.'
].join('\n');
do_wait.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['states', 's'],
type: 'arrayOfString',
default: ['active', 'failed'],
helpArg: 'STATES',
help: 'Instance states on which to wait. Default is "active,failed". '
+ 'Values can be comma-separated or multiple uses of the option.'
}
];
do_wait.completionArgtypes = ['tritonimage'];
module.exports = do_wait;

68
lib/do_image/index.js Normal file
View File

@ -0,0 +1,68 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton image ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function ImageCLI(top) {
this.top = top;
Cmdln.call(this, {
name: top.name + ' image',
/* BEGIN JSSTYLED */
desc: [
'List and manage Spearhead images.'
].join('\n'),
/* END JSSTYLED */
helpOpts: {
minHelpCol: 24 /* line up with option help */
},
helpSubcmds: [
'help',
'list',
'get',
'clone',
'copy',
'create',
'delete',
'export',
'share',
'unshare',
'wait'
]
});
}
util.inherits(ImageCLI, Cmdln);
ImageCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
ImageCLI.prototype.do_list = require('./do_list');
ImageCLI.prototype.do_get = require('./do_get');
ImageCLI.prototype.do_clone = require('./do_clone');
ImageCLI.prototype.do_copy = require('./do_copy');
ImageCLI.prototype.do_create = require('./do_create');
ImageCLI.prototype.do_delete = require('./do_delete');
ImageCLI.prototype.do_export = require('./do_export');
ImageCLI.prototype.do_share = require('./do_share');
ImageCLI.prototype.do_unshare = require('./do_unshare');
ImageCLI.prototype.do_wait = require('./do_wait');
ImageCLI.aliases = ['img'];
module.exports = ImageCLI;

View File

@ -1,153 +1,31 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* `triton images ...`
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var format = require('util').format;
var tabula = require('tabula');
var common = require('./common');
var errors = require('./errors');
/*
* Copyright 2017 Joyent, Inc.
*
* `triton images ...` bwcompat shortcut for `triton image list ...`.
*/
var targ = require('./do_image/do_list');
function do_images(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
this.handlerFromSubcmd('image').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
/* JSSTYLED */
var columns = opts.o.trim().split(/\s*,\s*/g);
/* JSSTYLED */
var sort = opts.s.trim().split(/\s*,\s*/g);
var validFilters = [
'name', 'os', 'version', 'public', 'state', 'owner', 'type'
];
var listOpts;
try {
listOpts = common.kvToObj(args, validFilters);
} catch (e) {
callback(e);
return;
}
if (opts.all) {
listOpts.state = 'all';
}
this.triton.cloudapi.listImages(listOpts, function onRes(err, imgs, res) {
if (err) {
return callback(err);
}
if (opts.json) {
// XXX we should have a common method for all these:
// XXX sorting
// XXX if opts.o is given, then filter to just those fields?
console.log(common.jsonStream(imgs));
} else {
// Add some convenience fields
// Added fields taken from imgapi-cli.git.
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
img.shortid = img.id.split('-', 1)[0];
if (img.published_at) {
// Just the date.
img.pubdate = img.published_at.slice(0, 10);
// Normalize on no milliseconds.
img.pub = img.published_at.replace(/\.\d+Z$/, 'Z');
}
if (img.files && img.files[0]) {
img.size = img.files[0].size;
}
var flags = [];
if (img.origin) flags.push('I');
if (img['public']) flags.push('P');
if (img.state !== 'active') flags.push('X');
img.flags = flags.length ? flags.join('') : undefined;
}
tabula(imgs, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
callback();
});
};
do_images.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Filtering options'
},
{
names: ['all', 'a'],
type: 'bool',
help: 'List all images, not just "active" ones. This ' +
'is a shortcut for the "state=all" filter.'
},
{
group: 'Output options'
},
{
names: ['H'],
type: 'bool',
help: 'Omit table header row.'
},
{
names: ['o'],
type: 'string',
default: 'id,name,version,state,flags,os,pubdate',
help: 'Specify fields (columns) to output.',
helpArg: 'field1,...'
},
{
names: ['s'],
type: 'string',
default: 'published_at',
help: 'Sort on the given fields. Default is "published_at".',
helpArg: 'field1,...'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_images.help = (
/* BEGIN JSSTYLED */
'List images.\n' +
'\n' +
'Usage:\n' +
' {{name}} images [<options>] [<filters>]\n' +
'\n' +
'Filters:\n' +
' FIELD=VALUE Field equality filter. Supported fields: \n' +
' account, owner, state, name, os, and type.\n' +
' FIELD=true|false Field boolean filter. Supported fields: public.\n' +
' FIELD=~SUBSTRING Field substring filter. Supported fields: name\n' +
'\n' +
'Fields (most are self explanatory, the client adds some for convenience):\n' +
' flags This is a set of single letter flags\n' +
' summarizing some fields. "P" indicates the\n' +
' image is public. "I" indicates an incremental\n' +
' image (i.e. has an origin). "X" indicates an\n' +
' image with a state *other* than "active".\n' +
' pubdate Short form of "published_at" with just the date\n' +
' pub Short form of "published_at" elliding milliseconds.\n' +
' size The number of bytes of the image file (files.0.size)\n' +
'\n' +
'{{options}}'
/* END JSSTYLED */
);
do_images.help = 'A shortcut for "spearhead image list".\n' + targ.help;
do_images.synopses = targ.synopses;
do_images.options = targ.options;
do_images.completionArgtypes = targ.completionArgtypes;
do_images.aliases = ['imgs'];
do_images.hidden = true;
module.exports = do_images;

View File

@ -1,5 +1,11 @@
/*
* Copyright 2015 Joyent Inc.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton info ...`
*/
@ -22,33 +28,36 @@ function do_info(subcmd, opts, args, callback) {
var out = {};
var i = 0;
var tritonapi = this.tritonapi;
this.triton.cloudapi.getAccount(cb.bind('account')); i++;
this.triton.cloudapi.listMachines(cb.bind('machines')); i++;
function cb(err, data) {
if (err) {
callback(err);
common.cliSetupTritonApi({cli: this}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
out[this.toString()] = data;
if (--i === 0)
done();
}
tritonapi.cloudapi.getAccount(cb.bind('account')); i++;
tritonapi.cloudapi.listMachines(cb.bind('machines')); i++;
function done() {
if (opts.json) {
console.log(JSON.stringify(out));
} else {
// pretty print
console.log('%s - %s %s <%s>',
out.account.login,
out.account.firstName,
out.account.lastName,
out.account.email);
console.log(self.triton.cloudapi.url);
console.log();
console.log('%d instance(s)', out.machines.length);
function cb(err, data) {
if (err) {
callback(err);
return;
}
out[this.toString()] = data;
if (--i === 0)
done();
}
function done() {
// parse name
var name;
if (out.account.firstName && out.account.lastName)
name = format('%s %s', out.account.firstName,
out.account.lastName);
else if (out.account.firstName)
name = out.account.firstName;
// parse machine states and accounting
var states = {};
var disk = 0;
var memory = 0;
@ -59,16 +68,36 @@ function do_info(subcmd, opts, args, callback) {
memory += machine.memory;
disk += machine.disk;
});
Object.keys(states).forEach(function (state) {
console.log('- %d %s', states[state], state);
});
console.log('- %s RAM Total',
common.humanSizeFromBytes(memory * 1000 * 1000));
console.log('- %s Disk Total',
common.humanSizeFromBytes(disk * 1000 * 1000));
disk *= 1000 * 1000;
memory *= 1000 * 1000;
var data = {};
data.login = out.account.login;
if (name)
data.name = name;
data.email = out.account.email;
data.url = self.tritonapi.cloudapi.url;
data.totalDisk = disk;
data.totalMemory = memory;
if (opts.json) {
data.totalInstances = out.machines.length;
data.instances = states;
console.log(JSON.stringify(data));
} else {
data.totalDisk = common.humanSizeFromBytes(disk);
data.totalMemory = common.humanSizeFromBytes(memory);
Object.keys(data).forEach(function (key) {
console.log('%s: %s', key, data[key]);
});
console.log('instances: %d', out.machines.length);
Object.keys(states).forEach(function (key) {
console.log(' %s: %d', key, states[key]);
});
}
callback();
}
callback();
}
});
}
do_info.options = [
@ -83,13 +112,15 @@ do_info.options = [
help: 'JSON output.'
}
];
do_info.help = (
'Print an account summary.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} account\n'
+ '\n'
+ '{{options}}'
);
do_info.synopses = ['{{name}} {{cmd}}'];
do_info.help = [
'Print an account summary.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
module.exports = do_info;

View File

@ -1,64 +0,0 @@
/*
* Copyright 2015 Joyent Inc.
*
* `triton instance ...`
*/
var common = require('./common');
function do_instance(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length !== 1) {
callback(new Error('invalid args: ' + args));
return;
}
var id = args[0];
if (common.isUUID(id)) {
this.triton.cloudapi.getMachine(id, cb);
} else {
this.triton.getMachineByAlias(id, cb);
}
function cb(err, machine) {
if (err) {
callback(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(machine));
} else {
console.log(JSON.stringify(machine, null, 4));
}
callback();
}
}
do_instance.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_instance.help = (
'Show a single instance.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} instance <alias|id>\n'
+ '\n'
+ '{{options}}'
);
do_instance.aliases = ['inst'];
module.exports = do_instance;

124
lib/do_instance/do_audit.js Normal file
View File

@ -0,0 +1,124 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2015 Joyent, Inc.
*
* `triton instance audit ...`
*/
var format = require('util').format;
var tabula = require('tabula');
var common = require('../common');
var errors = require('../errors');
// columns default without -o
var columnsDefault = 'shortid,time,action,success';
// columns default with -l
var columnsDefaultLong = 'id,time,action,success';
// sort default with -s
var sortDefault = 'id,time';
function do_audit(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length === 0) {
cb(new errors.UsageError('missing INST argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('too many arguments: ' + args));
return;
}
var columns = columnsDefault;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
var arg = args[0];
var uuid;
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
if (common.isUUID(arg)) {
uuid = arg;
go1();
} else {
tritonapi.getInstance(arg, function (err, inst) {
if (err) {
cb(err);
return;
}
uuid = inst.id;
go1();
});
}});
function go1() {
tritonapi.cloudapi.machineAudit(uuid, function (err, audit) {
if (err) {
cb(err);
return;
}
audit.forEach(function (a) {
a.id = uuid;
a.shortid = common.uuidToShortId(uuid);
});
if (opts.json) {
common.jsonStream(audit);
} else {
tabula(audit, {
skipHeader: opts.H,
columns: columns,
sort: sort,
dottedLookup: true
});
}
cb();
});
}
}
do_audit.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: sortDefault
}));
do_audit.synopses = ['{{name}} {{cmd}} [OPTIONS] INST'];
do_audit.help = [
'List instance actions.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_audit.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_audit;

View File

@ -0,0 +1,553 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2019 Joyent, Inc.
*
* `triton instance create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
var distractions = require('../distractions');
var errors = require('../errors');
var mat = require('../metadataandtags');
var NETWORK_OBJECT_FIELDS =
require('../constants').NETWORK_OBJECT_FIELDS;
function parseVolMount(volume) {
var components;
var volMode;
var volMountpoint;
var volName;
var VALID_MODES = ['ro', 'rw'];
var VALID_VOLUME_NAME_REGEXP = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]+$/;
assert.string(volume, 'volume');
components = volume.split(':');
if (components.length !== 2 && components.length !== 3) {
return new errors.UsageError('invalid volume specified, must be in ' +
'the form "<volume name>:<mount path>[:<mode>]", got: "' + volume +
'"');
}
volName = components[0];
volMountpoint = components[1];
volMode = components[2];
// first component should be a volume name. We only check here that it
// syntactically looks like a volume name, we'll leave the upstream to
// determine if it's not actually a volume.
if (!VALID_VOLUME_NAME_REGEXP.test(volName)) {
return new errors.UsageError('invalid volume name, got: "' + volume +
'"');
}
// second component should be an absolute path
// NOTE: if we ever move past node 0.10, we could use path.isAbsolute(path)
if (volMountpoint.length === 0 || volMountpoint[0] !== '/') {
return new errors.UsageError('invalid volume mountpoint, must be ' +
'absolute path, got: "' + volume + '"');
}
if (volMountpoint.indexOf('\0') !== -1) {
return new errors.UsageError('invalid volume mountpoint, contains ' +
'invalid characters, got: "' + volume + '"');
}
if (volMountpoint.search(/[^\/]/) === -1) {
return new errors.UsageError('invalid volume mountpoint, must contain' +
' at least one non-/ character, got: "' + volume + '"');
}
// third component is optional mode: 'ro' or 'rw'
if (components.length === 3 && VALID_MODES.indexOf(volMode) === -1) {
return new errors.UsageError('invalid volume mode, got: "' + volume +
'"');
}
return {
mode: volMode || 'rw',
mountpoint: volMountpoint,
name: volName
};
}
function do_create(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
return cb(new errors.UsageError('incorrect number of args'));
} else if (opts.nic && opts.network) {
return cb(new errors.UsageError(
'--network and --nic cannot be specified together'));
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
/*
* Make sure if volumes were passed, they're in the correct form.
*/
function parseVolMounts(ctx, next) {
var idx;
var validationErrs = [];
var parsedObj;
var volMounts = [];
if (!opts.volume) {
next();
return;
}
for (idx = 0; idx < opts.volume.length; idx++) {
parsedObj = parseVolMount(opts.volume[idx]);
if (parsedObj instanceof Error) {
validationErrs.push(parsedObj);
} else {
// if it's not an error, it's a volume
volMounts.push(parsedObj);
}
}
if (validationErrs.length > 0) {
next(new errors.MultiError(validationErrs));
return;
}
if (volMounts.length > 0) {
ctx.volMounts = volMounts;
}
next();
},
/*
* Parse any nics given via `--nic`
*/
function parseNics(ctx, next) {
if (!opts.nic) {
next();
return;
}
ctx.nics = [];
var i;
var networksSeen = {};
var nic;
var nics = opts.nic;
log.trace({nics: nics}, 'parsing nics');
for (i = 0; i < nics.length; i++) {
nic = nics[i].split(',');
try {
nic = common.parseNicStr(nic);
if (networksSeen[nic.ipv4_uuid]) {
throw new errors.UsageError(format(
'only 1 ip on a network allowed '
+ '(network %s specified multiple times)',
nic.ipv4_uuid));
}
networksSeen[nic.ipv4_uuid] = true;
ctx.nics.push(nic);
} catch (err) {
next(err);
return;
}
}
log.trace({nics: ctx.nics}, 'parsed nics');
next();
},
function loadMetadata(ctx, next) {
mat.metadataFromOpts(opts, log, function (err, metadata) {
if (err) {
next(err);
return;
}
if (metadata) {
log.trace({metadata: metadata},
'metadata loaded from opts');
ctx.metadata = metadata;
}
next();
});
},
function loadTags(ctx, next) {
mat.tagsFromCreateOpts(opts, log, function (err, tags) {
if (err) {
next(err);
return;
}
if (tags) {
log.trace({tags: tags}, 'tags loaded from opts');
ctx.tags = tags;
}
next();
});
},
function getImg(ctx, next) {
var _opts = {
name: args[0],
excludeInactive: true,
useCache: true
};
tritonapi.getImage(_opts, function (err, img) {
if (err) {
return next(err);
}
ctx.img = img;
log.trace({img: img}, 'create-instance img');
next();
});
},
function getPkg(ctx, next) {
if (args.length < 2) {
return next();
}
var id = args[1];
if (common.isUUID(id)) {
ctx.pkg = {id: id};
next();
return;
}
tritonapi.getPackage(id, function (err, pkg) {
if (err) {
return next(err);
}
log.trace({pkg: pkg}, 'create-instance pkg');
ctx.pkg = pkg;
next();
});
},
function getNets(ctx, next) {
if (!opts.network) {
return next();
}
// TODO: want an error or warning on no networks?
ctx.nets = [];
vasync.forEachPipeline({
inputs: opts.network,
func: function getOneNetwork(name, nextNet) {
tritonapi.getNetwork(name, function (err, net) {
if (err) {
nextNet(err);
} else {
ctx.nets.push(net);
nextNet();
}
});
}
}, next);
},
function createInst(ctx, next) {
assert.optionalArrayOfObject(ctx.volMounts, 'ctx.volMounts');
var createOpts = {
name: opts.name,
image: ctx.img.id,
'package': ctx.pkg && ctx.pkg.id
};
if (ctx.nets) {
createOpts.networks = ctx.nets.map(function (net) {
return net.id;
});
} else if (ctx.nics) {
createOpts.networks = ctx.nics;
}
if (ctx.volMounts) {
createOpts.volumes = ctx.volMounts;
}
if (opts.affinity) {
createOpts.affinity = opts.affinity;
}
if (ctx.metadata) {
Object.keys(ctx.metadata).forEach(function (key) {
createOpts['metadata.'+key] = ctx.metadata[key];
});
}
if (ctx.tags) {
Object.keys(ctx.tags).forEach(function (key) {
createOpts['tag.'+key] = ctx.tags[key];
});
}
if (opts.allow_shared_images) {
createOpts.allow_shared_images = true;
}
for (var i = 0; i < opts._order.length; i++) {
var opt = opts._order[i];
if (opt.key === 'firewall') {
createOpts.firewall_enabled = opt.value;
} else if (opt.key === 'deletion_protection') {
createOpts.deletion_protection = opt.value;
}
}
log.trace({dryRun: opts.dry_run, createOpts: createOpts},
'create-instance createOpts');
ctx.start = Date.now();
if (opts.dry_run) {
ctx.inst = {
id: 'beefbeef-4c0e-11e5-86cd-a7fd38d2a50b',
name: 'this-is-a-dry-run'
};
console.log('Creating instance %s (%s, %s@%s)',
ctx.inst.name, ctx.inst.id,
ctx.img.name, ctx.img.version);
return next();
}
tritonapi.cloudapi.createMachine(createOpts, function (err, inst) {
if (err) {
next(new errors.TritonError(err,
'error creating instance'));
return;
}
ctx.inst = inst;
if (opts.json) {
console.log(JSON.stringify(inst));
} else {
console.log('Creating instance %s (%s, %s@%s%s)',
inst.name, inst.id, ctx.img.name, ctx.img.version,
inst.package ? format(', %s', inst.package) : '');
}
next();
});
},
function maybeWait(ctx, next) {
if (!opts.wait) {
return next();
}
// 1 'wait': no distraction.
// >1 'wait': distraction, pass in the N.
var distraction;
if (process.stderr.isTTY && opts.wait.length > 1) {
distraction = distractions.createDistraction(opts.wait.length);
}
// Dry-run: fake wait for a few seconds.
var waiter = (opts.dry_run ?
function dryWait(waitOpts, waitCb) {
setTimeout(function () {
ctx.inst.state = 'running';
waitCb(null, ctx.inst);
}, 5000);
} : tritonapi.cloudapi.waitForMachineStates.bind(
tritonapi.cloudapi));
waiter({
id: ctx.inst.id,
states: ['running', 'failed']
}, function (err, inst) {
if (distraction) {
distraction.destroy();
}
if (err) {
return next(err);
}
if (opts.json) {
console.log(JSON.stringify(inst));
} else if (inst.state === 'running') {
var dur = Date.now() - ctx.start;
console.log('Created instance %s (%s) in %s',
inst.name, inst.id, common.humanDurationFromMs(dur));
}
if (inst.state !== 'running') {
next(new Error(format('failed to create instance %s (%s)',
inst.name, inst.id)));
} else {
next();
}
});
}
]}, function (err) {
cb(err);
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Create options'
},
{
names: ['name', 'n'],
helpArg: 'NAME',
type: 'string',
help: 'Instance name. If not given, one will be generated server-side.'
},
{
names: ['tag', 't'],
type: 'arrayOfString',
helpArg: 'TAG',
help: 'Add a tag when creating the instance. Tags are ' +
'key/value pairs available on the instance API object as the ' +
'"tags" field. TAG is one of: a "key=value" string (bool and ' +
'numeric "value" are converted to that type), a JSON object ' +
'(if first char is "{"), or a "@FILE" to have tags be ' +
'loaded from FILE. This option can be used multiple times.'
},
{
names: ['affinity', 'a'],
type: 'arrayOfString',
helpArg: 'RULE',
help: 'Affinity rules for selecting a server for this instance. ' +
'Rules have one of the following forms: `instance==INST` (the ' +
'new instance must be on the same server as INST), ' +
'`instance!=INST` (new inst must *not* be on the same server as ' +
'INST), `instance==~INST` (*attempt* to place on the same server ' +
'as INST), or `instance!=~INST` (*attempt* to place on a server ' +
'other than INST\'s). `INST` is an existing instance name or ' +
'id. Use this option more than once for multiple rules.',
completionType: 'tritonaffinityrule'
},
{
group: ''
},
{
names: ['network', 'N'],
type: 'arrayOfCommaSepString',
helpArg: 'NETWORK',
help: 'One or more comma-separated networks (ID, name or short id). ' +
'This option can be used multiple times.',
completionType: 'tritonnetwork'
},
{
names: ['nic'],
type: 'arrayOfString',
helpArg: 'NICOPTS',
help: 'A network interface object containing comma separated ' +
'key=value pairs (Network object format). ' +
'This option can be used multiple times for multiple NICs. ' +
'Valid keys are: ' + Object.keys(NETWORK_OBJECT_FIELDS).join(', ')
},
{
// TODO: add boolNegationPrefix:'no-' when that cmdln pull is in
names: ['firewall'],
type: 'bool',
help: 'Enable Cloud Firewall on this instance. See ' +
'<https://docs.spearhead.cloud/network/firewall>'
},
{
names: ['deletion-protection'],
type: 'bool',
help: 'Enable Deletion Protection on this instance. Such an instance ' +
'cannot be deleted until the protection is disabled. See ' +
'<https://apidocs.joyent.com/cloudapi/#deletion-protection>'
},
{
names: ['volume', 'v'],
type: 'arrayOfString',
help: 'Mount a volume into the instance (non-KVM only). VOLMOUNT is ' +
'"<volume-name:/mount/point>[:access-mode]" where access mode is ' +
'one of "ro" for read-only or "rw" for read-write (default). For ' +
'example: "-v myvolume:/mnt:ro" to mount "myvolume" read-only on ' +
'/mnt in this instance.',
helpArg: 'VOLMOUNT',
hidden: true
},
{
group: ''
},
{
names: ['metadata', 'm'],
type: 'arrayOfString',
helpArg: 'DATA',
help: 'Add metadata when creating the instance. Metadata are ' +
'key/value pairs available on the instance API object as the ' +
'"metadata" field, and inside the instance via the "mdata-*" ' +
'commands. DATA is one of: a "key=value" string (bool and ' +
'numeric "value" are converted to that type), a JSON object ' +
'(if first char is "{"), or a "@FILE" to have metadata be ' +
'loaded from FILE. This option can be used multiple times.'
},
{
names: ['metadata-file', 'M'],
type: 'arrayOfString',
helpArg: 'KEY=FILE',
help: 'Set a metadata key KEY from the contents of FILE.'
},
{
names: ['script'],
type: 'arrayOfString',
helpArg: 'FILE',
help: 'Load a file to be used for the "user-script" metadata key. In ' +
'Joyent-provided images, the user-script is run at every boot ' +
'of the instance. This is a shortcut for `-M user-script=FILE`.'
},
{
names: ['allow-shared-images'],
type: 'bool',
help: 'Allow instance creation to use a shared image.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually creating.'
},
{
names: ['wait', 'w'],
type: 'arrayOfBool',
help: 'Wait for the creation to complete. Use multiple times for a ' +
'spinner.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] IMAGE PACKAGE'];
do_create.help = [
/* BEGIN JSSTYLED */
'Create a new instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where IMAGE is an image name, name@version, id, or short id (from ',
'`spearhead image list`) and PACKAGE is a package name, id, or short id',
'(from `spearhead package list`).'
/* END JSSTYLED */
].join('\n');
do_create.helpOpts = {
maxHelpCol: 16
};
do_create.completionArgtypes = ['tritonimage', 'tritonpackage', 'none'];
module.exports = do_create;

View File

@ -0,0 +1,17 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance delete ...`
*/
var gen_do_ACTION = require('./gen_do_ACTION');
var do_delete = gen_do_ACTION({action: 'delete', aliases: ['rm']});
module.exports = do_delete;

View File

@ -0,0 +1,125 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance disable-deletion-protection ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_disable_deletion_protection(subcmd, opts, args, cb) {
assert.object(opts, 'opts');
assert.arrayOfString(args, 'args');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument(s)'));
return;
}
var cli = this.top;
function wait(name, id, next) {
assert.string(name, 'name');
assert.uuid(id, 'id');
assert.func(next, 'next');
cli.tritonapi.cloudapi.waitForDeletionProtectionEnabled({
id: id,
state: false
}, function (err, inst) {
if (err) {
next(err);
return;
}
assert.ok(!inst.deletion_protection, 'inst ' + id
+ ' deletion_protection not in expected state after '
+ 'waitForDeletionProtectionEnabled');
console.log('Disabled deletion protection for instance "%s"', name);
next();
});
}
function disableOne(name, next) {
assert.string(name, 'name');
assert.func(next, 'next');
cli.tritonapi.disableInstanceDeletionProtection({
id: name
}, function disableProtectionCb(err, fauxInst) {
if (err) {
next(err);
return;
}
console.log('Disabling deletion protection for instance "%s"',
name);
if (opts.wait) {
wait(name, fauxInst.id, next);
} else {
next();
}
});
}
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: disableOne
}, function vasyncCb(err) {
cb(err);
});
});
}
do_disable_deletion_protection.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for deletion protection to be removed.'
}
];
do_disable_deletion_protection.synopses = [
'{{name}} disable-deletion-protection [OPTIONS] INST [INST ...]'
];
do_disable_deletion_protection.help = [
'Disable deletion protection on one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_disable_deletion_protection.completionArgtypes = ['tritoninstance'];
module.exports = do_disable_deletion_protection;

View File

@ -0,0 +1,112 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance disable-firewall ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_disable_firewall(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument(s)'));
return;
}
var cli = this.top;
function wait(name, id, next) {
cli.tritonapi.cloudapi.waitForMachineFirewallEnabled({
id: id,
state: false
}, function (err, inst) {
if (err) {
next(err);
return;
}
assert.ok(!inst.firewall_enabled, format(
'inst %s firewall_enabled not in expected state after '
+ 'waitForMachineFirewallEnabled', id));
console.log('Disabled firewall for instance "%s"', name);
next();
});
}
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: function disableOne(name, nextInst) {
cli.tritonapi.disableInstanceFirewall({
id: name
}, function (err, fauxInst) {
if (err) {
nextInst(err);
return;
}
console.log('Disabling firewall for instance "%s"', name);
if (opts.wait) {
wait(name, fauxInst.id, nextInst);
} else {
nextInst();
}
});
}
}, function (err) {
cb(err);
});
});
}
do_disable_firewall.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the firewall to be disabled.'
}
];
do_disable_firewall.synopses = [
'{{name}} disable-firewall [OPTIONS] INST [INST ...]'
];
do_disable_firewall.help = [
'Disable the firewall of one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_disable_firewall.completionArgtypes = ['tritoninstance'];
module.exports = do_disable_firewall;

View File

@ -0,0 +1,125 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance enable-deletion-protection ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_enable_deletion_protection(subcmd, opts, args, cb) {
assert.object(opts, 'opts');
assert.arrayOfString(args, 'args');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument(s)'));
return;
}
var cli = this.top;
function wait(name, id, next) {
assert.string(name, 'name');
assert.uuid(id, 'id');
assert.func(next, 'next');
cli.tritonapi.cloudapi.waitForDeletionProtectionEnabled({
id: id,
state: true
}, function (err, inst) {
if (err) {
next(err);
return;
}
assert.ok(inst.deletion_protection, 'inst ' + id
+ ' deletion_protection not in expected state after '
+ 'waitForDeletionProtectionEnabled');
console.log('Enabled deletion protection for instance "%s"', name);
next();
});
}
function enableOne(name, next) {
assert.string(name, 'name');
assert.func(next, 'next');
cli.tritonapi.enableInstanceDeletionProtection({
id: name
}, function enableProtectionCb(err, fauxInst) {
if (err) {
next(err);
return;
}
console.log('Enabling deletion protection for instance "%s"',
name);
if (opts.wait) {
wait(name, fauxInst.id, next);
} else {
next();
}
});
}
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: enableOne
}, function vasyncCb(err) {
cb(err);
});
});
}
do_enable_deletion_protection.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for deletion protection to be enabled.'
}
];
do_enable_deletion_protection.synopses = [
'{{name}} enable-deletion-protection [OPTIONS] INST [INST ...]'
];
do_enable_deletion_protection.help = [
'Enable deletion protection for one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_enable_deletion_protection.completionArgtypes = ['tritoninstance'];
module.exports = do_enable_deletion_protection;

View File

@ -0,0 +1,112 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance enable-firewall ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_enable_firewall(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument(s)'));
return;
}
var cli = this.top;
function wait(name, id, next) {
cli.tritonapi.cloudapi.waitForMachineFirewallEnabled({
id: id,
state: true
}, function (err, inst) {
if (err) {
next(err);
return;
}
assert.ok(inst.firewall_enabled, format(
'inst %s firewall_enabled not in expected state after '
+ 'waitForMachineFirewallEnabled', id));
console.log('Enabled firewall for instance "%s"', name);
next();
});
}
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: args,
func: function enableOne(name, nextInst) {
cli.tritonapi.enableInstanceFirewall({
id: name
}, function (err, fauxInst) {
if (err) {
nextInst(err);
return;
}
console.log('Enabling firewall for instance "%s"', name);
if (opts.wait) {
wait(name, fauxInst.id, nextInst);
} else {
nextInst();
}
});
}
}, function (err) {
cb(err);
});
});
}
do_enable_firewall.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the firewall to be enabled.'
}
];
do_enable_firewall.synopses = [
'{{name}} enable-firewall [OPTIONS] INST [INST ...]'
];
do_enable_firewall.help = [
'Enable the firewall of one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_enable_firewall.completionArgtypes = ['tritoninstance'];
module.exports = do_enable_firewall;

View File

@ -0,0 +1,113 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance fwrules ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../../common');
var errors = require('../../errors');
var COLUMNS_DEFAULT = 'shortid,enabled,global,rule';
var COLUMNS_LONG = 'id,enabled,global,rule,description';
var SORT_DEFAULT = 'rule';
function do_list(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('too many arguments: ' + args.join(' ')));
return;
}
var id = args[0];
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
tritonapi.listInstanceFirewallRules({
id: id
}, function onRules(err, rules) {
if (err) {
cb(err);
return;
}
if (opts.json) {
common.jsonStream(rules);
} else {
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_LONG;
}
columns = columns.toLowerCase().split(',');
var sort = opts.s.toLowerCase().split(',');
if (columns.indexOf('shortid') !== -1) {
rules.forEach(function (rule) {
rule.shortid = common.normShortId(rule.id);
});
}
tabula(rules, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] INST'];
do_list.help = [
'Show firewall rules applied to an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_list.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_list;

View File

@ -0,0 +1,45 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* `triton instance fwrule ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function InstanceFwruleCLI(parent) {
this.top = parent.top;
Cmdln.call(this, {
name: parent.name + ' fwrule',
desc: [
'List fwrules on Spearhead instances.'
].join('\n'),
helpOpts: {
minHelpCol: 24 /* line up with option help */
},
helpSubcmds: [
'help',
'list'
]
});
}
util.inherits(InstanceFwruleCLI, Cmdln);
// `triton instance fwrules` came first, so we'll hide this one.
InstanceFwruleCLI.hidden = true;
InstanceFwruleCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
InstanceFwruleCLI.prototype.do_list = require('./do_list');
module.exports = InstanceFwruleCLI;

View File

@ -0,0 +1,26 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* `triton instance fwrules ...` shortcut for
* `triton instance fwrule list ...`.
*/
function do_fwrules(subcmd, opts, args, callback) {
this.handlerFromSubcmd('fwrule').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
var do_fwrule_list = require('./do_fwrule/do_list');
do_fwrules.help = do_fwrule_list.help;
do_fwrules.options = do_fwrule_list.options;
do_fwrules.synopses = do_fwrule_list.synopses;
do_fwrules.completionArgtypes = do_fwrule_list.completionArgtypes;
module.exports = do_fwrules;

83
lib/do_instance/do_get.js Normal file
View File

@ -0,0 +1,83 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance get ...`
*/
var common = require('../common');
function do_get(subcmd, opts, args, cb) {
if (opts.help) {
return this.do_help('help', {}, [subcmd], cb);
} else if (args.length !== 1) {
return cb(new Error('invalid args: ' + args));
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
tritonapi.getInstance({
id: args[0],
credentials: opts.credentials
}, function onInst(err, inst) {
if (inst) {
if (opts.json) {
console.log(JSON.stringify(inst));
} else {
console.log(JSON.stringify(inst, null, 4));
}
}
cb(err);
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['credentials'],
type: 'bool',
help: 'Include generated credentials, in the "metadata.credentials" ' +
'field, if any. Typically used with "-j", though one can show ' +
'values with "-o metadata.credentials".'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} [OPTIONS] INST'];
do_get.help = [
/* BEGIN JSSTYLED */
'Get an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.',
'',
'A *deleted* instance may still respond with the instance object. In that',
'case a the instance will be print *and* an error will be raised.',
'',
'Currently this dumps prettified JSON by default. That might change',
'in the future. Use "-j" to explicitly get JSON output.'
/* END JSSTYLED */
].join('\n');
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get;

80
lib/do_instance/do_ip.js Normal file
View File

@ -0,0 +1,80 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance ip ...`
*/
var format = require('util').format;
var common = require('../common');
var errors = require('../errors');
function do_ip(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length === 0) {
cb(new errors.UsageError('missing INST argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('too many arguments: ' + args.join(' ')));
return;
}
var cli = this.top;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getInstance(args[0], function (err, inst) {
if (err) {
cb(err);
return;
}
if (!inst.primaryIp) {
cb(new errors.TritonError(format(
'primaryIp not found for instance "%s"', args[0])));
return;
}
console.log(inst.primaryIp);
cb();
});
});
}
do_ip.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_ip.synopses = ['{{name}} {{cmd}} INST'];
do_ip.help = [
/* BEGIN JSSTYLED */
'Print the primary IP of the given instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.',
'For example: ssh root@$(spearhead ip my-instance)'
].join('\n');
do_ip.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_ip;

230
lib/do_instance/do_list.js Normal file
View File

@ -0,0 +1,230 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton instance list ...`
*/
var format = require('util').format;
var tabula = require('tabula');
var vasync = require('vasync');
var common = require('../common');
/*
* Filters to be passed as query string args to /my/machines.
* See <https://apidocs.joyent.com/cloudapi/#ListMachines>.
*/
var validFilters = [
'brand', // Added in CloudAPI 8.0.0
'docker', // Added in CloudAPI 8.0.0
'image',
'memory',
'name',
'state',
// jsl:ignore
/^tag\./,
// jsl:end
'type'
];
// columns default without -o
var columnsDefault = 'shortid,name,img,state,flags,age';
// columns default with -l
var columnsDefaultLong
= 'id,name,img,brand,package,state,flags,primaryIp,created';
// sort default with -s
var sortDefault = 'created';
function do_list(subcmd, opts, args, callback) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
var log = self.top.log;
var columns = columnsDefault;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
var listOpts;
try {
listOpts = common.objFromKeyValueArgs(args, {
disableDotted: true,
validKeys: validFilters,
disableTypeConversions: true
});
} catch (e) {
callback(e);
return;
}
if (opts.credentials) {
listOpts.credentials = true;
}
var imgs = [];
var insts;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
vasync.parallel({funcs: [
function getTheImages(next) {
self.top.tritonapi.listImages({
state: 'all',
useCache: true
}, function (err, _imgs) {
if (err) {
if (err.statusCode === 403) {
/*
* This could be a authorization error due
* to RBAC on a subuser. We don't want to
* fail `triton inst ls` if the subuser
* can ListMachines, but not ListImages.
*/
log.debug(
err,
'authz error listing images for insts info');
next();
} else {
next(err);
}
} else {
imgs = _imgs;
next();
}
});
},
function getTheMachines(next) {
self.top.tritonapi.cloudapi.listMachines(
listOpts,
function (err, _insts) {
if (err) {
next(err);
} else {
insts = _insts;
next();
}
});
}
]}, function (err, results) {
/*
* Error handling: vasync.parallel's `err` is always a
* MultiError. We want to prefer the `getTheMachines` err,
* e.g. if both get a self-signed cert error.
*/
if (err) {
err = results.operations[1].err || err;
return callback(err);
}
// map "uuid" => "image_name"
var imgmap = {};
imgs.forEach(function (img) {
imgmap[img.id] = format('%s@%s', img.name, img.version);
});
// Add extra fields for nice output.
var now = new Date();
insts.forEach(function (inst) {
var created = new Date(inst.created);
inst.age = common.longAgo(created, now);
inst.img = imgmap[inst.image] ||
common.uuidToShortId(inst.image);
inst.shortid = inst.id.split('-', 1)[0];
var flags = [];
if (inst.brand === 'bhyve') flags.push('B');
if (inst.docker) flags.push('D');
if (inst.firewall_enabled) flags.push('F');
if (inst.brand === 'kvm') flags.push('K');
if (inst.deletion_protection) flags.push('P');
inst.flags = flags.length ? flags.join('') : undefined;
});
if (opts.json) {
common.jsonStream(insts);
} else {
tabula(insts, {
skipHeader: opts.H,
columns: columns,
sort: sort,
dottedLookup: true
});
}
callback();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['credentials'],
type: 'bool',
help: 'Include generated credentials, in the "metadata.credentials" ' +
'keys, if any. Typically used with "-j", though one can show ' +
'values with "-o metadata.credentials".'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: sortDefault
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS...]'];
do_list.help = [
/* BEGIN JSSTYLED */
'List instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Filters:',
' FIELD=VALUE Equality filter. Supported fields: brand, image,',
' memory, name, state, tag.TAGNAME, and type.',
' FIELD=true|false Boolean filter. Supported fields: docker (added in',
' CloudAPI 8.0.0).',
'',
'Fields (most are self explanatory, "*" indicates a field added client-side',
'for convenience):',
' shortid* A short ID prefix.',
' flags* Single letter flags summarizing some fields:',
' "B" the brand is "bhyve"',
' "D" docker instance',
' "F" firewall is enabled',
' "K" the brand is "kvm"',
' "P" deletion protected',
' age* Approximate time since created, e.g. 1y, 2w.',
' img* The image "name@version", if available, else its',
' "shortid".',
'',
'Examples:',
' {{name}} ls -Ho id state=running # IDs of running insts',
' {{name}} ls docker=true tag.foo=bar # Docker insts w/ "foo=bar" tag'
/* END JSSTYLED */
].join('\n');
do_list.aliases = ['ls'];
module.exports = do_list;

View File

@ -0,0 +1,211 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance nic create ...`
*/
var assert = require('assert-plus');
var common = require('../../common');
var errors = require('../../errors');
function do_create(subcmd, opts, args, cb) {
assert.optionalBool(opts.wait, 'opts.wait');
assert.optionalBool(opts.json, 'opts.json');
assert.optionalBool(opts.help, 'opts.help');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and NETWORK or INST and' +
' NICOPT=VALUE arguments'));
return;
}
var cli = this.top;
var netObj;
var netObjArgs = [];
var regularArgs = [];
var createOpts = {};
args.forEach(function forEachArg(arg) {
if (arg.indexOf('=') !== -1) {
netObjArgs.push(arg);
return;
}
regularArgs.push(arg);
});
if (netObjArgs.length > 0) {
if (regularArgs.length > 1) {
cb(new errors.UsageError('cannot specify INST and NETWORK when'
+ ' passing in ipv4 arguments'));
return;
}
if (regularArgs.length !== 1) {
cb(new errors.UsageError('missing INST argument'));
return;
}
try {
netObj = common.parseNicStr(netObjArgs);
} catch (err) {
cb(err);
return;
}
}
if (netObj) {
assert.array(regularArgs, 'regularArgs');
assert.equal(regularArgs.length, 1, 'instance uuid');
createOpts.id = regularArgs[0];
createOpts.network = netObj;
} else {
assert.array(args, 'args');
assert.equal(args.length, 2, 'INST and NETWORK');
createOpts.id = args[0];
createOpts.network = args[1];
}
function wait(instId, mac, next) {
assert.string(instId, 'instId');
assert.string(mac, 'mac');
assert.func(next, 'next');
var waiter = cli.tritonapi.waitForNicStates.bind(cli.tritonapi);
/*
* We request state running|stopped because net-agent is doing work to
* keep a NICs state in sync with the VMs state. If a user adds a NIC
* to a stopped instance the final state of the NIC should also be
* stopped.
*/
waiter({
id: instId,
mac: mac,
states: ['running', 'stopped']
}, next);
}
// same signature as wait(), but is a nop
function waitNop(instId, mac, next) {
assert.string(instId, 'instId');
assert.string(mac, 'mac');
assert.func(next, 'next');
next();
}
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.addNic(createOpts, function onAddNic(err, nic) {
if (err) {
cb(err);
return;
}
// If a NIC exists on the network already we will receive a 302
if (!nic) {
var errMsg = 'Instance already has a NIC on that network';
cb(new errors.TritonError(errMsg));
return;
}
// either wait or invoke a nop stub
var func = opts.wait ? wait : waitNop;
if (opts.wait && !opts.json) {
console.log('Creating NIC %s', nic.mac);
}
func(createOpts.id, nic.mac, function onWait(err2, createdNic) {
if (err2) {
cb(err2);
return;
}
var nicInfo = createdNic || nic;
if (opts.json) {
console.log(JSON.stringify(nicInfo));
} else {
console.log('Created NIC %s', nic.mac);
}
cb();
});
});
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the creation to complete.'
}
];
do_create.synopses = [
'{{name}} {{cmd}} [OPTIONS] INST NETWORK',
'{{name}} {{cmd}} [OPTIONS] INST NICOPT=VALUE [NICOPT=VALUE ...]'
];
do_create.help = [
'Create a NIC.',
'',
'{{usage}}',
'',
'{{options}}',
'INST is an instance id (full UUID), name, or short id,',
'and NETWORK is a network id (full UUID), name, or short id.',
'',
'NICOPTs are NIC options. The following NIC options are supported:',
'ipv4_uuid=<full network uuid> (required),' +
' and ipv4_ips=<a single IP string>.',
'',
'Be aware that adding NICs to an instance will cause that instance to',
'reboot.',
'',
'Example:',
' triton instance nic create --wait 22b75576 ca8aefb9',
' triton instance nic create 22b75576' +
' ipv4_uuid=651446a8-dab0-439e-a2c4-2c841ab07c51' +
' ipv4_ips=192.168.128.13'
].join('\n');
do_create.helpOpts = {
helpCol: 25
};
do_create.completionArgtypes = ['tritoninstance', 'tritonnic', 'none'];
module.exports = do_create;

View File

@ -0,0 +1,126 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance nic delete ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
function do_delete(subcmd, opts, args, cb) {
assert.object(opts, 'opts');
assert.optionalBool(opts.force, 'opts.force');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and MAC argument(s)'));
return;
} else if (args.length > 2) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var inst = args[0];
var mac = args[1];
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
confirm({mac: mac, force: opts.force}, function onConfirm(confirmErr) {
if (confirmErr) {
console.error('Aborting');
cb();
return;
}
cli.tritonapi.removeNic({
id: inst,
mac: mac
}, function onRemove(err) {
if (err) {
cb(err);
return;
}
console.log('Deleted NIC %s', mac);
cb();
});
});
});
}
// Request confirmation before deleting, unless --force flag given.
// If user declines, terminate early.
function confirm(opts, cb) {
assert.object(opts, 'opts');
assert.func(cb, 'cb');
if (opts.force) {
cb();
return;
}
common.promptYesNo({
msg: 'Delete NIC "' + opts.mac + '"? [y/n] '
}, function (answer) {
if (answer !== 'y') {
cb(new Error('Aborted NIC deletion'));
} else {
cb();
}
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Force removal.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} INST MAC'];
do_delete.help = [
'Remove a NIC from an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id (full UUID), name, or short id.',
'',
'Be aware that removing NICs from an instance will cause that instance to',
'reboot.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete;

View File

@ -0,0 +1,89 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance nic get ...`
*/
var assert = require('assert-plus');
var common = require('../../common');
var errors = require('../../errors');
function do_get(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and MAC arguments'));
return;
} else if (args.length > 2) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var inst = args[0];
var mac = args[1];
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getNic({id: inst, mac: mac}, function onNic(err, nic) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(nic));
} else {
console.log(JSON.stringify(nic, null, 4));
}
cb();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} INST MAC'];
do_get.help = [
'Show a specific NIC.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id (full UUID), name, or short id.'
].join('\n');
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get;

View File

@ -0,0 +1,154 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance nic list ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../../common');
var errors = require('../../errors');
var VALID_FILTERS = ['ip', 'mac', 'state', 'network', 'primary', 'gateway'];
var COLUMNS_DEFAULT = 'ip,mac,state,network';
var COLUMNS_DEFAULT_LONG = 'ip,mac,state,network,primary,gateway';
var SORT_DEFAULT = 'ip';
function do_list(subcmd, opts, args, cb) {
assert.array(args, 'args');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 1) {
cb(new errors.UsageError('missing INST argument'));
return;
}
var inst = args.shift();
try {
var filters = common.objFromKeyValueArgs(args, {
validKeys: VALID_FILTERS,
disableDotted: true
});
} catch (e) {
cb(e);
return;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.listNics({id: inst}, function onNics(err, nics) {
if (err) {
cb(err);
return;
}
// do filtering
Object.keys(filters).forEach(function filterByKey(key) {
var val = filters[key];
nics = nics.filter(function filterByNic(nic) {
return nic[key] === val;
});
});
if (opts.json) {
common.jsonStream(nics);
} else {
nics.forEach(function onNic(nic) {
nic.network = nic.network.split('-')[0];
nic.ip = nic.ip + '/' + convertCidrSuffix(nic.netmask);
});
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_DEFAULT_LONG;
}
columns = columns.split(',');
var sort = opts.s.split(',');
tabula(nics, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
function convertCidrSuffix(netmask) {
var bitmask = netmask.split('.').map(function (octet) {
return (+octet).toString(2);
}).join('');
var i = 0;
for (i = 0; i < bitmask.length; i++) {
if (bitmask[i] === '0')
break;
}
return i;
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
do_list.help = [
'Show all NICs on an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'',
'Where INST is an instance id (full UUID), name, or short id.',
'',
'Filters:',
' FIELD=<string> String filter. Supported fields: ip, mac, state,',
' network, netmask',
'',
'Filters are applied client-side (i.e. done by the triton command itself).'
].join('\n');
do_list.completionArgtypes = ['tritoninstance', 'none'];
do_list.aliases = ['ls'];
module.exports = do_list;

View File

@ -0,0 +1,50 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton inst nic ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function NicCLI(top) {
this.top = top.top;
Cmdln.call(this, {
name: top.name + ' nic',
desc: 'List and manage instance NICs.',
helpSubcmds: [
'help',
'list',
'get',
'create',
'delete'
],
helpOpts: {
minHelpCol: 23
}
});
}
util.inherits(NicCLI, Cmdln);
NicCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
NicCLI.prototype.do_list = require('./do_list');
NicCLI.prototype.do_create = require('./do_create');
NicCLI.prototype.do_get = require('./do_get');
NicCLI.prototype.do_delete = require('./do_delete');
module.exports = NicCLI;

View File

@ -0,0 +1,105 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton instance reboot ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_reboot(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length < 1) {
cb(new errors.UsageError('missing INST arg(s)'));
return;
}
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
var rebootErrs = [];
vasync.forEachParallel({
inputs: args,
func: function rebootOne(arg, nextInst) {
console.log('Rebooting instance %s', arg);
tritonapi.rebootInstance({
id: arg,
wait: opts.wait,
waitTimeout: opts.wait_timeout * 1000
}, function (rebootErr) {
if (rebootErr) {
rebootErrs.push(rebootErr);
console.log('Error rebooting instance %s: %s', arg,
rebootErr.message);
} else if (opts.wait) {
console.log('Rebooted instance %s', arg);
}
nextInst();
});
}
}, function doneReboots(err) {
assert.ok(!err, '"err" should be impossible as written');
if (rebootErrs.length === 1) {
cb(rebootErrs[0]);
} else if (rebootErrs.length > 1) {
cb(new errors.MultiError(rebootErrs));
} else {
cb();
}
});
});
}
do_reboot.synopses = ['{{name}} reboot [OPTIONS] INST [INST ...]'];
do_reboot.help = [
'Reboot one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_reboot.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait until the instance(s) have rebooted.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
}
];
do_reboot.completionArgtypes = ['tritoninstance'];
module.exports = do_reboot;

View File

@ -0,0 +1,88 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var common = require('../common');
var errors = require('../errors');
function do_rename(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length < 1) {
callback(new errors.UsageError('missing INST arg'));
return;
} else if (args.length < 2) {
callback(new errors.UsageError('missing NEWNAME arg'));
return;
}
var id = args[0];
var name = args[1];
console.log('Renaming instance %s to "%s"', id, name);
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
tritonapi.renameInstance({
id: id,
name: name,
wait: opts.wait,
waitTimeout: opts.wait_timeout * 1000
}, function (err) {
if (err) {
callback(err);
return;
}
console.log('Renamed instance %s to "%s"', id, name);
callback();
});
});
}
do_rename.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Block until renaming instance is complete.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
}
];
do_rename.synopses = ['{{name}} rename [OPTIONS] INST NEWNAME'];
do_rename.help = [
'Rename an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id',
'and "NEWNAME" is an instance name.',
'',
'Changing an instance name is asynchronous.',
'Use "--wait" to not return until the changes are completed.'
].join('\n');
do_rename.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_rename;

View File

@ -0,0 +1,88 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var common = require('../common');
var errors = require('../errors');
function do_resize(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length < 1) {
callback(new errors.UsageError('missing INST arg'));
return;
} else if (args.length < 2) {
callback(new errors.UsageError('missing PACKAGE arg'));
return;
}
var id = args[0];
var pkg = args[1];
console.log('Resizing instance %s to "%s"', id, pkg);
var tritonapi = this.top.tritonapi;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
tritonapi.resizeInstance({
id: id,
package: pkg,
wait: opts.wait,
waitTimeout: opts.wait_timeout * 1000
}, function (err) {
if (err) {
callback(err);
return;
}
console.log('Resized instance %s to "%s"', id, pkg);
callback();
});
});
}
do_resize.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Block until resizing instance is complete.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
}
];
do_resize.synopses = ['{{name}} resize [OPTIONS] INST PACKAGE'];
do_resize.help = [
'Resize an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id',
'and "PACKAGE" is a package name, id, or short id.',
'',
'Changing an instance package is asynchronous.',
'Use "--wait" to not return until the changes are completed.'
].join('\n');
do_resize.completionArgtypes = ['tritoninstance', 'tritonpackage', 'none'];
module.exports = do_resize;

View File

@ -0,0 +1,141 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton snapshot create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
function do_create(subcmd, opts, args, cb) {
assert.optionalString(opts.name, 'opts.name');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing <inst> argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var inst = args[0];
var cli = this.top;
var createOpts = {
userId: opts.userId,
id: inst
};
if (opts.name) {
createOpts.name = opts.name;
}
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function createSnapshot(ctx, next) {
ctx.start = Date.now();
cli.tritonapi.createInstanceSnapshot(createOpts,
function (err, snapshot, res) {
if (err) {
next(err);
return;
}
console.log('Creating snapshot %s of instance %s',
snapshot.name, createOpts.id);
ctx.name = snapshot.name;
ctx.instId = res.instId;
next();
});
},
function maybeWait(ctx, next) {
if (!opts.wait) {
return next();
}
var cloudapi = cli.tritonapi.cloudapi;
var waiter = cloudapi.waitForSnapshotStates.bind(cloudapi);
waiter({
id: ctx.instId,
name: ctx.name,
states: ['created', 'failed']
}, function (err, snap) {
if (err) {
return next(err);
}
if (opts.json) {
console.log(JSON.stringify(snap));
} else if (snap.state === 'created') {
var duration = Date.now() - ctx.start;
console.log('Created snapshot "%s" in %s', snap.name,
common.humanDurationFromMs(duration));
next();
} else {
next(new Error(format('Failed to create snapshot "%s"',
snap.name)));
}
});
}
]}, cb);
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['name', 'n'],
type: 'string',
helpArg: 'SNAPNAME',
help: 'An optional name for a snapshot.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the creation to complete.'
}
];
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] INST'];
do_create.help = [
'Create a snapshot of an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Snapshots do not work for instances of type "bhyve" or "kvm".'
].join('\n');
do_create.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_create;

View File

@ -0,0 +1,160 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton snapshot delete ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
function do_delete(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and SNAPNAME argument(s)'));
return;
}
var cli = this.top;
var inst = args[0];
var names = args.slice(1, args.length);
function wait(instId, name, startTime, next) {
var cloudapi = cli.tritonapi.cloudapi;
var waiter = cloudapi.waitForSnapshotStates.bind(cloudapi);
waiter({
id: instId,
name: name,
states: ['deleted']
}, function (err, snap) {
if (err) {
return next(err);
}
if (snap.state === 'deleted') {
var duration = Date.now() - startTime;
var durStr = common.humanDurationFromMs(duration);
console.log('Deleted snapshot "%s" in %s', name, durStr);
next();
} else {
// shouldn't get here, but...
next(new Error(format('Failed to delete snapshot "%s"', name)));
}
});
}
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function confirm(_, next) {
if (opts.force) {
return next();
}
var msg;
if (names.length === 1) {
msg = 'Delete snapshot "' + names[0] + '"? [y/n] ';
} else {
msg = format('Delete %d snapshots (%s)? [y/n] ',
names.length, names.join(', '));
}
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
} else {
next();
}
});
},
function deleteThem(_, next) {
var startTime = Date.now();
vasync.forEachParallel({
inputs: names,
func: function deleteOne(name, nextName) {
cli.tritonapi.deleteInstanceSnapshot({
id: inst,
name: name
}, function (err, res) {
if (err) {
nextName(err);
return;
}
var instId = res.instId;
var msg = 'Deleting snapshot "%s" of instance "%s"';
console.log(msg, name, instId);
if (opts.wait) {
wait(instId, name, startTime, nextName);
} else {
nextName();
}
});
}
}, next);
}
]}, function (err) {
if (err === true) {
err = null;
}
cb(err);
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation of delete.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the deletion to complete.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} [OPTIONS] INST SNAPNAME [SNAPNAME...]'];
do_delete.help = [
'Remove a snapshot from an instance.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
do_delete.aliases = ['rm'];
// TODO: When have 'tritonsnapshot' completion, then use this:
// do_get.completionArgtypes = ['tritoninstance', 'tritonsnapshot'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete;

View File

@ -0,0 +1,91 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton snapshot get ...`
*/
var assert = require('assert-plus');
var common = require('../../common');
var errors = require('../../errors');
function do_get(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and/or SNAPNAME arguments'));
return;
} else if (args.length > 2) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var id = args[0];
var name = args[1];
var cli = this.top;
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getInstanceSnapshot({
id: id,
name: name
}, function onSnapshot(err, snapshot) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(snapshot));
} else {
console.log(JSON.stringify(snapshot, null, 4));
}
cb();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} [OPTIONS] INST SNAPNAME'];
do_get.help = [
'Show a specific snapshot of an instance.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
// TODO: When have 'tritonsnapshot' completion, then use this:
// do_get.completionArgtypes = ['tritoninstance', 'tritonsnapshot', 'none'];
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get;

View File

@ -0,0 +1,107 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton snapshot list ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../../common');
var errors = require('../../errors');
var COLUMNS_DEFAULT = 'name,state,created';
var SORT_DEFAULT = 'name';
function do_list(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing INST argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var cli = this.top;
var machineId = args[0];
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.listInstanceSnapshots({
id: machineId
}, function onSnapshots(err, snapshots) {
if (err) {
cb(err);
return;
}
if (opts.json) {
common.jsonStream(snapshots);
} else {
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_DEFAULT;
}
columns = columns.split(',');
var sort = opts.s.split(',');
tabula(snapshots, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] INST'];
do_list.help = [
'Show all of an instance\'s snapshots.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
do_list.completionArgtypes = ['tritoninstance', 'none'];
do_list.aliases = ['ls'];
module.exports = do_list;

View File

@ -0,0 +1,49 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton snapshot ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function SnapshotCLI(top) {
this.top = top.top;
Cmdln.call(this, {
name: top.name + ' snapshot',
desc: 'List, get, create and delete Spearhead instance snapshots.',
helpSubcmds: [
'help',
'create',
'list',
'get',
'delete'
],
helpBody: 'Instances can be rolled back to a snapshot using\n' +
'`spearhead instance start --snapshot=SNAPNAME`.'
});
}
util.inherits(SnapshotCLI, Cmdln);
SnapshotCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
SnapshotCLI.prototype.do_create = require('./do_create');
SnapshotCLI.prototype.do_get = require('./do_get');
SnapshotCLI.prototype.do_list = require('./do_list');
SnapshotCLI.prototype.do_delete = require('./do_delete');
module.exports = SnapshotCLI;

View File

@ -0,0 +1,32 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton instance snapshots ...` shortcut for
* `triton instance snapshot list ...`.
*/
var targ = require('./do_snapshot/do_list');
function do_snapshots(subcmd, opts, args, callback) {
this.handlerFromSubcmd('snapshot').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
do_snapshots.help = 'A shortcut for "spearhead instance snapshot list".\n' +
targ.help;
do_snapshots.synopses = targ.synopses;
do_snapshots.options = targ.options;
do_snapshots.completionArgtypes = targ.completionArgtypes;
do_snapshots.hidden = true;
module.exports = do_snapshots;

370
lib/do_instance/do_ssh.js Normal file
View File

@ -0,0 +1,370 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*
* `triton instance ssh ...`
*/
var assert = require('assert-plus');
var path = require('path');
var spawn = require('child_process').spawn;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
/*
* The tag "tritoncli.ssh.ip" may be set to an IP address that belongs to the
* instance but which is not the primary IP. If set, we will use that IP
* address for the SSH connection instead of the primary IP.
*/
var TAG_SSH_IP = 'tritoncli.ssh.ip';
/*
* The tag "tritoncli.ssh.proxy" may be set to either the name or the UUID of
* another instance in this account. If set, we will use the "ProxyJump"
* feature of SSH to tunnel through the SSH server on that host. This is
* useful when exposing a single zone to the Internet while keeping the rest of
* your infrastructure on a private fabric.
*/
var TAG_SSH_PROXY = 'tritoncli.ssh.proxy';
/*
* The tag "tritoncli.ssh.proxyuser" may be set on the instance used as an SSH
* proxy. If set, we will use this value when making the proxy connection
* (i.e., it will be passed via the "ProxyJump" option). If not set, the
* default user selection behaviour applies.
*/
var TAG_SSH_PROXY_USER = 'tritoncli.ssh.proxyuser';
function do_ssh(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length === 0) {
callback(new errors.UsageError('missing INST arg'));
return;
}
var id = args.shift();
var user;
var overrideUser = false;
var i = id.indexOf('@');
if (i >= 0) {
user = id.substr(0, i);
id = id.substr(i + 1);
overrideUser = true;
}
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function getInstanceIp(ctx, next) {
ctx.cli.tritonapi.getInstance(id, function (err, inst) {
if (err) {
next(err);
return;
}
ctx.inst = inst;
if (inst.tags && inst.tags[TAG_SSH_IP]) {
ctx.ip = inst.tags[TAG_SSH_IP];
if (!inst.ips || inst.ips.indexOf(ctx.ip) === -1) {
next(new Error('IP address ' + ctx.ip + ' not ' +
'attached to the instance'));
return;
}
} else {
ctx.ip = inst.primaryIp;
}
if (!ctx.ip) {
next(new Error('IP address not found for instance'));
return;
}
next();
});
},
function getInstanceBastionIp(ctx, next) {
if (opts.no_proxy) {
setImmediate(next);
return;
}
if (!ctx.inst.tags || !ctx.inst.tags[TAG_SSH_PROXY]) {
setImmediate(next);
return;
}
ctx.cli.tritonapi.getInstance(ctx.inst.tags[TAG_SSH_PROXY],
function (err, proxy) {
if (err) {
next(err);
return;
}
if (proxy.tags && proxy.tags[TAG_SSH_IP]) {
ctx.proxyIp = proxy.tags[TAG_SSH_IP];
if (!proxy.ips || proxy.ips.indexOf(ctx.proxyIp) === -1) {
next(new Error('IP address ' + ctx.proxyIp + ' not ' +
'attached to the instance'));
return;
}
} else {
ctx.proxyIp = proxy.primaryIp;
}
ctx.proxyImage = proxy.image;
/*
* Selecting the right user to use for the proxy connection is
* somewhat nuanced, in order to allow for various useful
* configurations. We wish to enable the following cases:
*
* 1. The least sophisticated configuration; i.e., using two
* instances (the target instance and the proxy instnace)
* with the default "root" (or, e.g., "ubuntu") account
* and smartlogin or authorized_keys metadata for SSH key
* management.
*
* 2. The user has set up their own accounts (e.g., "roberta")
* in all of their instances and does their own SSH key
* management. They connect with:
*
* triton inst ssh roberta@instance
*
* In this case we will use "roberta" for both the proxy
* and the target instance. This means a user provided on
* the command line will override the per-image default
* user (e.g., "root" or "ubuntu") -- if the user wants to
* retain the default account for the proxy, they should
* use case 3 below.
*
* 3. The user has set up their own accounts in the target
* instance (e.g., "felicity"), but the proxy instance is
* using a single specific account that should be used by
* all users in the organisation (e.g., "partyline"). In
* this case, we want the user to be able to specify the
* global proxy account setting as a tag on the proxy
* instance, so that for:
*
* triton inst ssh felicity@instance
*
* ... we will use "-o ProxyJump partyline@proxy" but
* still use "felicity" for the target connection. This
* last case requires the proxy user tag (if set) to
* override a user provided on the command line.
*/
if (proxy.tags && proxy.tags[TAG_SSH_PROXY_USER]) {
ctx.proxyUser = proxy.tags[TAG_SSH_PROXY_USER];
}
if (!ctx.proxyIp) {
next(new Error('IP address not found for proxy instance'));
return;
}
next();
});
},
function getUser(ctx, next) {
if (overrideUser) {
assert.string(user, 'user');
next();
return;
}
ctx.cli.tritonapi.getImage({
name: ctx.inst.image,
useCache: true
}, function (getImageErr, image) {
if (getImageErr) {
next(getImageErr);
return;
}
/*
* This is a convention as seen on Joyent's "ubuntu-certified"
* KVM images.
*/
if (image.tags && image.tags.default_user) {
user = image.tags.default_user;
} else {
user = 'root';
}
next();
});
},
function getBastionUser(ctx, next) {
if (!ctx.proxyImage || ctx.proxyUser) {
/*
* If there is no image for the proxy host, or an override user
* was already provided in the tags of the proxy instance
* itself, we don't need to look up the default user.
*/
next();
return;
}
if (overrideUser) {
/*
* A user was provided on the command line, but no user
* override tag was present on the proxy instance. To enable
* use case 2 (see comments above) we'll prefer this user over
* the image default.
*/
assert.string(user, 'user');
ctx.proxyUser = user;
next();
return;
}
ctx.cli.tritonapi.getImage({
name: ctx.proxyImage,
useCache: true
}, function (getImageErr, image) {
if (getImageErr) {
next(getImageErr);
return;
}
/*
* This is a convention as seen on Joyent's "ubuntu-certified"
* KVM images.
*/
assert.ok(!ctx.proxyUser, 'proxy user set twice');
if (image.tags && image.tags.default_user) {
ctx.proxyUser = image.tags.default_user;
} else {
ctx.proxyUser = 'root';
}
next();
});
},
function doSsh(ctx, next) {
args = ['-l', user, ctx.ip].concat(args);
if (ctx.proxyIp) {
assert.string(ctx.proxyUser, 'ctx.proxyUser');
args = [
'-o', 'ProxyJump=' + ctx.proxyUser + '@' + ctx.proxyIp
].concat(args);
}
/*
* By default we disable ControlMaster (aka mux, aka SSH
* connection multiplexing) because of
* https://github.com/joyent/node-triton/issues/52
*/
if (!opts.no_disable_mux) {
/*
* A simple `-o ControlMaster=no` doesn't work. With
* just that option, a `ControlPath` option (from
* ~/.ssh/config) will still be used if it exists. Our
* hack is to set a ControlPath we know should not
* exist. Using '/dev/null' wasn't a good alternative
* because `ssh` tries "$ControlPath.$somerandomnum"
* and also because Windows.
*/
var nullSshControlPath = path.resolve(
ctx.cli.tritonapi.config._configDir, 'tmp',
'nullSshControlPath');
args = [
'-o', 'ControlMaster=no',
'-o', 'ControlPath='+nullSshControlPath
].concat(args);
}
ctx.cli.log.info({args: args}, 'forking ssh');
var child = spawn('ssh', args, {stdio: 'inherit'});
child.on('close', function (code) {
/*
* Once node 0.10 support is dropped we could instead:
* process.exitCode = code;
* callback();
*/
process.exit(code);
/* process.exit does not return so no need to call next(). */
});
}
]}, callback);
}
do_ssh.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['no-proxy'],
type: 'bool',
help: 'Disable SSH proxy support (ignore "tritoncli.ssh.proxy" tag)'
}
];
do_ssh.synopses = ['{{name}} ssh [-h] [USER@]INST [SSH-ARGUMENTS]'];
do_ssh.help = [
/* BEGIN JSSTYLED */
'SSH to the primary IP of an instance',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is the name, id, or short id of an instance. Note that',
'the INST argument must come before any `ssh` options or arguments.',
'Where USER is the username to use on the instance. If not specified,',
'the instance\'s image is inspected for the default_user tag.',
'If USER is not specified and the default_user tag is not set, the user',
'is assumed to be \"root\".',
'',
'The "tritoncli.ssh.proxy" tag on the target instance may be set to',
'the name or the UUID of another instance through which to proxy this',
'SSH connection. If set, the primary IP of the proxy instance will be',
'loaded and passed to SSH via the ProxyJump option. The --no-proxy',
'flag can be used to ignore the tag and force a direct connection.',
'',
'For example, to proxy connections to zone "narnia" through "wardrobe":',
' triton instance tag set narnia tritoncli.ssh.proxy=wardrobe',
'',
'The "tritoncli.ssh.ip" tag on the target instance may be set to the',
'IP address to use for SSH connections. This may be useful if the',
'primary IP address is not available for SSH connections. This address',
'must be set to one of the IP addresses attached to the instance.',
'',
'The "tritoncli.ssh.proxyuser" tag on the proxy instance may be set to',
'the user account that should be used for the proxy connection (i.e., via',
'the SSH ProxyJump option). This is useful when all users of the proxy',
'instance should use a special common account, and will override the USER',
'value (if one is provided) for the SSH connection to the target instance.',
'',
'There is a known issue with SSH connection multiplexing (a.k.a. ',
'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`',
'is spawned with options disabling ControlMaster. See ',
'<https://github.com/joyent/node-triton/issues/52> for details. If you ',
'want to use ControlMaster, an alternative is:',
' ssh root@$(spearhead ip INST)'
/* END JSSTYLED */
].join('\n');
do_ssh.interspersedOptions = false;
// Use 'file' to fallback to the default bash completion... even though 'file'
// isn't quite right.
do_ssh.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_ssh;

View File

@ -0,0 +1,17 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance start ...`
*/
var gen_do_ACTION = require('./gen_do_ACTION');
var do_start = gen_do_ACTION({action: 'start'});
module.exports = do_start;

View File

@ -0,0 +1,17 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance stop ...`
*/
var gen_do_ACTION = require('./gen_do_ACTION');
var do_stop = gen_do_ACTION({action: 'stop'});
module.exports = do_stop;

View File

@ -0,0 +1,122 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag delete ...`
*/
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
function do_delete(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length < 1) {
cb(new errors.UsageError('incorrect number of args'));
return;
} else if (args.length > 1 && opts.all) {
cb(new errors.UsageError('cannot specify both tag names and --all'));
return;
}
var waitTimeoutMs = opts.wait_timeout * 1000; /* seconds to ms */
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
if (opts.all) {
self.top.tritonapi.deleteAllInstanceTags({
id: args[0],
wait: opts.wait,
waitTimeout: waitTimeoutMs
}, function (err) {
console.log('Deleted all tags on instance %s', args[0]);
cb(err);
});
} else {
// Uniq'ify the given names.
var names = {};
args.slice(1).forEach(function (arg) { names[arg] = true; });
names = Object.keys(names);
// TODO: Instead of waiting for each delete, let's delete
// them all then wait for the set.
vasync.forEachPipeline({
inputs: names,
func: function deleteOne(name, next) {
self.top.tritonapi.deleteInstanceTag({
id: args[0],
tag: name,
wait: opts.wait,
waitTimeout: waitTimeoutMs
}, function (err) {
if (!err) {
console.log('Deleted tag %s on instance %s',
name, args[0]);
}
next(err);
});
}
}, cb);
}
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['all', 'a'],
type: 'bool',
help: 'Remove all tags on this instance.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the tag changes to be applied.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
}
];
do_delete.synopses = [
'{{name}} {{cmd}} INST [NAME ...]',
'{{name}} {{cmd}} --all INST # delete all tags'
];
do_delete.help = [
'Delete one or more instance tags.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id, name, or shortid and NAME is a tag name.',
'',
'Changing instance tags is asynchronous. Use "--wait" to not return until',
'the changes are completed.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete;

View File

@ -0,0 +1,77 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag get ...`
*/
var common = require('../../common');
var errors = require('../../errors');
function do_get(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError('incorrect number of args'));
return;
}
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
self.top.tritonapi.getInstanceTag({
id: args[0],
tag: args[1]
}, function (err, value) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(value));
} else {
console.log(value);
}
cb();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} INST NAME'];
do_get.help = [
'Get an instance tag.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id, name, or shortid and NAME is a tag name.'
].join('\n');
// TODO: When have 'tritoninstancetag' completion, add that in.
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get;

View File

@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag list ...`
*/
var common = require('../../common');
var errors = require('../../errors');
function do_list(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 1) {
cb(new errors.UsageError('incorrect number of args'));
return;
}
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
self.top.tritonapi.listInstanceTags(
{id: args[0]}, function (err, tags) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(tags));
} else {
console.log(JSON.stringify(tags, null, 4));
}
cb();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_list.synopses = ['{{name}} {{cmd}} INST'];
do_list.help = [
'List instance tags.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id, name, or shortid.',
'',
'Note: Currently this dumps prettified JSON by default. That might change',
'in the future. Use "-j" to explicitly get JSON output.'
].join('\n');
do_list.aliases = ['ls'];
do_list.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_list;

View File

@ -0,0 +1,137 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag replace-all ...`
*/
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
var mat = require('../../metadataandtags');
function do_replace_all(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length < 1) {
cb(new errors.UsageError('incorrect number of args'));
return;
}
var log = self.log;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function gatherTags(ctx, next) {
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
if (err) {
next(err);
return;
}
log.trace({tags: tags || '<none>'},
'tags loaded from opts and args');
ctx.tags = tags;
next();
});
},
function replaceAway(ctx, next) {
if (!ctx.tags) {
next(new errors.UsageError('no tags were provided'));
return;
}
self.top.tritonapi.replaceAllInstanceTags({
id: args[0],
tags: ctx.tags,
wait: opts.wait,
waitTimeout: opts.wait_timeout * 1000 /* seconds to ms */
}, function (err, updatedTags) {
if (err) {
cb(err);
return;
}
if (!opts.quiet) {
if (opts.json) {
console.log(JSON.stringify(updatedTags));
} else {
console.log(JSON.stringify(updatedTags, null, 4));
}
}
cb();
});
}
]}, cb);
}
do_replace_all.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['file', 'f'],
type: 'arrayOfString',
helpArg: 'FILE',
help: 'Load tag name/value pairs from the given file path. '
+ 'The file may contain a JSON object or a file with "NAME=VALUE" '
+ 'pairs, one per line. This option can be used multiple times.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the tag changes to be applied.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
},
{
names: ['quiet', 'q'],
type: 'bool',
help: 'Quieter output. Specifically do not dump the updated set of '
+ 'tags on successful completion.'
}
];
do_replace_all.synopses = [
'{{name}} {{cmd}} INST [NAME=VALUE ...]',
'{{name}} {{cmd}} INST -f FILE # tags from file'
];
do_replace_all.help = [
'Replace all tags on the given instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id, name, or shortid; NAME is a tag name;',
'and VALUE is a tag value (bool and numeric "value" are converted to ',
'that type).',
'',
'Currently this dumps prettified JSON by default. That might change in the',
'future. Use "-j" to explicitly get JSON output.',
'',
'Changing instance tags is asynchronous. Use "--wait" to not return until',
'the changes are completed.'
].join('\n');
do_replace_all.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_replace_all;

View File

@ -0,0 +1,140 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag set ...`
*/
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
var mat = require('../../metadataandtags');
function do_set(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length < 1) {
cb(new errors.UsageError('incorrect number of args'));
return;
}
var log = self.log;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function gatherTags(ctx, next) {
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
if (err) {
next(err);
return;
}
log.trace({tags: tags || '<none>'},
'tags loaded from opts and args');
ctx.tags = tags;
next();
});
},
function setMachineTags(ctx, next) {
if (!ctx.tags) {
log.trace('no tags to set');
next();
return;
}
self.top.tritonapi.setInstanceTags({
id: args[0],
tags: ctx.tags,
wait: opts.wait,
waitTimeout: opts.wait_timeout * 1000 /* seconds to ms */
}, function (err, updatedTags) {
if (err) {
cb(err);
return;
}
if (!opts.quiet) {
if (opts.json) {
console.log(JSON.stringify(updatedTags));
} else {
console.log(JSON.stringify(updatedTags, null, 4));
}
}
cb();
});
}
]}, cb);
}
do_set.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['file', 'f'],
type: 'arrayOfString',
helpArg: 'FILE',
help: 'Load tag name/value pairs from the given file path. '
+ 'The file may contain a JSON object or a file with "NAME=VALUE" '
+ 'pairs, one per line. This option can be used multiple times.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the tag changes to be applied.'
},
{
names: ['wait-timeout'],
type: 'positiveInteger',
default: 120,
help: 'The number of seconds to wait before timing out with an error. '
+ 'The default is 120 seconds.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
},
{
names: ['quiet', 'q'],
type: 'bool',
help: 'Quieter output. Specifically do not dump the updated set of '
+ 'tags on successful completion.'
}
];
do_set.synopses = [
'{{name}} set INST [NAME=VALUE ...]',
'{{name}} set INST -f FILE # tags from file'
];
do_set.help = [
/* BEGIN JSSTYLED */
'Set one or more instance tags.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id, name, or shortid; NAME is a tag name;',
'and VALUE is a tag value (bool and numeric "value" are converted to ',
'that type).',
'',
'Currently this dumps prettified JSON by default. That might change in the',
'future. Use "-j" to explicitly get JSON output.',
'',
'Changing instance tags is asynchronous. Use "--wait" to not return until',
'the changes are completed.'
/* END JSSTYLED */
].join('\n');
do_set.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_set;

View File

@ -0,0 +1,54 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance tag ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function InstanceTagCLI(parent) {
this.top = parent.top;
Cmdln.call(this, {
name: parent.name + ' tag',
/* BEGIN JSSTYLED */
desc: [
'List, get, set and delete tags on Spearhead instances.'
].join('\n'),
/* END JSSTYLED */
helpOpts: {
minHelpCol: 24 /* line up with option help */
},
helpSubcmds: [
'help',
'list',
'get',
'set',
'replace-all',
'delete'
]
});
}
util.inherits(InstanceTagCLI, Cmdln);
InstanceTagCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
InstanceTagCLI.prototype.do_list = require('./do_list');
InstanceTagCLI.prototype.do_get = require('./do_get');
InstanceTagCLI.prototype.do_set = require('./do_set');
InstanceTagCLI.prototype.do_replace_all = require('./do_replace_all');
InstanceTagCLI.prototype.do_delete = require('./do_delete');
module.exports = InstanceTagCLI;

View File

@ -0,0 +1,30 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton instance tags ...` shortcut for `triton instance tag list ...`.
*/
var targ = require('./do_tag/do_list');
function do_tags(subcmd, opts, args, callback) {
this.handlerFromSubcmd('tag').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
do_tags.help = 'A shortcut for "spearhead instance tag list".\n' + targ.help;
do_tags.synopses = targ.synopses;
do_tags.options = targ.options;
do_tags.completionArgtypes = targ.completionArgtypes;
do_tags.hidden = true;
module.exports = do_tags;

147
lib/do_instance/do_wait.js Normal file
View File

@ -0,0 +1,147 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* `triton instance wait ...`
*/
var vasync = require('vasync');
var common = require('../common');
var distractions = require('../distractions');
var errors = require('../errors');
function do_wait(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
return this.do_help('help', {}, [subcmd], cb);
} else if (args.length < 1) {
return cb(new errors.UsageError('missing INST arg(s)'));
}
var ids = args;
var states = [];
opts.states.forEach(function (s) {
/* JSSTYLED */
states = states.concat(s.trim().split(/\s*,\s*/g));
});
var distraction;
var done = 0;
var instFromId = {};
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function getInsts(_, next) {
vasync.forEachParallel({
inputs: ids,
func: function getInst(id, nextInst) {
self.top.tritonapi.getInstance(id, function (err, inst) {
if (err) {
return nextInst(err);
}
if (states.indexOf(inst.state) !== -1) {
console.log('%d/%d: Instance %s (%s) already %s',
++done, ids.length, inst.name, id, inst.state);
} else {
instFromId[inst.id] = inst;
}
nextInst();
});
}
}, next);
},
function waitForInsts(_, next) {
var idsToWaitFor = Object.keys(instFromId);
if (idsToWaitFor.length === 0) {
return next();
}
if (idsToWaitFor.length === 1) {
var inst2 = instFromId[idsToWaitFor[0]];
console.log(
'Waiting for instance %s (%s) to enter state (states: %s)',
inst2.name, inst2.id, states.join(', '));
} else {
console.log(
'Waiting for %d instances to enter state (states: %s)',
idsToWaitFor.length, states.join(', '));
}
/*
* TODO: need BigSpinner.log first.
* TODO: Also when adding a spinner, we need an equiv option to
* `triton create -wwww` to trigger the spinner (and size). By
* default: no spinner.
*/
if (false &&
process.stderr.isTTY)
{
distraction = distractions.createDistraction();
}
vasync.forEachParallel({
inputs: idsToWaitFor,
func: function waitForInst(id, nextInst) {
self.top.tritonapi.cloudapi.waitForMachineStates({
id: id,
states: states
}, function (err, inst, res) {
if (err) {
return nextInst(err);
}
console.log('%d/%d: Instance %s (%s) moved to state %s',
++done, ids.length, inst.name, inst.id, inst.state);
nextInst();
});
}
}, next);
}
]}, function (err) {
if (distraction) {
distraction.destroy();
}
cb(err);
});
}
do_wait.synopses = ['{{name}} {{cmd}} [-s STATES] INST [INST ...]'];
do_wait.help = [
/* BEGIN JSSTYLED */
'Wait on instances changing state.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id; and "STATES" is a',
'comma-separated list of target instance states, by default "running,failed".',
'In other words, "spearhead inst wait foo0" will wait for instance "foo0" to',
'complete provisioning.'
/* END JSSTYLED */
].join('\n');
do_wait.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['states', 's'],
type: 'arrayOfString',
default: ['running', 'failed'],
helpArg: 'STATES',
help: 'Instance states on which to wait. Default is "running,failed". '
+ 'Values can be comma-separated or multiple uses of the option.'
}
];
do_wait.completionArgtypes = ['tritoninstance'];
module.exports = do_wait;

View File

@ -0,0 +1,204 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2016 Joyent, Inc.
*
* Shared support for:
* `triton instance start ...`
* `triton instance stop ...`
* `triton instance delete ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function perror(err) {
console.error('error: %s', err.message);
}
function gen_do_ACTION(opts) {
assert.object(opts, 'opts');
assert.string(opts.action, 'opts.action');
assert.optionalArrayOfString(opts.aliases, 'opts.aliases');
var action = opts.action;
assert.ok(['start', 'stop', 'delete'].indexOf(action) >= 0,
'invalid action');
function do_ACTION(subcmd, _opts, args, callback) {
return _doTheAction.call(this, action, subcmd, _opts, args, callback);
}
do_ACTION.name = 'do_' + action;
if (opts.aliases) {
do_ACTION.aliases = opts.aliases;
}
do_ACTION.synopses = ['{{name}} ' + action + ' [OPTIONS] INST [INST ...]'];
do_ACTION.help = [
common.capitalize(action) + ' one or more instances.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "INST" is an instance name, id, or short id.'
].join('\n');
do_ACTION.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Block until instance state indicates the action is complete.'
}
];
do_ACTION.completionArgtypes = ['tritoninstance'];
if (action === 'start') {
do_ACTION.options.push({
names: ['snapshot'],
type: 'string',
help: 'Name of snapshot with which to start the instance.',
helpArg: 'SNAPNAME'
});
}
return do_ACTION;
}
function _doTheAction(action, subcmd, opts, args, callback) {
var self = this;
var command, state;
switch (action) {
case 'start':
command = opts.snapshot ? 'startMachineFromSnapshot' :
'startMachine';
state = 'running';
break;
case 'stop':
command = 'stopMachine';
state = 'stopped';
break;
case 'delete':
command = 'deleteMachine';
state = 'deleted';
break;
default:
callback(new Error('unknown action: ' + action));
break;
}
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length < 1) {
callback(new errors.UsageError('missing INST arg(s)'));
return;
}
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
if (setupErr) {
callback(setupErr);
return;
}
_doOnEachInstance(self, action, command, state, args, opts, callback);
});
}
function _doOnEachInstance(self, action, command, state, instances,
opts, callback) {
var now = Date.now();
vasync.forEachParallel({
func: function (arg, cb) {
var alias, uuid;
if (common.isUUID(arg)) {
uuid = arg;
done();
} else {
self.top.tritonapi.getInstance(arg, function (err, inst) {
if (err) {
perror(err);
cb(err);
return;
}
alias = arg;
uuid = inst.id;
done();
});
}
// called when "uuid" is set
function done() {
var cOpts = uuid;
if (command === 'startMachineFromSnapshot') {
cOpts = { id: uuid, name: opts.snapshot };
}
self.top.tritonapi.cloudapi[command](cOpts,
function (err, body, res) {
if (err) {
perror(err);
cb(err);
return;
}
if (!opts.wait) {
if (alias)
console.log('%s (async) instance %s (%s)',
common.capitalize(action), alias, uuid);
else
console.log('%s (async) instance %s',
common.capitalize(action), uuid);
cb();
return;
}
self.top.tritonapi.cloudapi.waitForMachineStates({
id: uuid,
states: [state]
}, function (err2, inst2, res2) {
if (action === 'delete' &&
res2 && res2.statusCode === 410) {
// This is success, fall through to bottom.
/* jsl:pass */
} else if (err2) {
perror(err2);
cb(err2);
return;
}
var dur = common.humanDurationFromMs(Date.now() - now);
if (alias)
console.log('%s instance %s (%s, %s)',
common.capitalize(action), alias, uuid, dur);
else
console.log('%s instance %s (%s)',
common.capitalize(action), uuid, dur);
cb();
});
});
}
},
inputs: instances
}, function (err, results) {
var e = err ? (new Error('command failure')) : null;
callback(e);
});
}
module.exports = gen_do_ACTION;

101
lib/do_instance/index.js Normal file
View File

@ -0,0 +1,101 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2018 Joyent, Inc.
*
* `triton instance ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function InstanceCLI(top) {
this.top = top;
Cmdln.call(this, {
name: top.name + ' instance',
/* BEGIN JSSTYLED */
desc: [
'List and manage Spearhead instances.'
].join('\n'),
/* END JSSTYLED */
helpOpts: {
minHelpCol: 24 /* line up with option help */
},
helpSubcmds: [
'help',
'list',
'get',
'create',
'delete',
'resize',
'rename',
{ group: '' },
'start',
'stop',
'reboot',
{ group: '' },
'fwrules',
'enable-firewall',
'disable-firewall',
{ group: '' },
'enable-deletion-protection',
'disable-deletion-protection',
{ group: '' },
'ssh',
'ip',
'wait',
'audit',
'nic',
'snapshot',
'tag'
]
});
}
util.inherits(InstanceCLI, Cmdln);
InstanceCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
InstanceCLI.prototype.do_list = require('./do_list');
InstanceCLI.prototype.do_get = require('./do_get');
InstanceCLI.prototype.do_create = require('./do_create');
InstanceCLI.prototype.do_delete = require('./do_delete');
InstanceCLI.prototype.do_resize = require('./do_resize');
InstanceCLI.prototype.do_rename = require('./do_rename');
InstanceCLI.prototype.do_start = require('./do_start');
InstanceCLI.prototype.do_stop = require('./do_stop');
InstanceCLI.prototype.do_reboot = require('./do_reboot');
InstanceCLI.prototype.do_fwrule = require('./do_fwrule');
InstanceCLI.prototype.do_fwrules = require('./do_fwrules');
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
InstanceCLI.prototype.do_enable_deletion_protection =
require('./do_enable_deletion_protection');
InstanceCLI.prototype.do_disable_deletion_protection =
require('./do_disable_deletion_protection');
InstanceCLI.prototype.do_ssh = require('./do_ssh');
InstanceCLI.prototype.do_ip = require('./do_ip');
InstanceCLI.prototype.do_wait = require('./do_wait');
InstanceCLI.prototype.do_audit = require('./do_audit');
InstanceCLI.prototype.do_nic = require('./do_nic');
InstanceCLI.prototype.do_snapshot = require('./do_snapshot');
InstanceCLI.prototype.do_snapshots = require('./do_snapshots');
InstanceCLI.prototype.do_tag = require('./do_tag');
InstanceCLI.prototype.do_tags = require('./do_tags');
InstanceCLI.aliases = ['inst'];
module.exports = InstanceCLI;

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2015 Joyent Inc. All rights reserved.
*
* `triton instance-audit ...`
*/
var format = require('util').format;
var tabula = require('tabula');
var errors = require('./errors');
function do_instance_audit(subcmd, opts, args, callback) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
//XXX Support multiple machines.
return callback(new Error('too many args: ' + args));
}
var id = args[0];
this.sdc.machineAudit({machine: id}, function (err, audit, dc) {
if (err) {
return callback(err);
}
for (var i = 0; i < audit.length; i++) {
audit[i].dc = dc;
}
if (opts.json) {
p(JSON.stringify(audit, null, 4));
} else {
return callback(new error.InternalError("tabular output for audit NYI")); // XXX
//common.tabulate(audit, {
// columns: 'dc,id,name,state,created',
// sort: 'created',
// validFields: 'dc,id,name,type,state,image,package,memory,'
// + 'disk,created,updated,compute_node,primaryIp'
//});
}
callback();
});
};
do_instance_audit.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_instance_audit.help = (
'List instance actions.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} instance-audit <machine>\n'
+ '\n'
+ '{{options}}'
);
module.exports = do_instance_audit;

View File

@ -1,128 +1,30 @@
/*
* Copyright 2015 Joyent Inc.
*
* `triton instances ...`
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var tabula = require('tabula');
/*
* Copyright 2017 Joyent, Inc.
*
* `triton instances ...` bwcompat shortcut for `triton instance list ...`.
*/
var common = require('./common');
// to be passed as query string args to /my/machines
var validFilters = [
'name',
'image',
'state',
'memory',
'tombstone',
'credentials'
];
// valid output fields to be printed
var validFields = [
'id',
'name',
'type',
'state',
'dataset',
'memory',
'disk',
'ips',
'metadata',
'created',
'updated',
'package',
'image',
'ago'
];
var targ = require('./do_instance/do_list');
function do_instances(subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
callback(new Error('too many args: ' + args));
return;
}
var columns = opts.o.trim().split(',');
var sort = opts.s.trim().split(',');
var listOpts;
try {
listOpts = common.kvToObj(args, validFilters);
} catch (e) {
callback(e);
return;
}
this.triton.cloudapi.listMachines(listOpts, function (err, machines) {
if (err) {
callback(err);
return;
}
// add extra fields for nice output
var now = new Date();
machines.forEach(function (machine) {
var created = new Date(machine.created);
machine.ago = common.longAgo(created, now);
});
if (opts.json) {
console.log(common.jsonStream(machines));
} else {
tabula(machines, {
skipHeader: opts.H,
columns: columns,
sort: sort,
validFields: validFields
});
}
callback();
});
this.handlerFromSubcmd('instance').dispatch({
subcmd: 'list',
opts: opts,
args: args
}, callback);
}
do_instances.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['H'],
type: 'bool',
help: 'Omit table header row.'
},
{
names: ['o'],
type: 'string',
default: 'id,name,state,type,image,memory,disk,ago',
help: 'Specify fields (columns) to output.',
helpArg: 'field1,...'
},
{
names: ['s'],
type: 'string',
default: 'name',
help: 'Sort on the given fields. Default is "name".',
helpArg: 'field1,...'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
do_instances.help = (
'List instances.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} instances [<filters>...]\n'
+ '\n'
+ '{{options}}'
);
do_instances.help = 'A shortcut for "spearhead instance list".\n' + targ.help;
do_instances.synopses = targ.synopses;
do_instances.options = targ.options;
do_instances.completionArgtypes = targ.completionArgtypes;
do_instances.aliases = ['insts'];
do_instances.aliases = ['insts', 'ls'];
module.exports = do_instances;

28
lib/do_ip.js Normal file
View File

@ -0,0 +1,28 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2017 Joyent, Inc.
*
* `triton ip ...` shortcut for `triton instance ip ...`.
*/
var targ = require('./do_instance/do_ip');
function do_ip(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({
subcmd: 'ip',
opts: opts,
args: args
}, callback);
}
do_ip.help = 'A shortcut for "spearhead instance ip".\n' + targ.help;
do_ip.synopses = targ.synopses;
do_ip.options = targ.options;
do_ip.completionArgtypes = targ.completionArgtypes;
module.exports = do_ip;

Some files were not shown because too many files have changed in this diff Show More