Compare commits
1 Commits
master
...
hackathon_
Author | SHA1 | Date | |
---|---|---|---|
|
6fb1825f94 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,3 @@
|
|||||||
/test/*.json
|
/test/*.json
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
/triton-*.tgz
|
/triton-*.tgz
|
||||||
.DS_Store
|
|
||||||
.git
|
|
||||||
*.swp
|
|
||||||
|
146
CHANGES.md
146
CHANGES.md
@ -6,151 +6,7 @@ Known issues:
|
|||||||
|
|
||||||
## not yet released
|
## not yet released
|
||||||
|
|
||||||
(nothing)
|
(nothing yet)
|
||||||
|
|
||||||
## 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
|
## 5.5.0
|
||||||
|
|
||||||
|
425
README.md
425
README.md
@ -1,38 +1,427 @@
|
|||||||
![logo](https://code.spearhead.cloud/Spearhead/node-spearhead/raw/branch/master/tools/sphsp.png)
|
![logo](./tools/triton-text.png)
|
||||||
|
|
||||||
# node-spearhead
|
# node-triton
|
||||||
|
|
||||||
This repository holds the node-spearhead CLI tool to work with the Spearhead
|
This repository is part of the Joyent Triton project. See the [contribution
|
||||||
Cloud. It is a fork of [node-triton](https://github.com/joyent/node-triton).
|
guidelines](https://github.com/joyent/triton/blob/master/CONTRIBUTING.md) --
|
||||||
|
*Triton does not use GitHub PRs* -- and general documentation at the main
|
||||||
|
[Triton project](https://github.com/joyent/triton) page.
|
||||||
|
|
||||||
## Installation and configuration
|
`triton` is a CLI tool for working with the CloudAPI for Joyent's Triton [Public Cloud]
|
||||||
|
(https://docs.joyent.com/public-cloud) and [Private Cloud] (https://docs.joyent.com/private-cloud).
|
||||||
|
CloudAPI is a RESTful API for end users of the cloud to manage their accounts, instances,
|
||||||
|
networks, images, and to inquire other relevant details. CloudAPI provides a single view of
|
||||||
|
docker containers, infrastructure containers and hardware virtual machines available in the
|
||||||
|
Triton solution.
|
||||||
|
|
||||||
### Get a Spearhead Cloud account
|
There is currently another CLI tool known as [node-smartdc](https://github.com/joyent/node-smartdc)
|
||||||
|
for CloudAPI. `node-smartdc` CLI works off the 32-character object UUID to uniquely
|
||||||
|
identify object instances in API requests, and returns response payload in JSON format.
|
||||||
|
The CLI covers both basic and advanced usage of [CloudAPI](https://apidocs.joyent.com/cloudapi/).
|
||||||
|
|
||||||
Create an account on the Spearhead Cloud and upload your SSH key. You can create an account
|
**The `triton` CLI is currently in beta (effectively because it does not yet
|
||||||
[here](https://spearhead.cloud/).
|
have *complete* coverage of all commands from node-smartdc) and will be
|
||||||
|
expanded over time to support all CloudAPI commands, eventually replacing
|
||||||
|
`node-smartdc` as both the API client library for Triton cloud and the command
|
||||||
|
line tool.**
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### User accounts, authentication, and security
|
||||||
|
|
||||||
|
Before you can use the CLI you'll need an account on the cloud to which you are connecting and
|
||||||
|
an SSH key uploaded. The SSH key is used to identify and secure SSH access to containers and
|
||||||
|
other resources in Triton.
|
||||||
|
|
||||||
|
If you do not already have an account on Joyent Public Cloud, sign up [here](https://www.joyent.com/public-cloud).
|
||||||
|
|
||||||
|
|
||||||
### Data-centers
|
### API endpoint
|
||||||
|
|
||||||
The list of available Spearhead Cloud data-centers is available
|
Each data center has a single CloudAPI endpoint. For Joyent Public Cloud, you can find the
|
||||||
[here](https://spearhead.cloud/datacenters).
|
list of data centers [here](https://docs.joyent.com/public-cloud/data-centers).
|
||||||
|
For private cloud implementations, please consult the private cloud operator for the correct URL.
|
||||||
|
Have the URL handy as you'll need it in the next step.
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
Install [node.js](http://nodejs.org/), then:
|
Install [node.js](http://nodejs.org/), then:
|
||||||
|
|
||||||
npm install -g spearhead
|
npm install -g triton
|
||||||
|
|
||||||
Verify that it is installed and on your PATH:
|
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
|
$ triton --version
|
||||||
about installation and configuration are available
|
Triton CLI 4.15.0
|
||||||
[here](https://docs.spearhead.cloud).
|
https://github.com/joyent/node-triton
|
||||||
|
|
||||||
|
To use `triton`, you'll need to configure it to talk to a Triton DataCenter
|
||||||
|
API endpoint (called CloudAPI). Commonly that is done using a Triton profile:
|
||||||
|
|
||||||
|
$ triton profile create
|
||||||
|
A profile name. A short string to identify a CloudAPI endpoint to the
|
||||||
|
`triton` CLI.
|
||||||
|
name: sw1
|
||||||
|
|
||||||
|
The CloudAPI endpoint URL.
|
||||||
|
url: https://us-sw-1.api.joyent.com
|
||||||
|
|
||||||
|
Your account login name.
|
||||||
|
account: bob
|
||||||
|
|
||||||
|
Available SSH keys:
|
||||||
|
1. 2048-bit RSA key with fingerprint 4e:e7:56:9a:b0:91:31:3e:23:8d:f8:62:12:58:a2:ec
|
||||||
|
* [in homedir] bob-20160704 id_rsa
|
||||||
|
|
||||||
|
The fingerprint of the SSH key you want to use, or its index in the list
|
||||||
|
above. If the key you want to use is not listed, make sure it is either saved
|
||||||
|
in your SSH keys directory or loaded into the SSH agent.
|
||||||
|
keyId: 1
|
||||||
|
|
||||||
|
Saved profile "sw1".
|
||||||
|
|
||||||
|
WARNING: Docker uses TLS-based authentication with a different security model
|
||||||
|
from SSH keys. As a result, the Docker client cannot currently support
|
||||||
|
encrypted (password protected) keys or SSH agents. If you continue, the
|
||||||
|
Triton CLI will attempt to format a copy of your SSH *private* key as an
|
||||||
|
unencrypted TLS cert and place the copy in ~/.triton/docker for use by the
|
||||||
|
Docker client.
|
||||||
|
Continue? [y/n] y
|
||||||
|
Setting up profile "sw1" to use Docker.
|
||||||
|
Setup profile "sw1" to use Docker (v1.12.3). Try this:
|
||||||
|
eval "$(triton env --docker sw1)"
|
||||||
|
docker info
|
||||||
|
|
||||||
|
Set "sw1" as current profile (because it is your only profile).
|
||||||
|
|
||||||
|
Or instead of using profiles, you can set the required environment variables
|
||||||
|
(`triton` defaults to an "env" profile that uses these environment variables if
|
||||||
|
no profile is set). For example:
|
||||||
|
|
||||||
|
TRITON_URL=https://us-sw-1.api.joyent.com
|
||||||
|
TRITON_ACCOUNT=bob
|
||||||
|
TRITON_KEY_ID=SHA256:j2WoSeOWhFy69BQ0uCR3FAySp9qCZTSCEyT2vRKcL+s
|
||||||
|
|
||||||
|
For compatibility with the older [sdc-* tools from
|
||||||
|
node-smartdc](https://github.com/joyent/node-smartdc), `triton` also supports
|
||||||
|
`SDC_URL`, `SDC_ACCOUNT`, etc. environment variables.
|
||||||
|
|
||||||
|
|
||||||
|
### Bash completion
|
||||||
|
|
||||||
|
Install Bash completion with
|
||||||
|
|
||||||
|
```bash
|
||||||
|
triton completion > /usr/local/etc/bash_completion.d/triton # Mac
|
||||||
|
triton completion > /etc/bash_completion.d/triton # Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you don't have or don't want to use a "bash\_completion.d"
|
||||||
|
dir, then something like this would work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
triton completion > ~/.triton.completion
|
||||||
|
echo "source ~/.triton.completion" >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open a new shell or manually `source FILE` that completion file, and
|
||||||
|
play with the bash completions:
|
||||||
|
|
||||||
|
triton <TAB>
|
||||||
|
|
||||||
|
|
||||||
|
## `triton` CLI Usage
|
||||||
|
|
||||||
|
### Create and view instances
|
||||||
|
|
||||||
|
$ triton instance list
|
||||||
|
SHORTID NAME IMG STATE PRIMARYIP AGO
|
||||||
|
|
||||||
|
We have no instances created yet, so let's create some. In order to create
|
||||||
|
an instance we need to specify two things: an image and a package. An image
|
||||||
|
represents what will be used as the root of the instances filesystem, and the
|
||||||
|
package represents the size of the instance, eg. ram, disk size, cpu shares,
|
||||||
|
etc. More information on images and packages below - for now we'll just use
|
||||||
|
SmartOS 64bit and a small 128M ram package which is a combo available on the
|
||||||
|
Joyent Public Cloud.
|
||||||
|
|
||||||
|
$ triton instance create base-64 t4-standard-128M
|
||||||
|
|
||||||
|
Without a name specified, the container created will have a generated ID. Now
|
||||||
|
to create a container-native Ubuntu 14.04 container with 2GB of ram with the
|
||||||
|
name "server-1"
|
||||||
|
|
||||||
|
$ triton instance create --name=server-1 ubuntu-14.04 t4-standard-2G
|
||||||
|
|
||||||
|
Now list your instances again
|
||||||
|
|
||||||
|
$ triton instance list
|
||||||
|
SHORTID NAME IMG STATE PRIMARYIP AGO
|
||||||
|
7db6c907 b851ba9 base-64@15.2.0 running 165.225.169.63 9m
|
||||||
|
9cf1f427 server-1 ubuntu-14.04@20150819 provisioning - 0s
|
||||||
|
|
||||||
|
|
||||||
|
Get a quick overview of your account
|
||||||
|
|
||||||
|
$ triton info
|
||||||
|
login: dave.eddy@joyent.com
|
||||||
|
name: Dave Eddy
|
||||||
|
email: dave.eddy@joyent.com
|
||||||
|
url: https://us-east-3b.api.joyent.com
|
||||||
|
totalDisk: 50.5 GiB
|
||||||
|
totalMemory: 2.0 MiB
|
||||||
|
instances: 2
|
||||||
|
running: 1
|
||||||
|
provisioning: 1
|
||||||
|
|
||||||
|
To obtain more detailed information of your instance
|
||||||
|
|
||||||
|
$ triton instance get server-1
|
||||||
|
{
|
||||||
|
"id": "9cf1f427-9a40-c188-ce87-fd0c4a5a2c2c",
|
||||||
|
"name": "251d4fd",
|
||||||
|
"type": "smartmachine",
|
||||||
|
"state": "running",
|
||||||
|
"image": "c8d68a9e-4682-11e5-9450-4f4fadd0936d",
|
||||||
|
"ips": [
|
||||||
|
"165.225.169.54",
|
||||||
|
"192.168.128.16"
|
||||||
|
],
|
||||||
|
"memory": 2048,
|
||||||
|
"disk": 51200,
|
||||||
|
"metadata": {
|
||||||
|
"root_authorized_keys": "(...ssh keys...)"
|
||||||
|
},
|
||||||
|
"tags": {},
|
||||||
|
"created": "2015-09-08T04:56:27.734Z",
|
||||||
|
"updated": "2015-09-08T04:56:43.000Z",
|
||||||
|
"networks": [
|
||||||
|
"feb7b2c5-0063-42f0-a4e6-b812917397f7",
|
||||||
|
"726379ac-358b-4fb4-bb7c-8bc4548bac1e"
|
||||||
|
],
|
||||||
|
"dataset": "c8d68a9e-4682-11e5-9450-4f4fadd0936d",
|
||||||
|
"primaryIp": "165.225.169.54",
|
||||||
|
"firewall_enabled": false,
|
||||||
|
"compute_node": "44454c4c-5400-1034-8053-b5c04f383432",
|
||||||
|
"package": "t4-standard-2G"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### SSH to an instance
|
||||||
|
|
||||||
|
Connect to an instance over SSH
|
||||||
|
|
||||||
|
$ triton ssh b851ba9
|
||||||
|
Last login: Wed Aug 26 17:59:35 2015 from 208.184.5.170
|
||||||
|
__ . .
|
||||||
|
_| |_ | .-. . . .-. :--. |-
|
||||||
|
|_ _| ;| || |(.-' | | |
|
||||||
|
|__| `--' `-' `;-| `-' ' ' `-'
|
||||||
|
/ ; Instance (base-64 15.2.0)
|
||||||
|
`-' https://docs.joyent.com/images/smartos/base
|
||||||
|
|
||||||
|
[root@7db6c907-2693-42bc-ea9b-f38678f2554b ~]# uptime
|
||||||
|
20:08pm up 2:27, 0 users, load average: 0.00, 0.00, 0.01
|
||||||
|
[root@7db6c907-2693-42bc-ea9b-f38678f2554b ~]# logout
|
||||||
|
Connection to 165.225.169.63 closed.
|
||||||
|
|
||||||
|
Or non-interactively
|
||||||
|
|
||||||
|
$ triton ssh b851ba9 uname -v
|
||||||
|
joyent_20150826T120743Z
|
||||||
|
|
||||||
|
|
||||||
|
### Manage an instance
|
||||||
|
|
||||||
|
Commonly used container operations are supported in the Triton CLI:
|
||||||
|
|
||||||
|
$ triton help instance
|
||||||
|
...
|
||||||
|
list (ls) List instances.
|
||||||
|
get Get an instance.
|
||||||
|
create Create a new instance.
|
||||||
|
delete (rm) Delete one or more instances.
|
||||||
|
|
||||||
|
start Start one or more instances.
|
||||||
|
stop Stop one or more instances.
|
||||||
|
reboot Reboot one or more instances.
|
||||||
|
|
||||||
|
ssh SSH to the primary IP of an instance
|
||||||
|
wait Wait on instances changing state.
|
||||||
|
audit List instance actions.
|
||||||
|
|
||||||
|
### View packages and images
|
||||||
|
|
||||||
|
Package definitions and images available vary between different data centers
|
||||||
|
and different Triton cloud implementations.
|
||||||
|
|
||||||
|
To see all the packages offered in the data center and specific package
|
||||||
|
information, use
|
||||||
|
|
||||||
|
$ triton package list
|
||||||
|
$ triton package get ID|NAME
|
||||||
|
|
||||||
|
Similarly, to find out the available images and their details, do
|
||||||
|
|
||||||
|
$ triton image list
|
||||||
|
$ triton images ID|NAME
|
||||||
|
|
||||||
|
Note that docker images are not shown in `triton images` as they are
|
||||||
|
maintained in Docker Hub and other third-party registries configured to be
|
||||||
|
used with Joyent's Triton clouds. **In general, docker containers should be
|
||||||
|
provisioned and managed with the regular
|
||||||
|
[`docker` CLI](https://docs.docker.com/installation/#installation)**
|
||||||
|
(Triton provides an endpoint that represents the _entire datacenter_
|
||||||
|
as a single `DOCKER_HOST`. See the [Triton Docker
|
||||||
|
documentation](https://apidocs.joyent.com/docker) for more information.)
|
||||||
|
|
||||||
|
|
||||||
|
## `TritonApi` Module Usage
|
||||||
|
|
||||||
|
Node-triton can also be used as a node module for your own node.js tooling.
|
||||||
|
A basic example appropriate for a command-line tool is:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var mod_bunyan = require('bunyan');
|
||||||
|
var mod_triton = require('triton');
|
||||||
|
|
||||||
|
var log = mod_bunyan.createLogger({name: 'my-tool'});
|
||||||
|
|
||||||
|
// See the `createClient` block comment for full usage details:
|
||||||
|
// https://github.com/joyent/node-triton/blob/master/lib/index.js
|
||||||
|
mod_triton.createClient({
|
||||||
|
log: log,
|
||||||
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
|
// `profile` object.
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: mod_triton.promptPassphraseUnlockKey
|
||||||
|
}, function (err, client) {
|
||||||
|
if (err) {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.listImages(function (err, images) {
|
||||||
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
|
if (err) {
|
||||||
|
console.error('listImages err:', err);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(images, null, 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See the following for more details:
|
||||||
|
- The block-comment for `createClient` in [lib/index.js](lib/index.js).
|
||||||
|
- Some module-usage examples in [examples/](examples/).
|
||||||
|
- The lower-level details in the top-comment in
|
||||||
|
[lib/tritonapi.js](lib/tritonapi.js).
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This section defines all the vars in a TritonApi config. The baked in defaults
|
||||||
|
are in "etc/defaults.json" and can be overriden for the CLI in
|
||||||
|
"~/.triton/config.json" (on Windows: "%APPDATA%/Joyent/Triton/config.json").
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| profile | The name of the triton profile to use. The default with the CLI is "env", i.e. take config from `SDC_*` envvars. |
|
||||||
|
| cacheDir | The path (relative to the config dir, "~/.triton") where cache data is stored. The default is "cache", i.e. the `triton` CLI caches at "~/.triton/cache". |
|
||||||
|
|
||||||
|
|
||||||
|
## node-triton differences with node-smartdc
|
||||||
|
|
||||||
|
- There is a single `triton` command instead of a number of `sdc-*` commands.
|
||||||
|
- `TRITON_*` environment variables are preferred to the `SDC_*` environment
|
||||||
|
variables. However the `SDC_*` envvars are still supported.
|
||||||
|
- Node-smartdc still has more complete coverage of the Triton
|
||||||
|
[CloudAPI](https://apidocs.joyent.com/cloudapi/). However, `triton` is
|
||||||
|
catching up and is much more friendly to use.
|
||||||
|
|
||||||
|
|
||||||
|
## Development Hooks
|
||||||
|
|
||||||
|
Before commiting be sure to, at least:
|
||||||
|
|
||||||
|
make check # lint and style checks
|
||||||
|
make test-unit # run unit tests
|
||||||
|
|
||||||
|
A good way to do that is to install the stock pre-commit hook in your
|
||||||
|
clone via:
|
||||||
|
|
||||||
|
make git-hooks
|
||||||
|
|
||||||
|
Also please run the full (longer) test suite (`make test`). See the next
|
||||||
|
section.
|
||||||
|
|
||||||
|
|
||||||
|
## Test suite
|
||||||
|
|
||||||
|
node-triton has both unit tests (`make test-unit`) and integration tests (`make
|
||||||
|
test-integration`). Integration tests require a config file, by default at
|
||||||
|
"test/config.json". For example:
|
||||||
|
|
||||||
|
$ cat test/config.json
|
||||||
|
{
|
||||||
|
"profileName": "east3b",
|
||||||
|
"allowWriteActions": true,
|
||||||
|
"image": "minimal-64",
|
||||||
|
"package": "g4-highcpu-128M",
|
||||||
|
"resizePackage": "g4-highcpu-256M"
|
||||||
|
}
|
||||||
|
|
||||||
|
See "test/config.json.sample" for a description of all config vars. Minimally
|
||||||
|
just a "profileName" or "profile" is required.
|
||||||
|
|
||||||
|
*Warning:* Running the *integration* tests will create resources and could
|
||||||
|
incur costs if running against a public cloud.
|
||||||
|
|
||||||
|
Run all tests:
|
||||||
|
|
||||||
|
make test
|
||||||
|
|
||||||
|
You can use `TRITON_TEST_CONFIG` to override the test file, e.g.:
|
||||||
|
|
||||||
|
$ cat test/coal.json
|
||||||
|
{
|
||||||
|
"profileName": "coal",
|
||||||
|
"allowWriteActions": true
|
||||||
|
}
|
||||||
|
$ TRITON_TEST_CONFIG=test/coal.json make test
|
||||||
|
|
||||||
|
where "coal" here refers to a development Triton (a.k.a SDC) ["Cloud On A
|
||||||
|
Laptop"](https://github.com/joyent/sdc#getting-started) standup.
|
||||||
|
|
||||||
|
|
||||||
|
## Release process
|
||||||
|
|
||||||
|
Here is how to cut a release:
|
||||||
|
|
||||||
|
1. Make a commit to set the intended version in "package.json#version" and changing `## not yet released` at the top of "CHANGES.md" to:
|
||||||
|
|
||||||
|
```
|
||||||
|
## not yet released
|
||||||
|
|
||||||
|
|
||||||
|
## $version
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Get that commit approved and merged via <https://cr.joyent.us>, as with all
|
||||||
|
commits to this repo. See the discussion of contribution at the top of this
|
||||||
|
readme.
|
||||||
|
|
||||||
|
3. Once that is merged and you've updated your local copy, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
make cutarelease
|
||||||
|
```
|
||||||
|
|
||||||
|
This will run a couple checks (clean working copy, versions in package.json
|
||||||
|
and CHANGES.md match), then will git tag and npm publish.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MPL 2.0
|
MPL 2.0
|
||||||
|
45
examples/example-get-account.js
Executable file
45
examples/example-get-account.js
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Example creating a Triton API client and using it to get account info.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ./example-get-account.js
|
||||||
|
*
|
||||||
|
* # With trace-level logging
|
||||||
|
* LOG_LEVEL=trace ./example-get-account.js 2>&1 | bunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bunyan = require('bunyan');
|
||||||
|
var path = require('path');
|
||||||
|
var triton = require('../'); // typically `require('triton');`
|
||||||
|
|
||||||
|
var log = bunyan.createLogger({
|
||||||
|
name: path.basename(__filename),
|
||||||
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
|
stream: process.stderr
|
||||||
|
});
|
||||||
|
|
||||||
|
triton.createClient({
|
||||||
|
log: log,
|
||||||
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
|
// `profile` object.
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
|
}, function createdClient(err, client) {
|
||||||
|
if (err) {
|
||||||
|
console.error('error creating Triton client: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually the top-level TritonApi will have `.getAccount()`.
|
||||||
|
client.cloudapi.getAccount(function (err, account) {
|
||||||
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
|
if (err) {
|
||||||
|
console.error('getAccount error: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(account, null, 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
46
examples/example-list-instances.js
Executable file
46
examples/example-list-instances.js
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Example creating a Triton API client and using it to list instances.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ./example-list-instances.js
|
||||||
|
*
|
||||||
|
* # With trace-level logging
|
||||||
|
* LOG_LEVEL=trace ./example-list-instances.js 2>&1 | bunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bunyan = require('bunyan');
|
||||||
|
var path = require('path');
|
||||||
|
var triton = require('../'); // typically `require('triton');`
|
||||||
|
|
||||||
|
var log = bunyan.createLogger({
|
||||||
|
name: path.basename(__filename),
|
||||||
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
|
stream: process.stderr
|
||||||
|
});
|
||||||
|
|
||||||
|
triton.createClient({
|
||||||
|
log: log,
|
||||||
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
|
// `profile` object.
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
|
}, function createdClient(err, client) {
|
||||||
|
if (err) {
|
||||||
|
console.error('error creating Triton client: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually the top-level TritonApi will have `.listInstances()`.
|
||||||
|
client.cloudapi.listMachines(function (err, insts) {
|
||||||
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error('listInstances error: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(insts, null, 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
27
examples/rbac-simple/README.md
Normal file
27
examples/rbac-simple/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
*Caveat*: All `triton rbac ...` support is experimental.
|
||||||
|
|
||||||
|
This directly holds a super simple example Triton RBAC Profile for a mythical
|
||||||
|
"Simple Corp.", with `triton` CLI examples showing how to use it for RBAC.
|
||||||
|
|
||||||
|
Our Simple corporation will create an "rbactestsimple" Triton account and
|
||||||
|
use RBAC to manage its users, roles, etc. It has two users:
|
||||||
|
|
||||||
|
- emma: Should have full access, to everything.
|
||||||
|
- bert: Should only have read access, again to everything.
|
||||||
|
|
||||||
|
We want an RBAC config that allows appropriate access for all the employees
|
||||||
|
and tooling. Roughly we'll break that into roles as follows:
|
||||||
|
|
||||||
|
- Role `admin`. Complete access to the API. Only used by "emma" when, e.g.,
|
||||||
|
updating RBAC configuration itself.
|
||||||
|
- Role `ops`. Full access, except to RBAC configuration updates.
|
||||||
|
- Role `read`. Read-only access to compute resources.
|
||||||
|
|
||||||
|
See "rbac.json" where we encode all this.
|
||||||
|
|
||||||
|
The `triton rbac apply` command can work with a JSON config file (and
|
||||||
|
optionally separate user public ssh key files) to create and maintain a
|
||||||
|
Triton RBAC configuration. In our example this will be:
|
||||||
|
|
||||||
|
triton rbac apply # defaults to looking at "./rbac.json"
|
||||||
|
|
43
examples/rbac-simple/rbac.json
Normal file
43
examples/rbac-simple/rbac.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"users": [
|
||||||
|
{ "login": "emma", "email": "emma@simple.example.com" },
|
||||||
|
{ "login": "bert", "email": "bert@simple.example.com" }
|
||||||
|
],
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"default_members": [],
|
||||||
|
"members": ["emma"],
|
||||||
|
"policies": ["policy-admin"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ops",
|
||||||
|
"default_members": ["emma"],
|
||||||
|
"members": ["emma"],
|
||||||
|
"policies": ["policy-full"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read",
|
||||||
|
"default_members": ["bert", "emma"],
|
||||||
|
"members": ["bert", "emma"],
|
||||||
|
"policies": ["policy-readonly"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"name": "policy-admin",
|
||||||
|
"description": "full access",
|
||||||
|
"rules": ["CAN *"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "policy-full",
|
||||||
|
"description": "full access, except rbac",
|
||||||
|
"rules": ["CAN compute:*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "policy-readonly",
|
||||||
|
"description": "read-only access",
|
||||||
|
"rules": ["CAN compute:Get*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
57
lib/cli.js
57
lib/cli.js
@ -58,9 +58,9 @@ var OPTIONS = [
|
|||||||
names: ['profile', 'p'],
|
names: ['profile', 'p'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
completionType: 'tritonprofile',
|
completionType: 'tritonprofile',
|
||||||
env: 'SC_PROFILE',
|
env: 'TRITON_PROFILE',
|
||||||
helpArg: 'NAME',
|
helpArg: 'NAME',
|
||||||
help: 'Spearhead Cloud client profile to use.'
|
help: 'Triton client profile to use.'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -81,7 +81,8 @@ var OPTIONS = [
|
|||||||
{
|
{
|
||||||
names: ['account', 'a'],
|
names: ['account', 'a'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
help: 'Account (login name). Environment: SC_ACCOUNT=ACCOUNT ',
|
help: 'Account (login name). Environment: TRITON_ACCOUNT=ACCOUNT ' +
|
||||||
|
'or SDC_ACCOUNT=ACCOUNT.',
|
||||||
helpArg: 'ACCOUNT'
|
helpArg: 'ACCOUNT'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -89,48 +90,51 @@ var OPTIONS = [
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
help: 'Masquerade as the given account login name. This can only ' +
|
help: 'Masquerade as the given account login name. This can only ' +
|
||||||
'succeed for operator accounts. Note that accesses like these ' +
|
'succeed for operator accounts. Note that accesses like these ' +
|
||||||
'are audited on the CloudAPI server side.',
|
'audited on the CloudAPI server side.',
|
||||||
helpArg: 'ACCOUNT',
|
helpArg: 'ACCOUNT',
|
||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['user', 'u'],
|
names: ['user', 'u'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
help: 'RBAC user (login name). Environment: SC_USER=USER',
|
help: 'RBAC user (login name). Environment: TRITON_USER=USER ' +
|
||||||
|
'or SDC_USER=USER.',
|
||||||
helpArg: 'USER'
|
helpArg: 'USER'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['role', 'r'],
|
names: ['role', 'r'],
|
||||||
type: 'arrayOfCommaSepString',
|
type: 'arrayOfCommaSepString',
|
||||||
env: 'SC_ROLE',
|
env: 'TRITON_ROLE',
|
||||||
help: 'Assume an RBAC role. Use multiple times or once with a list',
|
help: 'Assume an RBAC role. Use multiple times or once with a list',
|
||||||
helpArg: 'ROLE,...'
|
helpArg: 'ROLE,...'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['keyId', 'k'],
|
names: ['keyId', 'k'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
help: 'SSH key fingerprint. Environment: SC_KEY_ID=FINGERPRINT.',
|
help: 'SSH key fingerprint. Environment: TRITON_KEY_ID=FINGERPRINT ' +
|
||||||
|
'or SDC_KEY_ID=FINGERPRINT.',
|
||||||
helpArg: 'FP'
|
helpArg: 'FP'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['url', 'U'],
|
names: ['url', 'U'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
help: 'Spearhead Cloud Datacenter URL. Environment: SC_URL=URL.',
|
help: 'CloudAPI URL. Environment: TRITON_URL=URL or SDC_URL=URL.',
|
||||||
helpArg: 'URL'
|
helpArg: 'URL'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['J'],
|
names: ['J'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
help: 'Spearhead Cloud (SC) datacenter name. This is ' +
|
help: 'Joyent Public Cloud (JPC) datacenter name. This is ' +
|
||||||
'a shortcut to the "https://$dc.api.spearhead.cloud" ' +
|
'a shortcut to the "https://$dc.api.joyent.com" ' +
|
||||||
'cloudapi URL.'
|
'cloudapi URL.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['insecure', 'i'],
|
names: ['insecure', 'i'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Do not validate the SSL certificate. Environment: ' +
|
help: 'Do not validate the CloudAPI SSL certificate. Environment: ' +
|
||||||
'SC_TLS_INSECURE=1 (or the deprecated SC_TESTING=1).',
|
'TRITON_TLS_INSECURE=1, SDC_TLS_INSECURE=1 (or the deprecated ' +
|
||||||
|
'SDC_TESTING=1).',
|
||||||
'default': false
|
'default': false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -139,10 +143,10 @@ var OPTIONS = [
|
|||||||
helpArg: 'VER',
|
helpArg: 'VER',
|
||||||
help: 'A cloudapi API version, or semver range, to attempt to use. ' +
|
help: 'A cloudapi API version, or semver range, to attempt to use. ' +
|
||||||
'This is passed in the "Accept-Version" header. ' +
|
'This is passed in the "Accept-Version" header. ' +
|
||||||
'See `spearhead cloudapi /--ping` to list supported versions. ' +
|
'See `triton cloudapi /--ping` to list supported versions. ' +
|
||||||
'The default is "' + lib_tritonapi.CLOUDAPI_ACCEPT_VERSION + '". ' +
|
'The default is "' + lib_tritonapi.CLOUDAPI_ACCEPT_VERSION + '". ' +
|
||||||
'*This is intended for development use only. It could cause ' +
|
'*This is intended for development use only. It could cause ' +
|
||||||
'`spearhead` processing of responses to break.*',
|
'`triton` processing of responses to break.*',
|
||||||
hidden: true
|
hidden: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -179,7 +183,7 @@ cmdln.dashdash.addOptionType({
|
|||||||
|
|
||||||
function CLI() {
|
function CLI() {
|
||||||
Cmdln.call(this, {
|
Cmdln.call(this, {
|
||||||
name: 'spearhead',
|
name: 'triton',
|
||||||
desc: packageJson.description,
|
desc: packageJson.description,
|
||||||
options: OPTIONS,
|
options: OPTIONS,
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -206,7 +210,6 @@ function CLI() {
|
|||||||
'package',
|
'package',
|
||||||
'network',
|
'network',
|
||||||
'fwrule',
|
'fwrule',
|
||||||
'vlan',
|
|
||||||
{ group: 'Other Commands' },
|
{ group: 'Other Commands' },
|
||||||
'info',
|
'info',
|
||||||
'account',
|
'account',
|
||||||
@ -245,7 +248,7 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts.version) {
|
if (opts.version) {
|
||||||
console.log('Spearhead CLI', packageJson.version);
|
console.log('Triton CLI', packageJson.version);
|
||||||
console.log(packageJson.homepage);
|
console.log(packageJson.homepage);
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
@ -255,7 +258,7 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
callback(new errors.UsageError(
|
callback(new errors.UsageError(
|
||||||
'cannot use both "--url" and "-J" options'));
|
'cannot use both "--url" and "-J" options'));
|
||||||
} else if (opts.J) {
|
} else if (opts.J) {
|
||||||
opts.url = format('https://%s.api.spearhead.cloud', opts.J);
|
opts.url = format('https://%s.api.joyent.com', opts.J);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configDir = constants.CLI_CONFIG_DIR;
|
this.configDir = constants.CLI_CONFIG_DIR;
|
||||||
@ -295,8 +298,9 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
pErr.message += '\n'
|
pErr.message += '\n'
|
||||||
+ ' No profile information could be loaded.\n'
|
+ ' No profile information could be loaded.\n'
|
||||||
+ ' Use "spearhead profile create" to create a profile or provide\n'
|
+ ' Use "triton profile create" to create a profile or provide\n'
|
||||||
+ ' the required "CloudAPI options" described in "spearhead --help".';
|
+ ' the required "CloudAPI options" described in "triton --help".\n'
|
||||||
|
+ ' See https://github.com/joyent/node-triton#setup for more help.';
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
}
|
}
|
||||||
throw pErr;
|
throw pErr;
|
||||||
@ -318,9 +322,9 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
return self._tritonapi;
|
return self._tritonapi;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.SC_COMPLETE) {
|
if (process.env.TRITON_COMPLETE) {
|
||||||
/*
|
/*
|
||||||
* If `SC_COMPLETE=<type>` is set (typically only in the
|
* If `TRITON_COMPLETE=<type>` is set (typically only in the
|
||||||
* Triton CLI bash completion driver, see
|
* Triton CLI bash completion driver, see
|
||||||
* "etc/triton-bash-completion-types.sh"), then Bash completions are
|
* "etc/triton-bash-completion-types.sh"), then Bash completions are
|
||||||
* fetched and printed, instead of the usual subcommand handling.
|
* fetched and printed, instead of the usual subcommand handling.
|
||||||
@ -329,9 +333,9 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
* to avoid hitting the server for data everytime.
|
* to avoid hitting the server for data everytime.
|
||||||
*
|
*
|
||||||
* Example usage:
|
* Example usage:
|
||||||
* SC_COMPLETE=images triton -p my-profile create
|
* TRITON_COMPLETE=images triton -p my-profile create
|
||||||
*/
|
*/
|
||||||
self._emitCompletions(process.env.SC_COMPLETE, function (err) {
|
self._emitCompletions(process.env.TRITON_COMPLETE, function (err) {
|
||||||
callback(err || false);
|
callback(err || false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -555,7 +559,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
process.stderr.write('warning: unknown spearhead completion type: '
|
process.stderr.write('warning: unknown triton completion type: '
|
||||||
+ type + '\n');
|
+ type + '\n');
|
||||||
next();
|
next();
|
||||||
break;
|
break;
|
||||||
@ -701,9 +705,6 @@ CLI.prototype.do_package = require('./do_package');
|
|||||||
CLI.prototype.do_networks = require('./do_networks');
|
CLI.prototype.do_networks = require('./do_networks');
|
||||||
CLI.prototype.do_network = require('./do_network');
|
CLI.prototype.do_network = require('./do_network');
|
||||||
|
|
||||||
// VLANs
|
|
||||||
CLI.prototype.do_vlan = require('./do_vlan');
|
|
||||||
|
|
||||||
// Hidden commands
|
// Hidden commands
|
||||||
CLI.prototype.do_cloudapi = require('./do_cloudapi');
|
CLI.prototype.do_cloudapi = require('./do_cloudapi');
|
||||||
CLI.prototype.do_badger = require('./do_badger');
|
CLI.prototype.do_badger = require('./do_badger');
|
||||||
|
736
lib/cloudapi2.js
736
lib/cloudapi2.js
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* Client library for the SmartDataCenter Cloud API (cloudapi).
|
* Client library for the SmartDataCenter Cloud API (cloudapi).
|
||||||
* http://apidocs.joyent.com/cloudapi/
|
* http://apidocs.joyent.com/cloudapi/
|
||||||
@ -42,13 +42,15 @@ var querystring = require('querystring');
|
|||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
var auth = require('smartdc-auth');
|
var auth = require('smartdc-auth');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var mod_watershed = require('watershed');
|
||||||
|
var mod_restify = require('restify-clients');
|
||||||
|
|
||||||
var bunyannoop = require('./bunyannoop');
|
var bunyannoop = require('./bunyannoop');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
var errors = require('./errors');
|
var errors = require('./errors');
|
||||||
var SaferJsonClient = require('./SaferJsonClient');
|
var SaferJsonClient = require('./SaferJsonClient');
|
||||||
|
|
||||||
|
var shed = new mod_watershed.Watershed();
|
||||||
|
|
||||||
// ---- globals
|
// ---- globals
|
||||||
|
|
||||||
@ -152,8 +154,12 @@ function CloudApi(options) {
|
|||||||
//this.token = options.token;
|
//this.token = options.token;
|
||||||
|
|
||||||
this.client = new SaferJsonClient(options);
|
this.client = new SaferJsonClient(options);
|
||||||
|
|
||||||
|
// Websockets cannot use the JSON client
|
||||||
|
this.webSocketClient = new mod_restify.HttpClient(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CloudApi.prototype.close = function close(callback) {
|
CloudApi.prototype.close = function close(callback) {
|
||||||
this.log.trace({host: this.client.url && this.client.url.host},
|
this.log.trace({host: this.client.url && this.client.url.host},
|
||||||
'close cloudapi http client');
|
'close cloudapi http client');
|
||||||
@ -301,6 +307,91 @@ CloudApi.prototype._request = function _request(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cloud API websocket request wrapper
|
||||||
|
*
|
||||||
|
* @param {Object|String} opts - object or string for endpoint
|
||||||
|
* - {String} path - URL endpoint to hit
|
||||||
|
* - {String} method - HTTP(s) request method
|
||||||
|
* - {Object} data - data to be passed
|
||||||
|
* - {Object} headers - optional additional request headers
|
||||||
|
* @param {Function} cb passed via the restify client
|
||||||
|
*/
|
||||||
|
CloudApi.prototype._createWebSocket = function _createWebSocket(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
if (typeof (opts) === 'string')
|
||||||
|
opts = {path: opts};
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.optionalObject(opts.data, 'opts.data');
|
||||||
|
assert.optionalString(opts.method, 'opts.method');
|
||||||
|
assert.optionalObject(opts.headers, 'opts.headers');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var method = (opts.method || 'GET').toLowerCase();
|
||||||
|
|
||||||
|
// GET and POST are the only two methods that seem to make sense for
|
||||||
|
// creating a websocket
|
||||||
|
assert.ok(['get', 'post'].indexOf(method) >= 0,
|
||||||
|
'invalid HTTP method given');
|
||||||
|
|
||||||
|
if (self.roles && self.roles.length > 0) {
|
||||||
|
if (opts.path.indexOf('?') !== -1) {
|
||||||
|
opts.path += '&as-role=' + self.roles.join(',');
|
||||||
|
} else {
|
||||||
|
opts.path += '?as-role=' + self.roles.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self._getAuthHeaders(method, opts.path, function (err, headers) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wskey = shed.generateKey();
|
||||||
|
|
||||||
|
if (opts.headers) {
|
||||||
|
common.objMerge(headers, opts.headers);
|
||||||
|
}
|
||||||
|
common.objMerge(headers, {
|
||||||
|
'connection': 'upgrade',
|
||||||
|
'upgrade': 'websocket',
|
||||||
|
'sec-websocket-key': wskey,
|
||||||
|
'sec-websocket-version': 13
|
||||||
|
});
|
||||||
|
|
||||||
|
var reqOpts = {
|
||||||
|
path: opts.path,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
|
var upgradeCb = function (upgradeErr, req, res, body) {
|
||||||
|
if (upgradeErr) {
|
||||||
|
cb(upgradeErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
req.once('upgradeResult', function (uErr, uRes, socket, head) {
|
||||||
|
if (uErr) {
|
||||||
|
cb(uErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything currently using websockets are latency
|
||||||
|
// sensitive, so send data as soon as it's given to us
|
||||||
|
socket.setNoDelay(true);
|
||||||
|
var client = shed.connect(uRes, socket, head, wskey);
|
||||||
|
cb(null, client, uRes);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.data)
|
||||||
|
self.webSocketClient[method](reqOpts, opts.data, upgradeCb);
|
||||||
|
else
|
||||||
|
self.webSocketClient[method](reqOpts, upgradeCb);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple wrapper around making a GET request to an endpoint and
|
* A simple wrapper around making a GET request to an endpoint and
|
||||||
* passing back the body returned
|
* passing back the body returned
|
||||||
@ -357,48 +448,6 @@ CloudApi.prototype.ping = function ping(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ---- config
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get config object for the current user.
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {Function} cb of the form `function (err, config, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.getConfig = function getConfig(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = this._path(format('/%s/config', this.account));
|
|
||||||
this._request(endpoint, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set config object for the current user.
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* - {String} default_network: network fabric docker containers are
|
|
||||||
* provisioned on. Optional.
|
|
||||||
* @param {Function} cb of the form `function (err, config, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.updateConfig = function updateConfig(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.optionalUuid(opts.default_network, 'opts.default_network');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'PUT',
|
|
||||||
path: format('/%s/config', this.account),
|
|
||||||
data: opts
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ---- networks
|
// ---- networks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -430,7 +479,7 @@ CloudApi.prototype.getNetwork = function getNetwork(id, cb) {
|
|||||||
/**
|
/**
|
||||||
* <http://apidocs.joyent.com/cloudapi/#ListNetworkIPs>
|
* <http://apidocs.joyent.com/cloudapi/#ListNetworkIPs>
|
||||||
*
|
*
|
||||||
* @param {String} id - The network UUID. Required.
|
* @param {String} - UUID
|
||||||
* @param {Function} callback of the form `function (err, ips, res)`
|
* @param {Function} callback of the form `function (err, ips, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
||||||
@ -447,14 +496,13 @@ CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
|||||||
* <http://apidocs.joyent.com/cloudapi/#GetNetworkIP>
|
* <http://apidocs.joyent.com/cloudapi/#GetNetworkIP>
|
||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - {String} id - The network UUID. Required.
|
* - {String} opts.id The network UUID, name, or shortID. Required.
|
||||||
* - {String} ip - The IP. Required.
|
* - {String} opts.ip The IP. Required.
|
||||||
* @param {Function} callback of the form `function (err, ip, res)`
|
* @param {Function} callback of the form `function (err, ip, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
||||||
assert.object(opts, 'opts');
|
assert.uuid(opts.id, 'id');
|
||||||
assert.uuid(opts.id, 'opts.id');
|
assert.string(opts.ip, 'ip');
|
||||||
assert.string(opts.ip, 'opts.ip');
|
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
||||||
@ -464,265 +512,6 @@ CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* <http://apidocs.joyent.com/cloudapi/#UpdateNetworkIP>
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* - {String} id - The network UUID. Required.
|
|
||||||
* - {String} ip - The IP. Required.
|
|
||||||
* - {Boolean} reserved - Reserve the IP. Required.
|
|
||||||
* @param {Function} callback of the form `function (err, body, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.updateNetworkIp = function updateNetworkIp(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.string(opts.ip, 'opts.ip');
|
|
||||||
assert.bool(opts.reserved, 'opts.reserved');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
|
||||||
this.account, opts.id, opts.ip));
|
|
||||||
var data = {
|
|
||||||
reserved: opts.reserved
|
|
||||||
};
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'PUT',
|
|
||||||
path: endpoint,
|
|
||||||
data: data
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// <updatable network ip field> -> <expected typeof>
|
|
||||||
CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS = {
|
|
||||||
reserved: 'boolean'
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// --- Fabric VLANs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a network on a fabric VLAN.
|
|
||||||
*
|
|
||||||
* @param {Object} options object containing:
|
|
||||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
|
||||||
* - {String} name (required) A name to identify the network.
|
|
||||||
* - {String} subnet (required) CIDR description of the network.
|
|
||||||
* - {String} provision_start_ip (required) First assignable IP addr.
|
|
||||||
* - {String} provision_end_ip (required) Last assignable IP addr.
|
|
||||||
* - {String} gateway (optional) Gateway IP address.
|
|
||||||
* - {Array} resolvers (optional) DNS resolvers for hosts on network.
|
|
||||||
* - {Object} routes (optional) Static routes for hosts on network.
|
|
||||||
* - {String} description (optional)
|
|
||||||
* - {Boolean} internet_nat (optional) Whether to provision an Internet
|
|
||||||
* NAT on the gateway address (default: true).
|
|
||||||
* @param {Function} callback of the form f(err, vlan, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.createFabricNetwork =
|
|
||||||
function createFabricNetwork(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.string(opts.name, 'opts.name');
|
|
||||||
assert.string(opts.subnet, 'opts.subnet');
|
|
||||||
assert.string(opts.provision_start_ip, 'opts.provision_start_ip');
|
|
||||||
assert.string(opts.provision_end_ip, 'opts.provision_end_ip');
|
|
||||||
assert.optionalString(opts.gateway, 'opts.gateway');
|
|
||||||
assert.optionalArrayOfString(opts.resolvers, 'opts.resolvers');
|
|
||||||
assert.optionalObject(opts.routes, 'opts.routes');
|
|
||||||
assert.optionalBool(opts.internet_nat, 'opts.internet_nat');
|
|
||||||
|
|
||||||
var data = common.objCopy(opts);
|
|
||||||
var vlanId = data.vlan_id;
|
|
||||||
delete data.vlan_id;
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/fabrics/default/vlans/%d/networks', this.account,
|
|
||||||
vlanId),
|
|
||||||
data: data
|
|
||||||
}, function reqCb(err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all networks on a VLAN.
|
|
||||||
*
|
|
||||||
* Returns an array of objects.
|
|
||||||
*
|
|
||||||
* @param {Object} options object containing:
|
|
||||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
|
||||||
* @param {Function} callback of the form f(err, networks, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.listFabricNetworks =
|
|
||||||
function listFabricNetworks(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = format('/%s/fabrics/default/vlans/%d/networks',
|
|
||||||
this.account, opts.vlan_id);
|
|
||||||
this._passThrough(endpoint, opts, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a fabric network
|
|
||||||
*
|
|
||||||
* @param {Object} opts (object)
|
|
||||||
* - {String} id: The network id. Required.
|
|
||||||
* - {Integer} vlan_id: The VLAN id. Required.
|
|
||||||
* @param {Function} cb of the form `function (err, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.deleteFabricNetwork =
|
|
||||||
function deleteFabricNetwork(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'DELETE',
|
|
||||||
path: format('/%s/fabrics/default/vlans/%d/networks/%s', this.account,
|
|
||||||
opts.vlan_id, opts.id)
|
|
||||||
}, function (err, req, res) {
|
|
||||||
cb(err, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a VLAN on a fabric.
|
|
||||||
*
|
|
||||||
* @param {Object} options object containing:
|
|
||||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
|
||||||
* - {String} name (required) A name to identify the VLAN.
|
|
||||||
* - {String} description (optional)
|
|
||||||
* @param {Function} callback of the form f(err, vlan, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.createFabricVlan =
|
|
||||||
function createFabricVlan(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.string(opts.name, 'opts.name');
|
|
||||||
assert.optionalString(opts.description, 'opts.description');
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
vlan_id: opts.vlan_id
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
|
|
||||||
if (opts[attr] !== undefined)
|
|
||||||
data[attr] = opts[attr];
|
|
||||||
});
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/fabrics/default/vlans', this.account),
|
|
||||||
data: data
|
|
||||||
}, function reqCb(err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all the VLANs.
|
|
||||||
*
|
|
||||||
* Returns an array of objects.
|
|
||||||
*
|
|
||||||
* @param opts {Object} Options
|
|
||||||
* @param {Function} callback of the form f(err, vlans, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.listFabricVlans =
|
|
||||||
function listFabricVlans(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = format('/%s/fabrics/default/vlans', this.account);
|
|
||||||
this._passThrough(endpoint, opts, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a VLAN.
|
|
||||||
*
|
|
||||||
* @param {Integer} id: The VLAN id.
|
|
||||||
* @param {Function} callback of the form `function (err, vlan, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.getFabricVlan =
|
|
||||||
function getFabricVlan(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = format('/%s/fabrics/default/vlans/%d', this.account,
|
|
||||||
opts.vlan_id);
|
|
||||||
this._request(endpoint, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// <updatable account field> -> <expected typeof>
|
|
||||||
CloudApi.prototype.UPDATE_VLAN_FIELDS = {
|
|
||||||
name: 'string',
|
|
||||||
description: 'string'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a VLAN.
|
|
||||||
*
|
|
||||||
* @param {Object} opts object containing:
|
|
||||||
* - {Integer} id: The VLAN id. Required.
|
|
||||||
* - {String} name: The VLAN name. Optional.
|
|
||||||
* - {String} description: Description of the VLAN. Optional.
|
|
||||||
* @param {Function} callback of the form `function (err, vlan, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.updateFabricVlan =
|
|
||||||
function updateFabricVlan(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.optionalString(opts.rule, 'opts.name');
|
|
||||||
assert.optionalString(opts.description, 'opts.description');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
|
|
||||||
if (opts[attr] !== undefined)
|
|
||||||
data[attr] = opts[attr];
|
|
||||||
});
|
|
||||||
|
|
||||||
var vlanId = opts.vlan_id;
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/fabrics/default/vlans/%d', this.account, vlanId),
|
|
||||||
data: data
|
|
||||||
}, function onReq(err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a VLAN.
|
|
||||||
*
|
|
||||||
* @param {Object} opts (object)
|
|
||||||
* - {Integer} vlan_id: The vlan id. Required.
|
|
||||||
* @param {Function} cb of the form `function (err, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.deleteFabricVlan =
|
|
||||||
function deleteFabricVlan(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'DELETE',
|
|
||||||
path: format('/%s/fabrics/default/vlans/%d', this.account, opts.vlan_id)
|
|
||||||
}, function onReq(err, req, res) {
|
|
||||||
cb(err, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ---- datacenters
|
// ---- datacenters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1031,92 +820,6 @@ CloudApi.prototype.exportImage = function exportImage(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an image.
|
|
||||||
* <http://apidocs.joyent.com/cloudapi/#UpdateImage>
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* - {UUID} id Required. The id of the image to update.
|
|
||||||
* - {Object} fields Required. The fields to update in the image.
|
|
||||||
* @param {Function} cb of the form `function (err, body, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.updateImage = function updateImage(opts, cb) {
|
|
||||||
assert.uuid(opts.id, 'id');
|
|
||||||
assert.object(opts.fields, 'fields');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/images/%s?action=update', this.account, opts.id),
|
|
||||||
data: opts.fields
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
if (err) {
|
|
||||||
cb(err, null, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cb(null, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone an image.
|
|
||||||
* <http://apidocs.joyent.com/cloudapi/#CloneImage>
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* - {UUID} id Required. The id of the image to update.
|
|
||||||
* @param {Function} cb of the form `function (err, body, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.cloneImage = function cloneImage(opts, cb) {
|
|
||||||
assert.uuid(opts.id, 'id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/images/%s?action=clone', this.account, opts.id),
|
|
||||||
data: {}
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
if (err) {
|
|
||||||
cb(err, null, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cb(null, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import image from another datacenter in the same cloud.
|
|
||||||
* <http://apidocs.joyent.com/cloudapi/#ImportImageFromDatacenter>
|
|
||||||
*
|
|
||||||
* @param {Object} opts
|
|
||||||
* - {String} datacenter Required. The datacenter to import from.
|
|
||||||
* - {UUID} id Required. The id of the image to update.
|
|
||||||
* @param {Function} cb of the form `function (err, body, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.importImageFromDatacenter =
|
|
||||||
function importImageFromDatacenter(opts, cb) {
|
|
||||||
assert.string(opts.datacenter, 'datacenter');
|
|
||||||
assert.uuid(opts.id, 'id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var p = this._path(format('/%s/images', this.account), {
|
|
||||||
action: 'import-from-datacenter',
|
|
||||||
datacenter: opts.datacenter,
|
|
||||||
id: opts.id
|
|
||||||
});
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: p,
|
|
||||||
data: {}
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
if (err) {
|
|
||||||
cb(err, null, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cb(null, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for an image to go one of a set of specfic states.
|
* Wait for an image to go one of a set of specfic states.
|
||||||
*
|
*
|
||||||
@ -1196,12 +899,13 @@ CloudApi.prototype.getPackage = function getPackage(opts, cb) {
|
|||||||
/**
|
/**
|
||||||
* Get a machine by id.
|
* Get a machine by id.
|
||||||
*
|
*
|
||||||
|
* XXX add getCredentials equivalent
|
||||||
|
* XXX cloudapi docs don't doc the credentials=true option
|
||||||
|
*
|
||||||
* For backwards compat, calling with `getMachine(id, cb)` is allowed.
|
* For backwards compat, calling with `getMachine(id, cb)` is allowed.
|
||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - {UUID} id - Required. The machine id.
|
* - id {UUID} Required. The machine id.
|
||||||
* - {Boolean} credentials - Optional. Set to true to include generated
|
|
||||||
* credentials for this machine in `machine.metadata.credentials`.
|
|
||||||
* @param {Function} cb of the form `function (err, machine, res)`
|
* @param {Function} cb of the form `function (err, machine, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
||||||
@ -1210,18 +914,24 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
|||||||
}
|
}
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.uuid(opts.id, 'opts.id');
|
assert.uuid(opts.id, 'opts.id');
|
||||||
assert.optionalBool(opts.credentials, 'opts.credentials');
|
|
||||||
|
|
||||||
var query = {};
|
var endpoint = format('/%s/machines/%s', this.account, opts.id);
|
||||||
if (opts.credentials) {
|
this._request(endpoint, function (err, req, res, body) {
|
||||||
query.credentials = 'true';
|
|
||||||
}
|
|
||||||
var p = this._path(format('/%s/machines/%s', this.account, opts.id), query);
|
|
||||||
this._request(p, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
cb(err, body, res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a VNC connection to a HVM's console
|
||||||
|
*
|
||||||
|
* @param {String} uuid (required) The machine id.
|
||||||
|
* @param {Function} callback of the form `function (err, shed, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.getMachineVnc = function getMachineVnc(uuid, cb) {
|
||||||
|
var endpoint = format('/%s/machines/%s/vnc', this.account, uuid);
|
||||||
|
this._createWebSocket({path: endpoint}, cb);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* resize a machine by id.
|
* resize a machine by id.
|
||||||
*
|
*
|
||||||
@ -1333,6 +1043,7 @@ function enableMachineFirewall(uuid, callback) {
|
|||||||
return this._doMachine('enable_firewall', uuid, callback);
|
return this._doMachine('enable_firewall', uuid, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables machine firewall.
|
* Disables machine firewall.
|
||||||
*
|
*
|
||||||
@ -1344,28 +1055,6 @@ function disableMachineFirewall(uuid, callback) {
|
|||||||
return this._doMachine('disable_firewall', uuid, callback);
|
return this._doMachine('disable_firewall', uuid, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables machine deletion protection.
|
|
||||||
*
|
|
||||||
* @param {String} id (required) The machine id.
|
|
||||||
* @param {Function} callback of the form `function (err, null, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.enableMachineDeletionProtection =
|
|
||||||
function enableMachineDeletionProtection(uuid, callback) {
|
|
||||||
return this._doMachine('enable_deletion_protection', uuid, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables machine deletion protection.
|
|
||||||
*
|
|
||||||
* @param {String} id (required) The machine id.
|
|
||||||
* @param {Function} callback of the form `function (err, null, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.disableMachineDeletionProtection =
|
|
||||||
function disableMachineDeletionProtection(uuid, callback) {
|
|
||||||
return this._doMachine('disable_deletion_protection', uuid, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* internal function for start/stop/reboot/enable_firewall/disable_firewall
|
* internal function for start/stop/reboot/enable_firewall/disable_firewall
|
||||||
*/
|
*/
|
||||||
@ -1498,7 +1187,7 @@ CloudApi.prototype.createMachine = function createMachine(options, callback) {
|
|||||||
assert.optionalString(options.name, 'options.name');
|
assert.optionalString(options.name, 'options.name');
|
||||||
assert.uuid(options.image, 'options.image');
|
assert.uuid(options.image, 'options.image');
|
||||||
assert.uuid(options.package, 'options.package');
|
assert.uuid(options.package, 'options.package');
|
||||||
assert.optionalArray(options.networks, 'options.networks');
|
assert.optionalArrayOfUuid(options.networks, 'options.networks');
|
||||||
// TODO: assert the other fields
|
// TODO: assert the other fields
|
||||||
assert.func(callback, 'callback');
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
@ -1576,53 +1265,6 @@ function waitForMachineFirewallEnabled(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for a machine's `deletion_protection` field to go true or
|
|
||||||
* false/undefined.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* - {String} id: Required. The machine UUID.
|
|
||||||
* - {Boolean} state: Required. The desired `deletion_protection` state.
|
|
||||||
* - {Number} interval: Optional. Time (in ms) to poll.
|
|
||||||
* @param {Function} callback of the form f(err, machine, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.waitForDeletionProtectionEnabled =
|
|
||||||
function waitForDeletionProtectionEnabled(opts, cb) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.bool(opts.state, 'opts.state');
|
|
||||||
assert.optionalNumber(opts.interval, 'opts.interval');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var interval = opts.interval || 1000;
|
|
||||||
assert.ok(interval > 0, 'interval must be a positive number');
|
|
||||||
|
|
||||||
poll();
|
|
||||||
|
|
||||||
function poll() {
|
|
||||||
self.getMachine({
|
|
||||||
id: opts.id
|
|
||||||
}, function getMachineCb(err, machine, res) {
|
|
||||||
if (err) {
|
|
||||||
cb(err, null, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// !! converts an undefined to a false
|
|
||||||
if (opts.state === !!machine.deletion_protection) {
|
|
||||||
cb(null, machine, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(poll, interval);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// --- machine tags
|
// --- machine tags
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1931,150 +1573,6 @@ function deleteMachineSnapshot(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// --- NICs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a NIC on a network to an instance.
|
|
||||||
*
|
|
||||||
* @param {Object} options object containing:
|
|
||||||
* - {String} id (required) the instance id.
|
|
||||||
* - {String|Object} (required) network uuid or network object.
|
|
||||||
* @param {Function} callback of the form f(err, nic, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.addNic =
|
|
||||||
function addNic(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.ok(opts.network, 'opts.network');
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
network: opts.network
|
|
||||||
};
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'POST',
|
|
||||||
path: format('/%s/machines/%s/nics', this.account, opts.id),
|
|
||||||
data: data
|
|
||||||
}, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all NICs on an instance.
|
|
||||||
*
|
|
||||||
* Returns an array of objects.
|
|
||||||
*
|
|
||||||
* @param opts {Object} Options
|
|
||||||
* - {String} id (required) the instance id.
|
|
||||||
* @param {Function} callback of the form f(err, nics, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.listNics =
|
|
||||||
function listNics(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var endpoint = format('/%s/machines/%s/nics', this.account, opts.id);
|
|
||||||
this._passThrough(endpoint, opts, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a NIC on an instance.
|
|
||||||
*
|
|
||||||
* @param {Object} options object containing:
|
|
||||||
* - {UUID} id: The instance id. Required.
|
|
||||||
* - {String} mac: The NIC's MAC. Required.
|
|
||||||
* @param {Function} callback of the form `function (err, nic, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.getNic =
|
|
||||||
function getNic(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.string(opts.mac, 'opts.mac');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var mac = opts.mac.replace(/:/g, '');
|
|
||||||
var endpoint = format('/%s/machines/%s/nics/%s', this.account, opts.id,
|
|
||||||
mac);
|
|
||||||
this._request(endpoint, function (err, req, res, body) {
|
|
||||||
cb(err, body, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a NIC off an instance.
|
|
||||||
*
|
|
||||||
* @param {Object} opts (object)
|
|
||||||
* - {UUID} id: The instance id. Required.
|
|
||||||
* - {String} mac: The NIC's MAC. Required.
|
|
||||||
* @param {Function} cb of the form `function (err, res)`
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.removeNic =
|
|
||||||
function removeNic(opts, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.string(opts.mac, 'opts.mac');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var mac = opts.mac.replace(/:/g, '');
|
|
||||||
|
|
||||||
this._request({
|
|
||||||
method: 'DELETE',
|
|
||||||
path: format('/%s/machines/%s/nics/%s', this.account, opts.id, mac)
|
|
||||||
}, function (err, req, res) {
|
|
||||||
cb(err, res);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for a machine's nic to go one of a set of specfic states.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* - {String} id {required} machine id
|
|
||||||
* - {String} mac {required} mac for new nic
|
|
||||||
* - {Array of String} states - desired state
|
|
||||||
* - {Number} interval (optional) - time in ms to poll
|
|
||||||
* @param {Function} callback of the form f(err, nic, res).
|
|
||||||
*/
|
|
||||||
CloudApi.prototype.waitForNicStates =
|
|
||||||
function waitForNicStates(opts, cb) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.uuid(opts.id, 'opts.id');
|
|
||||||
assert.string(opts.mac, 'opts.mac');
|
|
||||||
assert.arrayOfString(opts.states, 'opts.states');
|
|
||||||
assert.optionalNumber(opts.interval, 'opts.interval');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var interval = opts.interval || 1000;
|
|
||||||
assert.ok(interval > 0, 'interval must be a positive number');
|
|
||||||
|
|
||||||
poll();
|
|
||||||
|
|
||||||
function poll() {
|
|
||||||
self.getNic({
|
|
||||||
id: opts.id,
|
|
||||||
mac: opts.mac
|
|
||||||
}, function onPoll(err, nic, res) {
|
|
||||||
if (err) {
|
|
||||||
cb(err, null, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (opts.states.indexOf(nic.state) !== -1) {
|
|
||||||
cb(null, nic, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(poll, interval);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// --- firewall rules
|
// --- firewall rules
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
185
lib/common.js
185
lib/common.js
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Joyent, Inc.
|
* Copyright (c) 2017, Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
@ -24,8 +24,7 @@ var wordwrap = require('wordwrap');
|
|||||||
|
|
||||||
var errors = require('./errors'),
|
var errors = require('./errors'),
|
||||||
InternalError = errors.InternalError;
|
InternalError = errors.InternalError;
|
||||||
var NETWORK_OBJECT_FIELDS =
|
|
||||||
require('./constants').NETWORK_OBJECT_FIELDS;
|
|
||||||
|
|
||||||
|
|
||||||
// ---- support stuff
|
// ---- support stuff
|
||||||
@ -618,9 +617,9 @@ function promptYesNo(opts_, cb) {
|
|||||||
stdin.on('data', onData);
|
stdin.on('data', onData);
|
||||||
|
|
||||||
function postInput() {
|
function postInput() {
|
||||||
stdout.write('\n');
|
|
||||||
stdin.setRawMode(false);
|
stdin.setRawMode(false);
|
||||||
stdin.pause();
|
stdin.pause();
|
||||||
|
stdin.write('\n');
|
||||||
stdin.removeListener('data', onData);
|
stdin.removeListener('data', onData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1302,177 +1301,6 @@ function argvFromLine(line) {
|
|||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Read stdin in and callback with it as a string
|
|
||||||
*
|
|
||||||
* @param {Function} cb - callback in the form `function (str) {}`
|
|
||||||
*/
|
|
||||||
function readStdin(cb) {
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var stdin = '';
|
|
||||||
process.stdin.setEncoding('utf8');
|
|
||||||
process.stdin.resume();
|
|
||||||
process.stdin.on('data', function stdinOnData(chunk) {
|
|
||||||
stdin += chunk;
|
|
||||||
});
|
|
||||||
process.stdin.on('end', function stdinOnEnd() {
|
|
||||||
cb(stdin);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Validate an object of values against an object of types.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* var input = {
|
|
||||||
* foo: 'hello',
|
|
||||||
* bar: 42,
|
|
||||||
* baz: true
|
|
||||||
* };
|
|
||||||
* var valid = {
|
|
||||||
* foo: 'string',
|
|
||||||
* bar: 'number',
|
|
||||||
* baz: 'boolean'
|
|
||||||
* }
|
|
||||||
* validateObject(input, valid);
|
|
||||||
* // no error is thrown
|
|
||||||
*
|
|
||||||
* All keys in `input` are check for their matching counterparts in `valid`.
|
|
||||||
* If the key is not found in `valid`, or the type specified for the key in
|
|
||||||
* `valid` doesn't match the type of the value in `input` an error is thrown.
|
|
||||||
* Also an error is thrown (optionally, enabled by default) if the input object
|
|
||||||
* is empty. Note that any keys found in `valid` not found in `input` are not
|
|
||||||
* considered an error.
|
|
||||||
*
|
|
||||||
* @param {Object} input - Required. Input object of values.
|
|
||||||
* @param {Object} valid - Required. Validation object of types.
|
|
||||||
* @param {Object} opts: Optional
|
|
||||||
* - @param {Boolean} allowEmptyInput - don't consider an empty
|
|
||||||
* input object an error
|
|
||||||
* @throws {Error} if the input object contains a key not found in the
|
|
||||||
* validation object
|
|
||||||
*/
|
|
||||||
function validateObject(input, valid, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
assert.object(input, 'input');
|
|
||||||
assert.object(valid, 'valid');
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.optionalBool(opts.allowEmptyInput, 'opts.allowEmptyInput');
|
|
||||||
|
|
||||||
var validFields = Object.keys(valid).sort().join(', ');
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
Object.keys(input).forEach(function (key) {
|
|
||||||
var value = input[key];
|
|
||||||
var type = valid[key];
|
|
||||||
|
|
||||||
if (!type) {
|
|
||||||
throw new errors.UsageError(format('unknown or ' +
|
|
||||||
'unupdateable field: %s (updateable fields are: %s)',
|
|
||||||
key, validFields));
|
|
||||||
}
|
|
||||||
assert.string(type, 'type');
|
|
||||||
|
|
||||||
if (typeof (value) !== type) {
|
|
||||||
throw new errors.UsageError(format('field "%s" must be ' +
|
|
||||||
'of type "%s", but got a value of type "%s"',
|
|
||||||
key, type, typeof (value)));
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (i === 0 && !opts.allowEmptyInput) {
|
|
||||||
throw new errors.UsageError('Input object must not be empty');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert an IPv4 address (as a string) to a number
|
|
||||||
*/
|
|
||||||
function ipv4ToLong(ip) {
|
|
||||||
var l = 0;
|
|
||||||
var spl;
|
|
||||||
|
|
||||||
assert.string(ip, 'ip');
|
|
||||||
spl = ip.split('.');
|
|
||||||
assert.equal(spl.length, 4, 'ip octet length');
|
|
||||||
|
|
||||||
spl.forEach(function processIpOctet(octet) {
|
|
||||||
octet = parseInt(octet, 10);
|
|
||||||
|
|
||||||
assert.number(octet, 'octet');
|
|
||||||
assert(octet >= 0, 'octet >= 0');
|
|
||||||
assert(octet < 256, 'octet < 256');
|
|
||||||
|
|
||||||
l <<= 8;
|
|
||||||
l += octet;
|
|
||||||
});
|
|
||||||
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parse the input from the `--nics <nic>` CLI argument.
|
|
||||||
*
|
|
||||||
* @param a {Array} The array of strings formatted as key=value
|
|
||||||
* ex: ['ipv4_uuid=1234', 'ipv4_ips=1.2.3.4|5.6.7.8']
|
|
||||||
* @return {Object} A network object. From the example above:
|
|
||||||
* {
|
|
||||||
* "ipv4_uuid": 1234,
|
|
||||||
* "ipv4_ips": [
|
|
||||||
* "1.2.3.4",
|
|
||||||
* "5.6.7.8"
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* Note: "1234" is used as the UUID for this example, but would actually cause
|
|
||||||
* `parseNicStr` to throw as it is not a valid UUID.
|
|
||||||
*/
|
|
||||||
function parseNicStr(nic) {
|
|
||||||
assert.arrayOfString(nic);
|
|
||||||
|
|
||||||
var obj = objFromKeyValueArgs(nic, {
|
|
||||||
disableDotted: true,
|
|
||||||
typeHintFromKey: NETWORK_OBJECT_FIELDS,
|
|
||||||
validKeys: Object.keys(NETWORK_OBJECT_FIELDS)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!obj.ipv4_uuid) {
|
|
||||||
throw new errors.UsageError(
|
|
||||||
'ipv4_uuid must be specified in network object');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.ipv4_ips) {
|
|
||||||
obj.ipv4_ips = obj.ipv4_ips.split('|');
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.uuid(obj.ipv4_uuid, 'obj.ipv4_uuid');
|
|
||||||
assert.optionalArrayOfString(obj.ipv4_ips, 'obj.ipv4_ips');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Only 1 IP address may be specified at this time. In the future, this
|
|
||||||
* limitation should be removed.
|
|
||||||
*/
|
|
||||||
if (obj.ipv4_ips && obj.ipv4_ips.length !== 1) {
|
|
||||||
throw new errors.UsageError('only 1 ipv4_ip may be specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return a short image string that represents the given image object.
|
|
||||||
*
|
|
||||||
* @param img {Object} The image object.
|
|
||||||
* @returns {String} A network object. E.g.
|
|
||||||
* 'a6cf222d-73f4-414c-a427-5c238ef8e1b7 (jillmin@1.0.0)'
|
|
||||||
*/
|
|
||||||
function imageRepr(img) {
|
|
||||||
assert.object(img);
|
|
||||||
|
|
||||||
return format('%s (%s@%s)', img.id, img.name, img.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
@ -1511,11 +1339,6 @@ module.exports = {
|
|||||||
objFromKeyValueArgs: objFromKeyValueArgs,
|
objFromKeyValueArgs: objFromKeyValueArgs,
|
||||||
argvFromLine: argvFromLine,
|
argvFromLine: argvFromLine,
|
||||||
jsonPredFromKv: jsonPredFromKv,
|
jsonPredFromKv: jsonPredFromKv,
|
||||||
monotonicTimeDiffMs: monotonicTimeDiffMs,
|
monotonicTimeDiffMs: monotonicTimeDiffMs
|
||||||
readStdin: readStdin,
|
|
||||||
validateObject: validateObject,
|
|
||||||
ipv4ToLong: ipv4ToLong,
|
|
||||||
parseNicStr: parseNicStr,
|
|
||||||
imageRepr: imageRepr
|
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
@ -221,18 +221,18 @@ function validateProfile(profile, profilePath) {
|
|||||||
try {
|
try {
|
||||||
assert.string(profile.name, 'profile.name');
|
assert.string(profile.name, 'profile.name');
|
||||||
assert.string(profile.url,
|
assert.string(profile.url,
|
||||||
profile.name === 'env' ? 'SC_URL' : 'profile.url');
|
profile.name === 'env' ? 'TRITON_URL or SDC_URL' : 'profile.url');
|
||||||
assert.string(profile.account,
|
assert.string(profile.account,
|
||||||
profile.name === 'env' ? 'SC_ACCOUNT'
|
profile.name === 'env' ? 'TRITON_ACCOUNT or SDC_ACCOUNT'
|
||||||
: 'profile.account');
|
: 'profile.account');
|
||||||
assert.string(profile.keyId,
|
assert.string(profile.keyId,
|
||||||
profile.name === 'env' ? 'SC_KEY_ID'
|
profile.name === 'env' ? 'TRITON_KEY_ID or SDC_KEY_ID'
|
||||||
: 'profile.keyId');
|
: 'profile.keyId');
|
||||||
assert.optionalBool(profile.insecure,
|
assert.optionalBool(profile.insecure,
|
||||||
profile.name === 'env' ? 'SC_INSECURE'
|
profile.name === 'env' ? 'TRITON_INSECURE or SDC_INSECURE'
|
||||||
: 'profile.insecure');
|
: 'profile.insecure');
|
||||||
assert.optionalString(profile.user,
|
assert.optionalString(profile.user,
|
||||||
profile.name === 'env' ? 'SC_USER'
|
profile.name === 'env' ? 'TRITON_USER or SDC_USER'
|
||||||
: 'profile.user');
|
: 'profile.user');
|
||||||
assert.optionalString(profile.actAsAccount, 'profile.actAsAccount');
|
assert.optionalString(profile.actAsAccount, 'profile.actAsAccount');
|
||||||
assert.optionalArrayOfString(profile.roles, 'profile.roles');
|
assert.optionalArrayOfString(profile.roles, 'profile.roles');
|
||||||
@ -276,18 +276,21 @@ function _loadEnvProfile(profileOverrides) {
|
|||||||
name: 'env'
|
name: 'env'
|
||||||
};
|
};
|
||||||
|
|
||||||
envProfile.account = process.env.SC_ACCOUNT;
|
envProfile.account = process.env.TRITON_ACCOUNT || process.env.SDC_ACCOUNT;
|
||||||
var user = process.env.SC_USER;
|
var user = process.env.TRITON_USER || process.env.SDC_USER;
|
||||||
if (user) {
|
if (user) {
|
||||||
envProfile.user = user;
|
envProfile.user = user;
|
||||||
}
|
}
|
||||||
envProfile.url = process.env.SC_URL;
|
envProfile.url = process.env.TRITON_URL || process.env.SDC_URL;
|
||||||
envProfile.keyId = process.env.SC_KEY_ID;
|
envProfile.keyId = process.env.TRITON_KEY_ID || process.env.SDC_KEY_ID;
|
||||||
|
|
||||||
if (process.env.SC_TLS_INSECURE) {
|
if (process.env.TRITON_TLS_INSECURE) {
|
||||||
envProfile.insecure = common.boolFromString(
|
envProfile.insecure = common.boolFromString(
|
||||||
process.env.SC_TLS_INSECURE, undefined, 'SC_TLS_INSECURE');
|
process.env.TRITON_TLS_INSECURE, undefined, 'TRITON_TLS_INSECURE');
|
||||||
} else if (process.env.SC_TESTING) {
|
} else if (process.env.SDC_TLS_INSECURE) {
|
||||||
|
envProfile.insecure = common.boolFromString(
|
||||||
|
process.env.SDC_TLS_INSECURE, undefined, 'SDC_TLS_INSECURE');
|
||||||
|
} else if (process.env.SDC_TESTING) {
|
||||||
// For compatibility with the legacy behavior of the smartdc
|
// For compatibility with the legacy behavior of the smartdc
|
||||||
// tools, *any* set value but the empty string is considered true.
|
// tools, *any* set value but the empty string is considered true.
|
||||||
envProfile.insecure = true;
|
envProfile.insecure = true;
|
||||||
@ -296,11 +299,12 @@ function _loadEnvProfile(profileOverrides) {
|
|||||||
for (var attr in profileOverrides) {
|
for (var attr in profileOverrides) {
|
||||||
envProfile[attr] = profileOverrides[attr];
|
envProfile[attr] = profileOverrides[attr];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If missing any of the required vars, then there is no env profile.
|
* If none of the above envvars are defined, then there is no env profile.
|
||||||
*/
|
*/
|
||||||
if (!envProfile.account || !envProfile.url || !envProfile.keyId) {
|
if (!envProfile.account && !envProfile.user && !envProfile.url &&
|
||||||
|
!envProfile.keyId)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
validateProfile(envProfile, 'environment variables');
|
validateProfile(envProfile, 'environment variables');
|
||||||
@ -345,7 +349,7 @@ function loadProfile(opts) {
|
|||||||
var envProfile = _loadEnvProfile(opts.profileOverrides);
|
var envProfile = _loadEnvProfile(opts.profileOverrides);
|
||||||
if (!envProfile) {
|
if (!envProfile) {
|
||||||
throw new errors.ConfigError('could not load "env" profile '
|
throw new errors.ConfigError('could not load "env" profile '
|
||||||
+ '(missing SC_* environment variables)');
|
+ '(missing TRITON_*, or SDC_*, environment variables)');
|
||||||
}
|
}
|
||||||
return envProfile;
|
return envProfile;
|
||||||
} else if (!opts.configDir) {
|
} else if (!opts.configDir) {
|
||||||
@ -362,11 +366,10 @@ function loadProfile(opts) {
|
|||||||
function loadAllProfiles(opts) {
|
function loadAllProfiles(opts) {
|
||||||
assert.string(opts.configDir, 'opts.configDir');
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
assert.object(opts.log, 'opts.log');
|
assert.object(opts.log, 'opts.log');
|
||||||
assert.optionalObject(opts.profileOverrides, 'opts.profileOverrides');
|
|
||||||
|
|
||||||
var profiles = [];
|
var profiles = [];
|
||||||
|
|
||||||
var envProfile = _loadEnvProfile(opts.profileOverrides);
|
var envProfile = _loadEnvProfile();
|
||||||
if (envProfile) {
|
if (envProfile) {
|
||||||
profiles.push(envProfile);
|
profiles.push(envProfile);
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ var mod_path = require('path');
|
|||||||
* For *testing* only, we allow override of this dir.
|
* For *testing* only, we allow override of this dir.
|
||||||
*/
|
*/
|
||||||
var CLI_CONFIG_DIR;
|
var CLI_CONFIG_DIR;
|
||||||
if (process.env.SCTEST_CLI_CONFIG_DIR) {
|
if (process.env.TRITONTEST_CLI_CONFIG_DIR) {
|
||||||
CLI_CONFIG_DIR = process.env.SCTEST_CLI_CONFIG_DIR;
|
CLI_CONFIG_DIR = process.env.TRITONTEST_CLI_CONFIG_DIR;
|
||||||
} else if (process.platform === 'win32') {
|
} else if (process.platform === 'win32') {
|
||||||
/*
|
/*
|
||||||
* For better or worse we are using APPDATA (i.e. the *Roaming* AppData
|
* For better or worse we are using APPDATA (i.e. the *Roaming* AppData
|
||||||
@ -41,23 +41,16 @@ if (process.env.SCTEST_CLI_CONFIG_DIR) {
|
|||||||
* TODO: We should likely separate out the *cache* subdir to
|
* TODO: We should likely separate out the *cache* subdir to
|
||||||
* machine-specific data dir.
|
* machine-specific data dir.
|
||||||
*/
|
*/
|
||||||
CLI_CONFIG_DIR = mod_path.resolve(process.env.APPDATA, 'Spearhead', 'sc');
|
CLI_CONFIG_DIR = mod_path.resolve(process.env.APPDATA, 'Joyent', 'Triton');
|
||||||
} else {
|
} else {
|
||||||
CLI_CONFIG_DIR = mod_path.resolve(process.env.HOME, '.spearhead');
|
CLI_CONFIG_DIR = mod_path.resolve(process.env.HOME, '.triton');
|
||||||
}
|
}
|
||||||
|
|
||||||
// <Network Object Key> -> <expected typeof>
|
|
||||||
var NETWORK_OBJECT_FIELDS = {
|
|
||||||
ipv4_uuid: 'string',
|
|
||||||
ipv4_ips: 'string'
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ---- exports
|
// ---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CLI_CONFIG_DIR: CLI_CONFIG_DIR,
|
CLI_CONFIG_DIR: CLI_CONFIG_DIR
|
||||||
NETWORK_OBJECT_FIELDS: NETWORK_OBJECT_FIELDS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,8 +72,12 @@ function do_update(subcmd, opts, args, callback) {
|
|||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
ctx.data = JSON.parse(stdin);
|
ctx.data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -88,18 +92,36 @@ function do_update(subcmd, opts, args, callback) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
function validateIt(ctx, next) {
|
function validateIt(ctx, next) {
|
||||||
try {
|
var keys = Object.keys(ctx.data);
|
||||||
common.validateObject(ctx.data, UPDATE_ACCOUNT_FIELDS);
|
for (var i = 0; i < keys.length; i++) {
|
||||||
} catch (e) {
|
var key = keys[i];
|
||||||
next(e);
|
var value = ctx.data[key];
|
||||||
|
var type = UPDATE_ACCOUNT_FIELDS[key];
|
||||||
|
if (!type) {
|
||||||
|
next(new errors.UsageError(format('unknown or ' +
|
||||||
|
'unupdateable field: %s (updateable fields are: %s)',
|
||||||
|
key,
|
||||||
|
Object.keys(UPDATE_ACCOUNT_FIELDS).sort().join(', '))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof (value) !== type) {
|
||||||
|
next(new errors.UsageError(format('field "%s" must be ' +
|
||||||
|
'of type "%s", but got a value of type "%s"', key,
|
||||||
|
type, typeof (value))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
function updateAway(ctx, next) {
|
function updateAway(ctx, next) {
|
||||||
var keys = Object.keys(ctx.data);
|
var keys = Object.keys(ctx.data);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
console.log('No fields given for account update');
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tritonapi.cloudapi.updateAccount(ctx.data, function (err) {
|
tritonapi.cloudapi.updateAccount(ctx.data, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -23,7 +23,7 @@ function AccountCLI(top) {
|
|||||||
name: top.name + ' account',
|
name: top.name + ' account',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'Get and update your Spearhead account.'
|
'Get and update your Triton account.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
|
@ -20,7 +20,7 @@ function do_create(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_create.help = 'A shortcut for "spearhead instance create".\n' + targ.help;
|
do_create.help = 'A shortcut for "triton instance create".\n' + targ.help;
|
||||||
do_create.helpOpts = targ.helpOpts;
|
do_create.helpOpts = targ.helpOpts;
|
||||||
do_create.synopses = targ.synopses;
|
do_create.synopses = targ.synopses;
|
||||||
do_create.options = targ.options;
|
do_create.options = targ.options;
|
||||||
|
@ -20,7 +20,7 @@ function do_delete(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_delete.help = 'A shortcut for "spearhead instance delete".\n' + targ.help;
|
do_delete.help = 'A shortcut for "triton instance delete".\n' + targ.help;
|
||||||
do_delete.synopses = targ.synopses;
|
do_delete.synopses = targ.synopses;
|
||||||
do_delete.options = targ.options;
|
do_delete.options = targ.options;
|
||||||
do_delete.completionArgtypes = targ.completionArgtypes;
|
do_delete.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -74,17 +74,17 @@ function do_env(subcmd, opts, args, cb) {
|
|||||||
p('# triton');
|
p('# triton');
|
||||||
if (opts.unset) {
|
if (opts.unset) {
|
||||||
[
|
[
|
||||||
'SC_PROFILE',
|
'TRITON_PROFILE',
|
||||||
'SC_URL',
|
'TRITON_URL',
|
||||||
'SC_ACCOUNT',
|
'TRITON_ACCOUNT',
|
||||||
'SC_USER',
|
'TRITON_USER',
|
||||||
'SC_KEY_ID',
|
'TRITON_KEY_ID',
|
||||||
'SC_TLS_INSECURE'
|
'TRITON_TLS_INSECURE'
|
||||||
].forEach(function (key) {
|
].forEach(function (key) {
|
||||||
p('unset %s', key);
|
p('unset %s', key);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
p('export SC_PROFILE="%s"', profile.name);
|
p('export TRITON_PROFILE="%s"', profile.name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'docker':
|
case 'docker':
|
||||||
@ -160,11 +160,10 @@ function do_env(subcmd, opts, args, cb) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
p('# Run this command to configure your shell:');
|
p('# Run this command to configure your shell:');
|
||||||
p('# eval "$(spearhead env%s%s)"',
|
p('# eval "$(triton env%s%s)"',
|
||||||
(shortOpts ? ' -'+shortOpts : ''),
|
(shortOpts ? ' -'+shortOpts : ''),
|
||||||
(profile.name === this.tritonapi.profile.name
|
(profile.name === this.tritonapi.profile.name
|
||||||
? '' : ' ' + profile.name));
|
? '' : ' ' + profile.name));
|
||||||
cb();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do_env.options = [
|
do_env.options = [
|
||||||
@ -180,7 +179,7 @@ do_env.options = [
|
|||||||
names: ['triton', 't'],
|
names: ['triton', 't'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Emit environment commands for node-triton itself (i.e. the ' +
|
help: 'Emit environment commands for node-triton itself (i.e. the ' +
|
||||||
'"SC_PROFILE" variable).'
|
'"TRITON_PROFILE" variable).'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['docker', 'd'],
|
names: ['docker', 'd'],
|
||||||
@ -210,8 +209,8 @@ do_env.help = [
|
|||||||
'Emit shell commands to setup environment.',
|
'Emit shell commands to setup environment.',
|
||||||
'',
|
'',
|
||||||
'Supported "clients" here are: node-smartdc (i.e. the `sdc-*` tools),',
|
'Supported "clients" here are: node-smartdc (i.e. the `sdc-*` tools),',
|
||||||
'node-triton and spearhead-node. By default this emits the environment ',
|
'and node-triton itself. By default this emits the environment for all',
|
||||||
'for all supported tools. Use options to be specific.',
|
'supported tools. Use options to be specific.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
@ -220,7 +219,7 @@ do_env.help = [
|
|||||||
'clients. If PROFILE is not given, the current profile is used.',
|
'clients. If PROFILE is not given, the current profile is used.',
|
||||||
'',
|
'',
|
||||||
'The following Bash function can be added to one\'s "~/.bashrc" to quickly',
|
'The following Bash function can be added to one\'s "~/.bashrc" to quickly',
|
||||||
'change between Spearhead profiles:',
|
'change between Triton profiles:',
|
||||||
' triton-select () { eval "$(triton env $1)"; }',
|
' triton-select () { eval "$(triton env $1)"; }',
|
||||||
'for example:',
|
'for example:',
|
||||||
' $ triton-select west1',
|
' $ triton-select west1',
|
||||||
|
@ -80,7 +80,7 @@ do_create.options = [
|
|||||||
names: ['disabled', 'd'],
|
names: ['disabled', 'd'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Disable the created firewall rule. By default a created '
|
help: 'Disable the created firewall rule. By default a created '
|
||||||
+ 'firewall rule is enabled. Use "spearhead fwrule enable" '
|
+ 'firewall rule is enabled. Use "triton fwrule enable" '
|
||||||
+ 'to enable it later.'
|
+ 'to enable it later.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -102,10 +102,10 @@ do_create.help = [
|
|||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Examples:',
|
'Examples:',
|
||||||
' # Allow SSH access from any IP to all instances in a datacenter.',
|
' # 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"',
|
' triton fwrule create -D "ssh" "FROM any TO all vms ALLOW tcp PORT 22"',
|
||||||
'',
|
'',
|
||||||
' # Allow SSH access to a specific instance.',
|
' # Allow SSH access to a specific instance.',
|
||||||
' spearhead fwrule create \\',
|
' triton fwrule create \\',
|
||||||
' "FROM any TO vm ba2c95e9-1cdf-4295-8253-3fee371374d9 ALLOW tcp PORT 22"'
|
' "FROM any TO vm ba2c95e9-1cdf-4295-8253-3fee371374d9 ALLOW tcp PORT 22"'
|
||||||
// TODO: link to
|
// TODO: link to
|
||||||
// https://github.com/joyent/sdc-fwrule/blob/master/docs/examples.md
|
// https://github.com/joyent/sdc-fwrule/blob/master/docs/examples.md
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2018 Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton fwrule instances ...`
|
* `triton fwrule instances ...`
|
||||||
*/
|
*/
|
||||||
@ -111,11 +111,9 @@ function do_instances(subcmd, opts, args, cb) {
|
|||||||
common.uuidToShortId(inst.image);
|
common.uuidToShortId(inst.image);
|
||||||
inst.shortid = inst.id.split('-', 1)[0];
|
inst.shortid = inst.id.split('-', 1)[0];
|
||||||
var flags = [];
|
var flags = [];
|
||||||
if (inst.brand === 'bhyve') flags.push('B');
|
|
||||||
if (inst.docker) flags.push('D');
|
if (inst.docker) flags.push('D');
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
if (inst.deletion_protection) flags.push('P');
|
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -161,11 +159,9 @@ do_instances.help = [
|
|||||||
'for convenience):',
|
'for convenience):',
|
||||||
' shortid* A short ID prefix.',
|
' shortid* A short ID prefix.',
|
||||||
' flags* Single letter flags summarizing some fields:',
|
' flags* Single letter flags summarizing some fields:',
|
||||||
' "B" the brand is "bhyve"',
|
|
||||||
' "D" docker instance',
|
' "D" docker instance',
|
||||||
' "F" firewall is enabled',
|
' "F" firewall is enabled',
|
||||||
' "K" the brand is "kvm"',
|
' "K" the brand is "kvm"',
|
||||||
' "P" deletion protected',
|
|
||||||
' age* Approximate time since created, e.g. 1y, 2w.',
|
' age* Approximate time since created, e.g. 1y, 2w.',
|
||||||
' img* The image "name@version", if available, else its',
|
' img* The image "name@version", if available, else its',
|
||||||
' "shortid".'
|
' "shortid".'
|
||||||
|
@ -84,7 +84,14 @@ function do_update(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
common.readStdin(function gotStdin(stdin) {
|
var stdin = '';
|
||||||
|
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
ctx.data = JSON.parse(stdin);
|
ctx.data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -100,13 +107,33 @@ function do_update(subcmd, opts, args, cb) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
function validateIt(ctx, next) {
|
function validateIt(ctx, next) {
|
||||||
try {
|
var keys = Object.keys(ctx.data);
|
||||||
common.validateObject(ctx.data, UPDATE_FWRULE_FIELDS);
|
|
||||||
} catch (e) {
|
if (keys.length === 0) {
|
||||||
next(e);
|
console.log('No fields given for firewall rule update');
|
||||||
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var key = keys[i];
|
||||||
|
var value = ctx.data[key];
|
||||||
|
var type = UPDATE_FWRULE_FIELDS[key];
|
||||||
|
if (!type) {
|
||||||
|
next(new errors.UsageError(format('unknown or ' +
|
||||||
|
'unupdateable field: %s (updateable fields are: %s)',
|
||||||
|
key,
|
||||||
|
Object.keys(UPDATE_FWRULE_FIELDS).sort().join(', '))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (value) !== type) {
|
||||||
|
next(new errors.UsageError(format('field "%s" must be ' +
|
||||||
|
'of type "%s", but got a value of type "%s"', key,
|
||||||
|
type, typeof (value))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ function FirewallRuleCLI(top) {
|
|||||||
|
|
||||||
Cmdln.call(this, {
|
Cmdln.call(this, {
|
||||||
name: top.name + ' fwrule',
|
name: top.name + ' fwrule',
|
||||||
desc: 'List and manage Spearhead firewall rules.',
|
desc: 'List and manage Triton firewall rules.',
|
||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
'list',
|
'list',
|
||||||
|
@ -20,7 +20,7 @@ function do_fwrules(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_fwrules.help = 'A shortcut for "spearhead fwrule list".\n' + targ.help;
|
do_fwrules.help = 'A shortcut for "triton fwrule list".\n' + targ.help;
|
||||||
do_fwrules.synopses = targ.synopses;
|
do_fwrules.synopses = targ.synopses;
|
||||||
do_fwrules.options = targ.options;
|
do_fwrules.options = targ.options;
|
||||||
do_fwrules.completionArgtypes = targ.completionArgtypes;
|
do_fwrules.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -31,11 +31,7 @@ function do_get(subcmd, opts, args, callback) {
|
|||||||
callback(setupErr);
|
callback(setupErr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var getOpts = {
|
tritonapi.getImage(args[0], function onRes(err, img) {
|
||||||
name: args[0],
|
|
||||||
excludeInactive: !opts.all
|
|
||||||
};
|
|
||||||
tritonapi.getImage(getOpts, function onRes(err, img) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
@ -60,15 +56,6 @@ do_get.options = [
|
|||||||
names: ['json', 'j'],
|
names: ['json', 'j'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'JSON stream output.'
|
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.'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -5,15 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2018 Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton image list ...`
|
* `triton image list ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
var vasync = require('vasync');
|
|
||||||
|
|
||||||
var common = require('../common');
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
@ -69,45 +67,17 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
listOpts.state = 'all';
|
listOpts.state = 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var tritonapi = this.top.tritonapi;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
vasync.pipeline({ arg: {}, funcs: [
|
if (setupErr) {
|
||||||
function setupTritonApi(_, next) {
|
callback(setupErr);
|
||||||
common.cliSetupTritonApi({cli: self.top}, next);
|
return;
|
||||||
},
|
}
|
||||||
function getImages(ctx, next) {
|
|
||||||
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
|
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
return callback(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) {
|
if (opts.json) {
|
||||||
common.jsonStream(imgs);
|
common.jsonStream(imgs);
|
||||||
} else {
|
} else {
|
||||||
@ -129,20 +99,6 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
if (img.origin) flags.push('I');
|
if (img.origin) flags.push('I');
|
||||||
if (img['public']) flags.push('P');
|
if (img['public']) flags.push('P');
|
||||||
if (img.state !== 'active') flags.push('X');
|
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;
|
img.flags = flags.length ? flags.join('') : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,9 +108,9 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
sort: sort
|
sort: sort
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
next();
|
callback();
|
||||||
}
|
});
|
||||||
]}, callback);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
do_list.options = [
|
do_list.options = [
|
||||||
@ -185,6 +141,7 @@ do_list.help = [
|
|||||||
'',
|
'',
|
||||||
'Note: Currently, *docker* images are not included in this endpoint\'s responses.',
|
'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.',
|
'You must use `docker images` against the Docker service for this data center.',
|
||||||
|
'See <https://apidocs.joyent.com/docker>.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
@ -200,8 +157,6 @@ do_list.help = [
|
|||||||
' shortid* A short ID prefix.',
|
' shortid* A short ID prefix.',
|
||||||
' flags* Single letter flags summarizing some fields:',
|
' flags* Single letter flags summarizing some fields:',
|
||||||
' "P" image is public',
|
' "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)',
|
' "I" an incremental image (i.e. has an origin)',
|
||||||
' "X" has a state *other* than "active"',
|
' "X" has a state *other* than "active"',
|
||||||
' pubdate* Short form of "published_at" with just the date',
|
' pubdate* Short form of "published_at" with just the date',
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -123,7 +123,7 @@ do_wait.help = [
|
|||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Where "states" is a comma-separated list of target instance states,',
|
'Where "states" is a comma-separated list of target instance states,',
|
||||||
'by default "active,failed". In other words, "spearhead img wait foo0" will',
|
'by default "active,failed". In other words, "triton img wait foo0" will',
|
||||||
'wait for image "foo0" to complete creation.'
|
'wait for image "foo0" to complete creation.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton image ...`
|
* `triton image ...`
|
||||||
*/
|
*/
|
||||||
@ -23,7 +23,7 @@ function ImageCLI(top) {
|
|||||||
name: top.name + ' image',
|
name: top.name + ' image',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and manage Spearhead images.'
|
'List and manage Triton images.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -33,13 +33,9 @@ function ImageCLI(top) {
|
|||||||
'help',
|
'help',
|
||||||
'list',
|
'list',
|
||||||
'get',
|
'get',
|
||||||
'clone',
|
|
||||||
'copy',
|
|
||||||
'create',
|
'create',
|
||||||
'delete',
|
'delete',
|
||||||
'export',
|
'export',
|
||||||
'share',
|
|
||||||
'unshare',
|
|
||||||
'wait'
|
'wait'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -53,13 +49,9 @@ ImageCLI.prototype.init = function init(opts, args, cb) {
|
|||||||
|
|
||||||
ImageCLI.prototype.do_list = require('./do_list');
|
ImageCLI.prototype.do_list = require('./do_list');
|
||||||
ImageCLI.prototype.do_get = require('./do_get');
|
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_create = require('./do_create');
|
||||||
ImageCLI.prototype.do_delete = require('./do_delete');
|
ImageCLI.prototype.do_delete = require('./do_delete');
|
||||||
ImageCLI.prototype.do_export = require('./do_export');
|
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.prototype.do_wait = require('./do_wait');
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ function do_images(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_images.help = 'A shortcut for "spearhead image list".\n' + targ.help;
|
do_images.help = 'A shortcut for "triton image list".\n' + targ.help;
|
||||||
do_images.synopses = targ.synopses;
|
do_images.synopses = targ.synopses;
|
||||||
do_images.options = targ.options;
|
do_images.options = targ.options;
|
||||||
do_images.completionArgtypes = targ.completionArgtypes;
|
do_images.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance create ...`
|
* `triton instance create ...`
|
||||||
*/
|
*/
|
||||||
@ -19,8 +19,6 @@ var common = require('../common');
|
|||||||
var distractions = require('../distractions');
|
var distractions = require('../distractions');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
var mat = require('../metadataandtags');
|
var mat = require('../metadataandtags');
|
||||||
var NETWORK_OBJECT_FIELDS =
|
|
||||||
require('../constants').NETWORK_OBJECT_FIELDS;
|
|
||||||
|
|
||||||
function parseVolMount(volume) {
|
function parseVolMount(volume) {
|
||||||
var components;
|
var components;
|
||||||
@ -85,9 +83,6 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
} else if (args.length !== 2) {
|
} else if (args.length !== 2) {
|
||||||
return cb(new errors.UsageError('incorrect number of args'));
|
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 log = this.top.log;
|
||||||
@ -95,6 +90,103 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
common.cliSetupTritonApi,
|
common.cliSetupTritonApi,
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
/*
|
||||||
|
* Parse --affinity options for validity to `ctx.affinities`.
|
||||||
|
* Later (in `resolveLocality`) we'll translate this to locality hints
|
||||||
|
* that CloudAPI speaks.
|
||||||
|
*
|
||||||
|
* Some examples. Inspired by
|
||||||
|
* <https://docs.docker.com/swarm/scheduler/filter/#how-to-write-filter-expressions>
|
||||||
|
*
|
||||||
|
* instance==vm1
|
||||||
|
* container==vm1 # alternative to 'instance'
|
||||||
|
* inst==vm1 # alternative to 'instance'
|
||||||
|
* inst=vm1 # '=' is shortcut for '=='
|
||||||
|
* inst!=vm1 # '!='
|
||||||
|
* inst==~vm1 # '~' for soft/non-strict
|
||||||
|
* inst!=~vm1
|
||||||
|
*
|
||||||
|
* inst==vm* # globbing (not yet supported)
|
||||||
|
* inst!=/vm\d/ # regex (not yet supported)
|
||||||
|
*
|
||||||
|
* some-tag!=db # tags (not yet supported)
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* - no support for tags yet
|
||||||
|
* - no globbing or regex yet
|
||||||
|
* - we resolve name -> instance id *client-side* for now (until
|
||||||
|
* CloudAPI supports that)
|
||||||
|
* - Triton doesn't support mixed strict and non-strict, so we error
|
||||||
|
* out on that. We *could* just drop the non-strict, but that is
|
||||||
|
* slightly different.
|
||||||
|
*/
|
||||||
|
/* END JSSTYLED */
|
||||||
|
function parseAffinity(ctx, next) {
|
||||||
|
if (!opts.affinity) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var affinities = [];
|
||||||
|
|
||||||
|
// TODO: stricter rules on the value part
|
||||||
|
// JSSTYLED
|
||||||
|
var affinityRe = /((instance|inst|container)(==~|!=~|==|!=|=~|=))?(.*?)$/;
|
||||||
|
for (var i = 0; i < opts.affinity.length; i++) {
|
||||||
|
var raw = opts.affinity[i];
|
||||||
|
var match = affinityRe.exec(raw);
|
||||||
|
if (!match) {
|
||||||
|
next(new errors.UsageError(format('invalid affinity: "%s"',
|
||||||
|
raw)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = match[2];
|
||||||
|
if ([undefined, 'inst', 'container'].indexOf(key) !== -1) {
|
||||||
|
key = 'instance';
|
||||||
|
}
|
||||||
|
assert.equal(key, 'instance');
|
||||||
|
var op = match[3];
|
||||||
|
if ([undefined, '='].indexOf(op) !== -1) {
|
||||||
|
op = '==';
|
||||||
|
}
|
||||||
|
var strict = true;
|
||||||
|
if (op[op.length - 1] === '~') {
|
||||||
|
strict = false;
|
||||||
|
op = op.slice(0, op.length - 1);
|
||||||
|
}
|
||||||
|
var val = match[4];
|
||||||
|
|
||||||
|
// Guard against mixed strictness (Triton can't handle those).
|
||||||
|
if (affinities.length > 0) {
|
||||||
|
var lastAff = affinities[affinities.length - 1];
|
||||||
|
if (strict !== lastAff.strict) {
|
||||||
|
next(new errors.TritonError(format('mixed strict and '
|
||||||
|
+ 'non-strict affinities are not supported: '
|
||||||
|
+ '%j (%s) and %j (%s)',
|
||||||
|
lastAff.raw,
|
||||||
|
(lastAff.strict ? 'strict' : 'non-strict'),
|
||||||
|
raw, (strict ? 'strict' : 'non-strict'))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
affinities.push({
|
||||||
|
raw: raw,
|
||||||
|
key: key,
|
||||||
|
op: op,
|
||||||
|
strict: strict,
|
||||||
|
val: val
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (affinities.length) {
|
||||||
|
log.trace({affinities: affinities}, 'affinities');
|
||||||
|
ctx.affinities = affinities;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure if volumes were passed, they're in the correct form.
|
* Make sure if volumes were passed, they're in the correct form.
|
||||||
@ -133,44 +225,61 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse any nics given via `--nic`
|
* Determine `ctx.locality` according to what CloudAPI supports
|
||||||
|
* based on `ctx.affinities` parsed earlier.
|
||||||
*/
|
*/
|
||||||
function parseNics(ctx, next) {
|
function resolveLocality(ctx, next) {
|
||||||
if (!opts.nic) {
|
if (!ctx.affinities) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.nics = [];
|
var strict;
|
||||||
var i;
|
var near = [];
|
||||||
var networksSeen = {};
|
var far = [];
|
||||||
var nic;
|
|
||||||
var nics = opts.nic;
|
|
||||||
|
|
||||||
log.trace({nics: nics}, 'parsing nics');
|
vasync.forEachPipeline({
|
||||||
|
inputs: ctx.affinities,
|
||||||
|
func: function resolveAffinity(aff, nextAff) {
|
||||||
|
assert.ok(['==', '!='].indexOf(aff.op) !== -1,
|
||||||
|
'unexpected op: ' + aff.op);
|
||||||
|
var nearFar = (aff.op == '==' ? near : far);
|
||||||
|
|
||||||
for (i = 0; i < nics.length; i++) {
|
strict = aff.strict;
|
||||||
nic = nics[i].split(',');
|
if (common.isUUID(aff.val)) {
|
||||||
|
nearFar.push(aff.val);
|
||||||
try {
|
nextAff();
|
||||||
nic = common.parseNicStr(nic);
|
} else {
|
||||||
if (networksSeen[nic.ipv4_uuid]) {
|
tritonapi.getInstance({
|
||||||
throw new errors.UsageError(format(
|
id: aff.val,
|
||||||
'only 1 ip on a network allowed '
|
fields: ['id']
|
||||||
+ '(network %s specified multiple times)',
|
}, function (err, inst) {
|
||||||
nic.ipv4_uuid));
|
if (err) {
|
||||||
|
nextAff(err);
|
||||||
|
} else {
|
||||||
|
log.trace({val: aff.val, inst: inst.id},
|
||||||
|
'resolveAffinity');
|
||||||
|
nearFar.push(inst.id);
|
||||||
|
nextAff();
|
||||||
}
|
}
|
||||||
networksSeen[nic.ipv4_uuid] = true;
|
});
|
||||||
ctx.nics.push(nic);
|
}
|
||||||
} catch (err) {
|
}
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.trace({nics: ctx.nics}, 'parsed nics');
|
ctx.locality = {
|
||||||
|
strict: strict
|
||||||
|
};
|
||||||
|
if (near.length > 0) ctx.locality.near = near;
|
||||||
|
if (far.length > 0) ctx.locality.far = far;
|
||||||
|
log.trace({locality: ctx.locality}, 'resolveLocality');
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
function loadMetadata(ctx, next) {
|
function loadMetadata(ctx, next) {
|
||||||
@ -203,7 +312,6 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
function getImg(ctx, next) {
|
function getImg(ctx, next) {
|
||||||
var _opts = {
|
var _opts = {
|
||||||
name: args[0],
|
name: args[0],
|
||||||
excludeInactive: true,
|
|
||||||
useCache: true
|
useCache: true
|
||||||
};
|
};
|
||||||
tritonapi.getImage(_opts, function (err, img) {
|
tritonapi.getImage(_opts, function (err, img) {
|
||||||
@ -263,22 +371,16 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
var createOpts = {
|
var createOpts = {
|
||||||
name: opts.name,
|
name: opts.name,
|
||||||
image: ctx.img.id,
|
image: ctx.img.id,
|
||||||
'package': ctx.pkg && ctx.pkg.id
|
'package': ctx.pkg && ctx.pkg.id,
|
||||||
|
networks: ctx.nets && ctx.nets.map(
|
||||||
|
function (net) { return net.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) {
|
if (ctx.volMounts) {
|
||||||
createOpts.volumes = ctx.volMounts;
|
createOpts.volumes = ctx.volMounts;
|
||||||
}
|
}
|
||||||
if (opts.affinity) {
|
if (ctx.locality) {
|
||||||
createOpts.affinity = opts.affinity;
|
createOpts.locality = ctx.locality;
|
||||||
}
|
}
|
||||||
if (ctx.metadata) {
|
if (ctx.metadata) {
|
||||||
Object.keys(ctx.metadata).forEach(function (key) {
|
Object.keys(ctx.metadata).forEach(function (key) {
|
||||||
@ -290,16 +392,11 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
createOpts['tag.'+key] = ctx.tags[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++) {
|
for (var i = 0; i < opts._order.length; i++) {
|
||||||
var opt = opts._order[i];
|
var opt = opts._order[i];
|
||||||
if (opt.key === 'firewall') {
|
if (opt.key === 'firewall') {
|
||||||
createOpts.firewall_enabled = opt.value;
|
createOpts.firewall_enabled = opt.value;
|
||||||
} else if (opt.key === 'deletion_protection') {
|
|
||||||
createOpts.deletion_protection = opt.value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +520,9 @@ do_create.options = [
|
|||||||
'INST), `instance==~INST` (*attempt* to place on the same server ' +
|
'INST), `instance==~INST` (*attempt* to place on the same server ' +
|
||||||
'as INST), or `instance!=~INST` (*attempt* to place on a server ' +
|
'as INST), or `instance!=~INST` (*attempt* to place on a server ' +
|
||||||
'other than INST\'s). `INST` is an existing instance name or ' +
|
'other than INST\'s). `INST` is an existing instance name or ' +
|
||||||
'id. Use this option more than once for multiple rules.',
|
'id. There are two shortcuts: `inst` may be used instead of ' +
|
||||||
|
'`instance` and `instance==INST` can be shortened to just ' +
|
||||||
|
'`INST`. Use this option more than once for multiple rules.',
|
||||||
completionType: 'tritonaffinityrule'
|
completionType: 'tritonaffinityrule'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -438,28 +537,12 @@ do_create.options = [
|
|||||||
'This option can be used multiple times.',
|
'This option can be used multiple times.',
|
||||||
completionType: 'tritonnetwork'
|
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
|
// TODO: add boolNegationPrefix:'no-' when that cmdln pull is in
|
||||||
names: ['firewall'],
|
names: ['firewall'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Enable Cloud Firewall on this instance. See ' +
|
help: 'Enable Cloud Firewall on this instance. See ' +
|
||||||
'<https://docs.spearhead.cloud/network/firewall>'
|
'<https://docs.joyent.com/public-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'],
|
names: ['volume', 'v'],
|
||||||
@ -502,11 +585,6 @@ do_create.options = [
|
|||||||
'Joyent-provided images, the user-script is run at every boot ' +
|
'Joyent-provided images, the user-script is run at every boot ' +
|
||||||
'of the instance. This is a shortcut for `-M user-script=FILE`.'
|
'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'
|
group: 'Other options'
|
||||||
@ -539,8 +617,8 @@ do_create.help = [
|
|||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Where IMAGE is an image name, name@version, id, or short id (from ',
|
'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',
|
'`triton image list`) and PACKAGE is a package name, id, or short id',
|
||||||
'(from `spearhead package list`).'
|
'(from `triton package list`).'
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -19,7 +19,7 @@ function InstanceFwruleCLI(parent) {
|
|||||||
Cmdln.call(this, {
|
Cmdln.call(this, {
|
||||||
name: parent.name + ' fwrule',
|
name: parent.name + ' fwrule',
|
||||||
desc: [
|
desc: [
|
||||||
'List fwrules on Spearhead instances.'
|
'List fwrules on Triton instances.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
minHelpCol: 24 /* line up with option help */
|
minHelpCol: 24 /* line up with option help */
|
||||||
|
@ -25,10 +25,7 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
cb(setupErr);
|
cb(setupErr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tritonapi.getInstance({
|
tritonapi.getInstance(args[0], function (err, inst) {
|
||||||
id: args[0],
|
|
||||||
credentials: opts.credentials
|
|
||||||
}, function onInst(err, inst) {
|
|
||||||
if (inst) {
|
if (inst) {
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(inst));
|
console.log(JSON.stringify(inst));
|
||||||
@ -47,13 +44,6 @@ do_get.options = [
|
|||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Show this help.'
|
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'],
|
names: ['json', 'j'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
@ -68,6 +58,7 @@ do_get.help = [
|
|||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
|
'',
|
||||||
'Where "INST" is an instance name, id, or short id.',
|
'Where "INST" is an instance name, id, or short id.',
|
||||||
'',
|
'',
|
||||||
'A *deleted* instance may still respond with the instance object. In that',
|
'A *deleted* instance may still respond with the instance object. In that',
|
||||||
|
@ -71,7 +71,7 @@ do_ip.help = [
|
|||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Where "INST" is an instance name, id, or short id.',
|
'Where "INST" is an instance name, id, or short id.',
|
||||||
'For example: ssh root@$(spearhead ip my-instance)'
|
'For example: ssh root@$(triton ip my-instance)'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Joyent, Inc.
|
* Copyright (c) 2017, Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance list ...`
|
* `triton instance list ...`
|
||||||
*/
|
*/
|
||||||
@ -150,11 +150,9 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
common.uuidToShortId(inst.image);
|
common.uuidToShortId(inst.image);
|
||||||
inst.shortid = inst.id.split('-', 1)[0];
|
inst.shortid = inst.id.split('-', 1)[0];
|
||||||
var flags = [];
|
var flags = [];
|
||||||
if (inst.brand === 'bhyve') flags.push('B');
|
|
||||||
if (inst.docker) flags.push('D');
|
if (inst.docker) flags.push('D');
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
if (inst.deletion_protection) flags.push('P');
|
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -210,11 +208,9 @@ do_list.help = [
|
|||||||
'for convenience):',
|
'for convenience):',
|
||||||
' shortid* A short ID prefix.',
|
' shortid* A short ID prefix.',
|
||||||
' flags* Single letter flags summarizing some fields:',
|
' flags* Single letter flags summarizing some fields:',
|
||||||
' "B" the brand is "bhyve"',
|
|
||||||
' "D" docker instance',
|
' "D" docker instance',
|
||||||
' "F" firewall is enabled',
|
' "F" firewall is enabled',
|
||||||
' "K" the brand is "kvm"',
|
' "K" the brand is "kvm"',
|
||||||
' "P" deletion protected',
|
|
||||||
' age* Approximate time since created, e.g. 1y, 2w.',
|
' age* Approximate time since created, e.g. 1y, 2w.',
|
||||||
' img* The image "name@version", if available, else its',
|
' img* The image "name@version", if available, else its',
|
||||||
' "shortid".',
|
' "shortid".',
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2018 Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton snapshot create ...`
|
* `triton snapshot create ...`
|
||||||
*/
|
*/
|
||||||
@ -133,7 +133,7 @@ do_create.help = [
|
|||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Snapshots do not work for instances of type "bhyve" or "kvm".'
|
'Snapshot do not work for instances of type "kvm".'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
do_create.completionArgtypes = ['tritoninstance', 'none'];
|
do_create.completionArgtypes = ['tritoninstance', 'none'];
|
||||||
|
@ -22,7 +22,7 @@ function SnapshotCLI(top) {
|
|||||||
|
|
||||||
Cmdln.call(this, {
|
Cmdln.call(this, {
|
||||||
name: top.name + ' snapshot',
|
name: top.name + ' snapshot',
|
||||||
desc: 'List, get, create and delete Spearhead instance snapshots.',
|
desc: 'List, get, create and delete Triton instance snapshots.',
|
||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
'create',
|
'create',
|
||||||
@ -31,7 +31,7 @@ function SnapshotCLI(top) {
|
|||||||
'delete'
|
'delete'
|
||||||
],
|
],
|
||||||
helpBody: 'Instances can be rolled back to a snapshot using\n' +
|
helpBody: 'Instances can be rolled back to a snapshot using\n' +
|
||||||
'`spearhead instance start --snapshot=SNAPNAME`.'
|
'`triton instance start --snapshot=SNAPNAME`.'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
util.inherits(SnapshotCLI, Cmdln);
|
util.inherits(SnapshotCLI, Cmdln);
|
||||||
|
@ -21,7 +21,7 @@ function do_snapshots(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_snapshots.help = 'A shortcut for "spearhead instance snapshot list".\n' +
|
do_snapshots.help = 'A shortcut for "triton instance snapshot list".\n' +
|
||||||
targ.help;
|
targ.help;
|
||||||
do_snapshots.synopses = targ.synopses;
|
do_snapshots.synopses = targ.synopses;
|
||||||
do_snapshots.options = targ.options;
|
do_snapshots.options = targ.options;
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance ssh ...`
|
* `triton instance ssh ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
@ -18,30 +17,6 @@ var vasync = require('vasync');
|
|||||||
var common = require('../common');
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
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) {
|
function do_ssh(subcmd, opts, args, callback) {
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
@ -55,12 +30,10 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
var id = args.shift();
|
var id = args.shift();
|
||||||
|
|
||||||
var user;
|
var user;
|
||||||
var overrideUser = false;
|
|
||||||
var i = id.indexOf('@');
|
var i = id.indexOf('@');
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
user = id.substr(0, i);
|
user = id.substr(0, i);
|
||||||
id = id.substr(i + 1);
|
id = id.substr(i + 1);
|
||||||
overrideUser = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
@ -75,112 +48,17 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
|
|
||||||
ctx.inst = inst;
|
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;
|
ctx.ip = inst.primaryIp;
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.ip) {
|
if (!ctx.ip) {
|
||||||
next(new Error('IP address not found for instance'));
|
next(new Error('primaryIp not found for instance'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
next();
|
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) {
|
function getUser(ctx, next) {
|
||||||
if (overrideUser) {
|
if (user) {
|
||||||
assert.string(user, 'user');
|
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -195,8 +73,8 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is a convention as seen on Joyent's "ubuntu-certified"
|
* This is a convention as seen on Joyent's
|
||||||
* KVM images.
|
* "ubuntu-certified" KVM images.
|
||||||
*/
|
*/
|
||||||
if (image.tags && image.tags.default_user) {
|
if (image.tags && image.tags.default_user) {
|
||||||
user = image.tags.default_user;
|
user = image.tags.default_user;
|
||||||
@ -208,64 +86,9 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
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) {
|
function doSsh(ctx, next) {
|
||||||
args = ['-l', user, ctx.ip].concat(args);
|
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
|
* By default we disable ControlMaster (aka mux, aka SSH
|
||||||
* connection multiplexing) because of
|
* connection multiplexing) because of
|
||||||
@ -310,11 +133,6 @@ do_ssh.options = [
|
|||||||
names: ['help', 'h'],
|
names: ['help', 'h'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Show this help.'
|
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.synopses = ['{{name}} ssh [-h] [USER@]INST [SSH-ARGUMENTS]'];
|
||||||
@ -332,32 +150,12 @@ do_ssh.help = [
|
|||||||
'If USER is not specified and the default_user tag is not set, the user',
|
'If USER is not specified and the default_user tag is not set, the user',
|
||||||
'is assumed to be \"root\".',
|
'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. ',
|
'There is a known issue with SSH connection multiplexing (a.k.a. ',
|
||||||
'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`',
|
'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`',
|
||||||
'is spawned with options disabling ControlMaster. See ',
|
'is spawned with options disabling ControlMaster. See ',
|
||||||
'<https://github.com/joyent/node-triton/issues/52> for details. If you ',
|
'<https://github.com/joyent/node-triton/issues/52> for details. If you ',
|
||||||
'want to use ControlMaster, an alternative is:',
|
'want to use ControlMaster, an alternative is:',
|
||||||
' ssh root@$(spearhead ip INST)'
|
' ssh root@$(triton ip INST)'
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ function InstanceTagCLI(parent) {
|
|||||||
name: parent.name + ' tag',
|
name: parent.name + ' tag',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List, get, set and delete tags on Spearhead instances.'
|
'List, get, set and delete tags on Triton instances.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
|
@ -20,7 +20,7 @@ function do_tags(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_tags.help = 'A shortcut for "spearhead instance tag list".\n' + targ.help;
|
do_tags.help = 'A shortcut for "triton instance tag list".\n' + targ.help;
|
||||||
do_tags.synopses = targ.synopses;
|
do_tags.synopses = targ.synopses;
|
||||||
do_tags.options = targ.options;
|
do_tags.options = targ.options;
|
||||||
do_tags.completionArgtypes = targ.completionArgtypes;
|
do_tags.completionArgtypes = targ.completionArgtypes;
|
||||||
|
150
lib/do_instance/do_vnc.js
Normal file
150
lib/do_instance/do_vnc.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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 vnc ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var net = require('net');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
var format = require('util').format;
|
||||||
|
|
||||||
|
function getInstance(ctx, next) {
|
||||||
|
ctx.cli.tritonapi.getInstance(ctx.id, function onInstance(err, inst) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.inst = inst;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createServer(port, cb) {
|
||||||
|
var server = net.createServer(function (conn) {
|
||||||
|
cb(null, conn);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
|
||||||
|
server.on('listening', function serverListen() {
|
||||||
|
var actualPort = server.address().port;
|
||||||
|
var connstr = format('vnc://127.0.0.1:%d', actualPort);
|
||||||
|
console.log('Listening on ' + connstr);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', function serverError(err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startProxy(ctx, next) {
|
||||||
|
createServer(ctx.port, function onConnect(cErr, conn) {
|
||||||
|
if (cErr) {
|
||||||
|
next(cErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VNC is latency sensitive, so send data as soon as it's available
|
||||||
|
conn.setNoDelay(true);
|
||||||
|
|
||||||
|
// The VNC protocol starts with the _server_ sending a handshake
|
||||||
|
// to the client, so we explicitly want to defer creation of the
|
||||||
|
// websocket until we have a connection on the proxy
|
||||||
|
ctx.cli.tritonapi.getInstanceVnc(ctx.inst.id, function vnc(vErr, shed) {
|
||||||
|
conn.on('data', function serverData(data) {
|
||||||
|
shed.send(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
shed.on('binary', function shedData(data) {
|
||||||
|
conn.write(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
conn.on('end', function serverEnd(data) {
|
||||||
|
console.log('# Connection closed');
|
||||||
|
shed.end();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
shed.on('end', function shedEnd(code, reason) {
|
||||||
|
conn.end();
|
||||||
|
// XXX: Should we translate codes into exit values?
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
shed.on('error', function shedError(shedErr) {
|
||||||
|
conn.end();
|
||||||
|
// send 'end' event should be called after this
|
||||||
|
});
|
||||||
|
|
||||||
|
shed.on('connectionReset', function shedReset() {
|
||||||
|
console.log('# Connection reset by peer');
|
||||||
|
conn.end();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_vnc(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 port = opts.port || 0;
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {cli: this.top, id: id, port: port}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
|
// We could skip the instance lookup here and directly call
|
||||||
|
// tritonapi.getInstanceVnc with 'id', however, the instance id given
|
||||||
|
// would not be validated until a connection is made to the server
|
||||||
|
// proxy we create with start_server. Instead the id is validated
|
||||||
|
// before we start the proxy so that we can immediately exit if
|
||||||
|
// there is an error.
|
||||||
|
getInstance,
|
||||||
|
startProxy
|
||||||
|
]}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_vnc.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['port', 'p'],
|
||||||
|
helpArg: 'PORT',
|
||||||
|
type: 'positiveInteger',
|
||||||
|
help: 'The port number the server listens on. If not specified, '
|
||||||
|
+ 'a random port number is used.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_vnc.synopses = ['{{name}} vnc [OPTIONS] INST'];
|
||||||
|
do_vnc.help = [
|
||||||
|
'Start VNC server for instance.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where INST is an instance name, id, or short id.'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_vnc.completionArgtypes = ['tritoninstance', 'none'];
|
||||||
|
|
||||||
|
module.exports = do_vnc;
|
@ -122,7 +122,7 @@ do_wait.help = [
|
|||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Where "INST" is an instance name, id, or short id; and "STATES" is a',
|
'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".',
|
'comma-separated list of target instance states, by default "running,failed".',
|
||||||
'In other words, "spearhead inst wait foo0" will wait for instance "foo0" to',
|
'In other words, "triton inst wait foo0" will wait for instance "foo0" to',
|
||||||
'complete provisioning.'
|
'complete provisioning.'
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2018 Joyent, Inc.
|
* Copyright 2015 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance ...`
|
* `triton instance ...`
|
||||||
*/
|
*/
|
||||||
@ -22,7 +22,7 @@ function InstanceCLI(top) {
|
|||||||
name: top.name + ' instance',
|
name: top.name + ' instance',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and manage Spearhead instances.'
|
'List and manage Triton instances.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -45,14 +45,11 @@ function InstanceCLI(top) {
|
|||||||
'enable-firewall',
|
'enable-firewall',
|
||||||
'disable-firewall',
|
'disable-firewall',
|
||||||
{ group: '' },
|
{ group: '' },
|
||||||
'enable-deletion-protection',
|
'vnc',
|
||||||
'disable-deletion-protection',
|
|
||||||
{ group: '' },
|
|
||||||
'ssh',
|
'ssh',
|
||||||
'ip',
|
'ip',
|
||||||
'wait',
|
'wait',
|
||||||
'audit',
|
'audit',
|
||||||
'nic',
|
|
||||||
'snapshot',
|
'snapshot',
|
||||||
'tag'
|
'tag'
|
||||||
]
|
]
|
||||||
@ -81,16 +78,11 @@ InstanceCLI.prototype.do_fwrules = require('./do_fwrules');
|
|||||||
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
|
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
|
||||||
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
|
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
|
||||||
|
|
||||||
InstanceCLI.prototype.do_enable_deletion_protection =
|
InstanceCLI.prototype.do_vnc = require('./do_vnc');
|
||||||
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_ssh = require('./do_ssh');
|
||||||
InstanceCLI.prototype.do_ip = require('./do_ip');
|
InstanceCLI.prototype.do_ip = require('./do_ip');
|
||||||
InstanceCLI.prototype.do_wait = require('./do_wait');
|
InstanceCLI.prototype.do_wait = require('./do_wait');
|
||||||
InstanceCLI.prototype.do_audit = require('./do_audit');
|
InstanceCLI.prototype.do_audit = require('./do_audit');
|
||||||
InstanceCLI.prototype.do_nic = require('./do_nic');
|
|
||||||
InstanceCLI.prototype.do_snapshot = require('./do_snapshot');
|
InstanceCLI.prototype.do_snapshot = require('./do_snapshot');
|
||||||
InstanceCLI.prototype.do_snapshots = require('./do_snapshots');
|
InstanceCLI.prototype.do_snapshots = require('./do_snapshots');
|
||||||
InstanceCLI.prototype.do_tag = require('./do_tag');
|
InstanceCLI.prototype.do_tag = require('./do_tag');
|
||||||
|
@ -20,7 +20,7 @@ function do_instances(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_instances.help = 'A shortcut for "spearhead instance list".\n' + targ.help;
|
do_instances.help = 'A shortcut for "triton instance list".\n' + targ.help;
|
||||||
do_instances.synopses = targ.synopses;
|
do_instances.synopses = targ.synopses;
|
||||||
do_instances.options = targ.options;
|
do_instances.options = targ.options;
|
||||||
do_instances.completionArgtypes = targ.completionArgtypes;
|
do_instances.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -20,7 +20,7 @@ function do_ip(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_ip.help = 'A shortcut for "spearhead instance ip".\n' + targ.help;
|
do_ip.help = 'A shortcut for "triton instance ip".\n' + targ.help;
|
||||||
do_ip.synopses = targ.synopses;
|
do_ip.synopses = targ.synopses;
|
||||||
do_ip.options = targ.options;
|
do_ip.options = targ.options;
|
||||||
do_ip.completionArgtypes = targ.completionArgtypes;
|
do_ip.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -47,7 +47,13 @@ function do_add(subcmd, opts, args, cb) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
common.readStdin(function gotStdin(stdin) {
|
var stdin = '';
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdin.on('end', function () {
|
||||||
ctx.data = stdin;
|
ctx.data = stdin;
|
||||||
ctx.from = '<stdin>';
|
ctx.from = '<stdin>';
|
||||||
next();
|
next();
|
||||||
|
@ -20,7 +20,7 @@ function do_keys(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_keys.help = 'A shortcut for "spearhead key list".\n' + targ.help;
|
do_keys.help = 'A shortcut for "triton key list".\n' + targ.help;
|
||||||
do_keys.synopses = targ.synopses;
|
do_keys.synopses = targ.synopses;
|
||||||
do_keys.options = targ.options;
|
do_keys.options = targ.options;
|
||||||
do_keys.completionArgtypes = targ.completionArgtypes;
|
do_keys.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -1,273 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 network create ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var format = require('util').format;
|
|
||||||
var jsprim = require('jsprim');
|
|
||||||
|
|
||||||
var common = require('../common');
|
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function do_create(subcmd, opts, args, cb) {
|
|
||||||
assert.optionalString(opts.name, 'opts.name');
|
|
||||||
assert.optionalString(opts.subnet, 'opts.subnet');
|
|
||||||
assert.optionalString(opts.start_ip, 'opts.start_ip');
|
|
||||||
assert.optionalString(opts.end_ip, 'opts.end_ip');
|
|
||||||
assert.optionalString(opts.description, 'opts.description');
|
|
||||||
assert.optionalString(opts.gateway, 'opts.gateway');
|
|
||||||
assert.optionalArrayOfString(opts.resolver, 'opts.resolver');
|
|
||||||
assert.optionalArrayOfString(opts.route, 'opts.route');
|
|
||||||
assert.optionalBool(opts.no_nat, 'opts.no_nat');
|
|
||||||
assert.optionalBool(opts.json, 'opts.json');
|
|
||||||
assert.optionalBool(opts.help, 'opts.help');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
var i;
|
|
||||||
|
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (args.length === 0) {
|
|
||||||
cb(new errors.UsageError('missing VLAN argument'));
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
cb(new errors.UsageError('incorrect number of arguments'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var vlanId = jsprim.parseInteger(args[0], { allowSign: false });
|
|
||||||
if (typeof (vlanId) !== 'number') {
|
|
||||||
cb(new errors.UsageError('VLAN must be an integer'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.subnet) {
|
|
||||||
cb(new errors.UsageError('must specify --subnet (-s) option'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.name) {
|
|
||||||
cb(new errors.UsageError('must specify --name (-n) option'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.start_ip) {
|
|
||||||
cb(new errors.UsageError('must specify --start-ip (-S) option'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.end_ip) {
|
|
||||||
cb(new errors.UsageError('must specify --end-ip (-E) option'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var createOpts = {
|
|
||||||
vlan_id: vlanId,
|
|
||||||
name: opts.name,
|
|
||||||
subnet: opts.subnet,
|
|
||||||
provision_start_ip: opts.start_ip,
|
|
||||||
provision_end_ip: opts.end_ip,
|
|
||||||
resolvers: [],
|
|
||||||
routes: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (opts.resolver) {
|
|
||||||
for (i = 0; i < opts.resolver.length; i++) {
|
|
||||||
if (createOpts.resolvers.indexOf(opts.resolver[i]) === -1) {
|
|
||||||
createOpts.resolvers.push(opts.resolver[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.route) {
|
|
||||||
for (i = 0; i < opts.route.length; i++) {
|
|
||||||
var m = opts.route[i].match(new RegExp('^([^=]+)=([^=]+)$'));
|
|
||||||
|
|
||||||
if (m === null) {
|
|
||||||
cb(new errors.UsageError('invalid route: ' + opts.route[i]));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
createOpts.routes[m[1]] = m[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.no_nat) {
|
|
||||||
createOpts.internet_nat = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.gateway) {
|
|
||||||
createOpts.gateway = opts.gateway;
|
|
||||||
} else {
|
|
||||||
if (!opts.no_nat) {
|
|
||||||
cb(new errors.UsageError('without a --gateway (-g), you must ' +
|
|
||||||
'specify --no-nat (-x)'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.description) {
|
|
||||||
createOpts.description = opts.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cloudapi = cli.tritonapi.cloudapi;
|
|
||||||
|
|
||||||
cloudapi.createFabricNetwork(createOpts, function onCreate(err, net) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
console.log(JSON.stringify(net));
|
|
||||||
} else {
|
|
||||||
console.log('Created network %s (%s)', net.name, net.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_create.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: 'Create options'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['name', 'n'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'NAME',
|
|
||||||
help: 'Name of the NETWORK.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['description', 'D'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'DESC',
|
|
||||||
help: 'Description of the NETWORK.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['subnet', 's'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'SUBNET',
|
|
||||||
help: 'A CIDR string describing the NETWORK.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['start-ip', 'S', 'start_ip'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'START_IP',
|
|
||||||
help: 'First assignable IP address on NETWORK.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['end-ip', 'E', 'end_ip'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'END_IP',
|
|
||||||
help: 'Last assignable IP address on NETWORK.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['gateway', 'g'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'IP',
|
|
||||||
help: 'Default gateway IP address.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['resolver', 'r'],
|
|
||||||
type: 'arrayOfString',
|
|
||||||
helpArg: 'RESOLVER',
|
|
||||||
help: 'DNS resolver IP address. Specify multiple -r options for ' +
|
|
||||||
'multiple resolvers.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['route', 'R'],
|
|
||||||
type: 'arrayOfString',
|
|
||||||
helpArg: 'SUBNET=IP',
|
|
||||||
help: [ 'Static route for network. Each route must include the',
|
|
||||||
'subnet (IP address with CIDR prefix length) and the router',
|
|
||||||
'address. Specify multiple -R options for multiple static',
|
|
||||||
'routes.' ].join(' ')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['no-nat', 'x', 'no_nat'],
|
|
||||||
type: 'bool',
|
|
||||||
helpArg: 'NO_NAT',
|
|
||||||
help: 'Disable creation of an Internet NAT zone on GATEWAY.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: 'Other options'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['json', 'j'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'JSON stream output.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
|
||||||
|
|
||||||
do_create.help = [
|
|
||||||
'Create a network on a VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'',
|
|
||||||
'Examples:',
|
|
||||||
' Create the "accounting" network on VLAN 1000:',
|
|
||||||
' triton network create -n accounting --subnet 192.168.0.0/24 \\',
|
|
||||||
' --start-ip 192.168.0.1 --end-ip 192.168.0.254 --no-nat \\',
|
|
||||||
' 1000',
|
|
||||||
'',
|
|
||||||
' Create the "eng" network on VLAN 1001 with a pair of static routes:',
|
|
||||||
' triton network create -n eng -s 192.168.1.0/24 \\',
|
|
||||||
' -S 192.168.1.1 -E 192.168.1.249 --no-nat \\',
|
|
||||||
' --route 10.1.1.0/24=192.168.1.50 \\',
|
|
||||||
' --route 10.1.2.0/24=192.168.1.100 \\',
|
|
||||||
' 1001',
|
|
||||||
'',
|
|
||||||
' Create the "ops" network on VLAN 1002 with DNS resolvers and NAT:',
|
|
||||||
' triton network create -n ops -s 192.168.2.0/24 \\',
|
|
||||||
' -S 192.168.2.10 -E 192.168.2.249 \\',
|
|
||||||
' --resolver 8.8.8.8 --resolver 8.4.4.4 \\',
|
|
||||||
' --gateway 192.168.2.1 \\',
|
|
||||||
' 1002'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_create.helpOpts = {
|
|
||||||
helpCol: 16
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = do_create;
|
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 network 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 NETWORK argument(s)'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
var networks = args;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vasync.forEachParallel({
|
|
||||||
inputs: networks,
|
|
||||||
func: function deleteOne(id, next) {
|
|
||||||
cli.tritonapi.deleteFabricNetwork({ id: id },
|
|
||||||
function onDelete(err) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Deleted network %s', id);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_delete.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_delete.synopses = ['{{name}} {{cmd}} NETWORK [NETWORK ...]'];
|
|
||||||
|
|
||||||
do_delete.help = [
|
|
||||||
'Remove a fabric network.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where NETWORK is a network id (full UUID), name, or short id.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_delete.aliases = ['rm'];
|
|
||||||
|
|
||||||
do_delete.completionArgtypes = ['tritonnetwork'];
|
|
||||||
|
|
||||||
module.exports = do_delete;
|
|
@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 network get-default ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var vasync = require('vasync');
|
|
||||||
|
|
||||||
var common = require('../common');
|
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
|
|
||||||
function do_get_default(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 cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.tritonapi.cloudapi.getConfig({}, function getConf(err, conf) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultNetwork = conf.default_network;
|
|
||||||
|
|
||||||
if (!defaultNetwork) {
|
|
||||||
cb(new Error('account has no default network configured'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.handlerFromSubcmd('network').dispatch({
|
|
||||||
subcmd: 'get',
|
|
||||||
opts: opts,
|
|
||||||
args: [defaultNetwork]
|
|
||||||
}, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_get_default.options = require('./do_get').options;
|
|
||||||
|
|
||||||
do_get_default.synopses = ['{{name}} {{cmd}}'];
|
|
||||||
|
|
||||||
do_get_default.help = [
|
|
||||||
'Get default network.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_get_default.completionArgtypes = ['tritonnetwork'];
|
|
||||||
|
|
||||||
module.exports = do_get_default;
|
|
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
@ -42,29 +41,7 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
}
|
}
|
||||||
columns = columns.split(',');
|
columns = columns.split(',');
|
||||||
|
|
||||||
var sort = opts.s.split(',').map(function mapSort(field) {
|
var sort = opts.s.split(',');
|
||||||
var so = {};
|
|
||||||
|
|
||||||
field = field.trim();
|
|
||||||
assert.ok(field, 'non-empty field');
|
|
||||||
|
|
||||||
if (field[0] === '-') {
|
|
||||||
so.field = field.slice(1);
|
|
||||||
so.reverse = true;
|
|
||||||
} else {
|
|
||||||
so.field = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (so.field) {
|
|
||||||
case 'ip':
|
|
||||||
so.keyFunc = common.ipv4ToLong;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return so;
|
|
||||||
});
|
|
||||||
|
|
||||||
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
common.cliSetupTritonApi,
|
common.cliSetupTritonApi,
|
||||||
@ -117,7 +94,7 @@ do_list.help = [
|
|||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'Fields (most are self explanatory, the significant ones are as follows):',
|
'Fields (most are self explanatory, the significant ones are as follows):',
|
||||||
' managed IP is manged by Spearhead and cannot be modified directly.',
|
' managed IP is manged by Triton and cannot be modified directly.',
|
||||||
'',
|
'',
|
||||||
'See https://apidocs.joyent.com/cloudapi/#ListNetworkIPs for a full' +
|
'See https://apidocs.joyent.com/cloudapi/#ListNetworkIPs for a full' +
|
||||||
' listing.'
|
' listing.'
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 network ip update ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var format = require('util').format;
|
|
||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
var vasync = require('vasync');
|
|
||||||
|
|
||||||
var common = require('../../common');
|
|
||||||
var errors = require('../../errors');
|
|
||||||
var UPDATE_NETWORK_IP_FIELDS
|
|
||||||
= require('../../cloudapi2').CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS;
|
|
||||||
|
|
||||||
function do_update(subcmd, opts, args, callback) {
|
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], callback);
|
|
||||||
return;
|
|
||||||
} else if (args.length < 2) {
|
|
||||||
callback(new errors.UsageError(format(
|
|
||||||
'incorrect number of args (%d)', args.length)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var log = this.log;
|
|
||||||
var tritonapi = this.top.tritonapi;
|
|
||||||
var updateIpOpts = {
|
|
||||||
id: args.shift(),
|
|
||||||
ip: args.shift()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (args.length === 0 && !opts.file) {
|
|
||||||
callback(new errors.UsageError(
|
|
||||||
'FIELD=VALUE arguments or "-f FILE" must be specified'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_NETWORK_IP_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 network IP 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 network IP update JSON on stdin');
|
|
||||||
next(new errors.TritonError(format(
|
|
||||||
'invalid JSON for network IP update on stdin: %s',
|
|
||||||
err)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
function validateIt(ctx, next) {
|
|
||||||
try {
|
|
||||||
common.validateObject(ctx.data, UPDATE_NETWORK_IP_FIELDS);
|
|
||||||
} catch (e) {
|
|
||||||
next(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
|
|
||||||
function updateNetworkIP(ctx, next) {
|
|
||||||
Object.keys(ctx.data).forEach(function (key) {
|
|
||||||
updateIpOpts[key] = ctx.data[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
tritonapi.updateNetworkIp(updateIpOpts, function (err, body, res) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
console.log(JSON.stringify(body));
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Updated network %s IP %s (fields: %s)',
|
|
||||||
updateIpOpts.id, updateIpOpts.ip,
|
|
||||||
Object.keys(ctx.data).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.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['json', 'j'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'JSON output.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_update.synopses = [
|
|
||||||
'{{name}} {{cmd}} NETWORK IP [FIELD=VALUE ...]',
|
|
||||||
'{{name}} {{cmd}} NETWORK IP -f JSON-FILE'
|
|
||||||
];
|
|
||||||
|
|
||||||
do_update.help = [
|
|
||||||
/* BEGIN JSSTYLED */
|
|
||||||
'Update a network ip.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where NETWORK is a network id, and IP is the ip address you want to update.',
|
|
||||||
'',
|
|
||||||
'Updateable fields:',
|
|
||||||
' ' + Object.keys(UPDATE_NETWORK_IP_FIELDS).sort().map(function (field) {
|
|
||||||
return field + ' (' + UPDATE_NETWORK_IP_FIELDS[field] + ')';
|
|
||||||
}).join('\n '),
|
|
||||||
|
|
||||||
''
|
|
||||||
/* END JSSTYLED */
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_update.completionArgtypes = [
|
|
||||||
'tritonnetwork',
|
|
||||||
'tritonnetworkip',
|
|
||||||
'tritonupdatenetworkipfield'
|
|
||||||
];
|
|
||||||
|
|
||||||
module.exports = do_update;
|
|
@ -23,7 +23,7 @@ function IpCLI(top) {
|
|||||||
name: top.name + ' ip',
|
name: top.name + ' ip',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and manage Spearhead network IPs.'
|
'List and manage Triton network IPs.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -32,8 +32,7 @@ function IpCLI(top) {
|
|||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
'list',
|
'list',
|
||||||
'get',
|
'get'
|
||||||
'update'
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -46,6 +45,5 @@ IpCLI.prototype.init = function init(opts, args, cb) {
|
|||||||
|
|
||||||
IpCLI.prototype.do_list = require('./do_list');
|
IpCLI.prototype.do_list = require('./do_list');
|
||||||
IpCLI.prototype.do_get = require('./do_get');
|
IpCLI.prototype.do_get = require('./do_get');
|
||||||
IpCLI.prototype.do_update = require('./do_update');
|
|
||||||
|
|
||||||
module.exports = IpCLI;
|
module.exports = IpCLI;
|
||||||
|
@ -66,23 +66,14 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
common.cliSetupTritonApi,
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
function searchNetworks(arg, next) {
|
function searchNetworks(arg, next) {
|
||||||
// since this command is also used by do_vlan/do_networks.js
|
self.top.tritonapi.cloudapi.listNetworks(function (err, networks) {
|
||||||
if (opts.vlan_id) {
|
|
||||||
self.top.tritonapi.listFabricNetworks({
|
|
||||||
vlan_id: opts.vlan_id
|
|
||||||
}, listedNetworks);
|
|
||||||
} else {
|
|
||||||
self.top.tritonapi.cloudapi.listNetworks({}, listedNetworks);
|
|
||||||
}
|
|
||||||
|
|
||||||
function listedNetworks(err, networks) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
arg.networks = networks;
|
arg.networks = networks;
|
||||||
next();
|
next();
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
function filterNetworks(arg, next) {
|
function filterNetworks(arg, next) {
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 network set-default ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var vasync = require('vasync');
|
|
||||||
|
|
||||||
var common = require('../common');
|
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
|
|
||||||
function do_set_default(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 NETWORK argument'));
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
cb(new errors.UsageError('incorrect number of arguments'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.tritonapi.getNetwork(args[0], function onNetwork(err, net) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
default_network: net.id
|
|
||||||
};
|
|
||||||
|
|
||||||
var cloudapi = cli.tritonapi.cloudapi;
|
|
||||||
|
|
||||||
cloudapi.updateConfig(params, function onUpdate(err2) {
|
|
||||||
if (err2) {
|
|
||||||
cb(err2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Set network %s (%s) as default.', net.name,
|
|
||||||
net.id);
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_set_default.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_set_default.synopses = ['{{name}} {{cmd}} NETWORK'];
|
|
||||||
|
|
||||||
do_set_default.help = [
|
|
||||||
'Set default network.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where NETWORK is a network id (full UUID), name, or short id.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_set_default.completionArgtypes = ['tritonnetwork'];
|
|
||||||
|
|
||||||
module.exports = do_set_default;
|
|
@ -23,7 +23,7 @@ function NetworkCLI(top) {
|
|||||||
name: top.name + ' network',
|
name: top.name + ' network',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and manage Spearhead networks.'
|
'List and manage Triton networks.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -33,11 +33,7 @@ function NetworkCLI(top) {
|
|||||||
'help',
|
'help',
|
||||||
'list',
|
'list',
|
||||||
'get',
|
'get',
|
||||||
'ip',
|
'ip'
|
||||||
'create',
|
|
||||||
'delete',
|
|
||||||
'get-default',
|
|
||||||
'set-default'
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -51,10 +47,6 @@ NetworkCLI.prototype.init = function init(opts, args, cb) {
|
|||||||
NetworkCLI.prototype.do_list = require('./do_list');
|
NetworkCLI.prototype.do_list = require('./do_list');
|
||||||
NetworkCLI.prototype.do_get = require('./do_get');
|
NetworkCLI.prototype.do_get = require('./do_get');
|
||||||
NetworkCLI.prototype.do_ip = require('./do_ip');
|
NetworkCLI.prototype.do_ip = require('./do_ip');
|
||||||
NetworkCLI.prototype.do_create = require('./do_create');
|
|
||||||
NetworkCLI.prototype.do_delete = require('./do_delete');
|
|
||||||
NetworkCLI.prototype.do_get_default = require('./do_get_default');
|
|
||||||
NetworkCLI.prototype.do_set_default = require('./do_set_default');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = NetworkCLI;
|
module.exports = NetworkCLI;
|
||||||
|
@ -20,7 +20,7 @@ function do_networks(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_networks.help = 'A shortcut for "spearhead network list".\n' + targ.help;
|
do_networks.help = 'A shortcut for "triton network list".\n' + targ.help;
|
||||||
do_networks.synopses = targ.synopses;
|
do_networks.synopses = targ.synopses;
|
||||||
do_networks.options = targ.options;
|
do_networks.options = targ.options;
|
||||||
do_networks.completionArgtypes = targ.completionArgtypes;
|
do_networks.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -23,7 +23,7 @@ function PackageCLI(top) {
|
|||||||
name: top.name + ' package',
|
name: top.name + ' package',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and get Spearhead packages.',
|
'List and get Triton packages.',
|
||||||
'',
|
'',
|
||||||
'A package is a collection of attributes -- for example disk quota,',
|
'A package is a collection of attributes -- for example disk quota,',
|
||||||
'amount of RAM -- used when creating an instance. They have a name',
|
'amount of RAM -- used when creating an instance. They have a name',
|
||||||
|
@ -20,7 +20,7 @@ function do_packages(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_packages.help = 'A shortcut for "spearhead package list".\n' + targ.help;
|
do_packages.help = 'A shortcut for "triton package list".\n' + targ.help;
|
||||||
do_packages.synopses = targ.synopses;
|
do_packages.synopses = targ.synopses;
|
||||||
do_packages.options = targ.options;
|
do_packages.options = targ.options;
|
||||||
do_packages.completionArgtypes = targ.completionArgtypes;
|
do_packages.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -93,8 +93,12 @@ function _createProfile(opts, cb) {
|
|||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(stdin);
|
data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -167,7 +171,7 @@ function _createProfile(opts, cb) {
|
|||||||
defaults = ctx.copy;
|
defaults = ctx.copy;
|
||||||
delete defaults.name; // we don't copy a profile name
|
delete defaults.name; // we don't copy a profile name
|
||||||
} else {
|
} else {
|
||||||
defaults.url = 'https://eu-ro-1.api.spearhead.cloud';
|
defaults.url = 'https://us-sw-1.api.joyent.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -216,7 +220,7 @@ function _createProfile(opts, cb) {
|
|||||||
|
|
||||||
var fields = [ {
|
var fields = [ {
|
||||||
desc: 'A profile name. A short string to identify this ' +
|
desc: 'A profile name. A short string to identify this ' +
|
||||||
'profile to the `spearhead` command.',
|
'profile to the `triton` command.',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
default: defaults.name,
|
default: defaults.name,
|
||||||
validate: function validateName(value, valCb) {
|
validate: function validateName(value, valCb) {
|
||||||
@ -355,7 +359,7 @@ function _createProfile(opts, cb) {
|
|||||||
|
|
||||||
console.log(common.ansiStylizeTty('\n\n# Docker setup\n', 'bold'));
|
console.log(common.ansiStylizeTty('\n\n# Docker setup\n', 'bold'));
|
||||||
console.log(wrap80('This section will setup authentication to ' +
|
console.log(wrap80('This section will setup authentication to ' +
|
||||||
'Spearhead Datacenter\'s Docker endpoint using your account ' +
|
'Triton DataCenter\'s Docker endpoint using your account ' +
|
||||||
'and key information specified above. This is only required ' +
|
'and key information specified above. This is only required ' +
|
||||||
'if you intend to use `docker` with this profile.\n'));
|
'if you intend to use `docker` with this profile.\n'));
|
||||||
|
|
||||||
@ -424,7 +428,7 @@ do_create.options = [
|
|||||||
names: ['file', 'f'],
|
names: ['file', 'f'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
helpArg: 'FILE',
|
helpArg: 'FILE',
|
||||||
help: 'A JSON file (of the same form as "spearhead profile get -j") ' +
|
help: 'A JSON file (of the same form as "triton profile get -j") ' +
|
||||||
'with the profile, or "-" to read JSON from stdin.'
|
'with the profile, or "-" to read JSON from stdin.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -437,7 +441,7 @@ do_create.options = [
|
|||||||
{
|
{
|
||||||
names: ['no-docker'],
|
names: ['no-docker'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'As of Spearhead CLI 4.9, creating a profile will attempt (on '
|
help: 'As of Triton CLI 4.9, creating a profile will attempt (on '
|
||||||
+ 'non-Windows) to also setup for running Docker. This is '
|
+ 'non-Windows) to also setup for running Docker. This is '
|
||||||
+ 'experimental and might fail. Use this option to disable '
|
+ 'experimental and might fail. Use this option to disable '
|
||||||
+ 'the attempt.'
|
+ 'the attempt.'
|
||||||
@ -452,19 +456,19 @@ do_create.options = [
|
|||||||
|
|
||||||
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
||||||
do_create.help = [
|
do_create.help = [
|
||||||
'Create a Spearhead CLI profile.',
|
'Create a Triton CLI profile.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'',
|
'',
|
||||||
'Examples:',
|
'Examples:',
|
||||||
' spearhead profile create # interactively create a profile',
|
' triton profile create # interactively create a profile',
|
||||||
' spearhead profile create --copy env # ... copying from "env" profile',
|
' triton profile create --copy env # ... copying from "env" profile',
|
||||||
'',
|
'',
|
||||||
' # Or non-interactively create from stdin or a file:',
|
' # Or non-interactively create from stdin or a file:',
|
||||||
' cat a-profile.json | spearhead profile create -f -',
|
' cat a-profile.json | triton profile create -f -',
|
||||||
' spearhead profile create -f another-profile.json'
|
' triton profile create -f another-profile.json'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ do_delete.options = [
|
|||||||
|
|
||||||
do_delete.synopses = ['{{name}} {{cmd}} PROFILE'];
|
do_delete.synopses = ['{{name}} {{cmd}} PROFILE'];
|
||||||
do_delete.help = [
|
do_delete.help = [
|
||||||
'Delete a Spearhead CLI profile.',
|
'Delete a Triton CLI profile.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
|
@ -23,8 +23,7 @@ function do_docker_setup(subcmd, opts, args, cb) {
|
|||||||
cli: this.top,
|
cli: this.top,
|
||||||
name: profileName,
|
name: profileName,
|
||||||
implicit: false,
|
implicit: false,
|
||||||
yes: opts.yes,
|
yes: opts.yes
|
||||||
lifetime: opts.lifetime
|
|
||||||
}, cb);
|
}, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +33,6 @@ do_docker_setup.options = [
|
|||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Show this help.'
|
help: 'Show this help.'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
names: ['lifetime', 't'],
|
|
||||||
type: 'number',
|
|
||||||
help: 'Lifetime of the generated docker certificate, in days'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
names: ['yes', 'y'],
|
names: ['yes', 'y'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
@ -49,21 +43,21 @@ do_docker_setup.options = [
|
|||||||
do_docker_setup.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
do_docker_setup.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
||||||
do_docker_setup.help = [
|
do_docker_setup.help = [
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
'Setup for using Docker with the current Spearhead CLI profile.',
|
'Setup for using Docker with the current Triton CLI profile.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'A Spearhead datacenter can act as a virtual Docker Engine, where the entire',
|
'A Triton datacenter can act as a virtual Docker Engine, where the entire',
|
||||||
'datacenter is available for running containers. The datacenter provides',
|
'datacenter is available on for running containers. The datacenter provides',
|
||||||
'an endpoint against which you can run the regular `docker` client. This',
|
'an endpoint against which you can run the regular `docker` client. This',
|
||||||
'requires a one time setup to (a) generate a client TLS certificate to enable',
|
'requires a one time setup to (a) generate a client TLS certificate to enable',
|
||||||
'secure authentication with the Spearhead Docker Engine, and (b) to determine',
|
'secure authentication with the Triton Docker Engine, and (b) to determine',
|
||||||
'the DOCKER_HOST and related environment variables.',
|
'the DOCKER_HOST and related environment variables.',
|
||||||
'',
|
'',
|
||||||
'After running this, you can setup your shell environment for `docker` via:',
|
'After running this, you can setup your shell environment for `docker` via:',
|
||||||
' eval "$(spearhead env --docker)"',
|
' eval "$(triton env --docker)"',
|
||||||
'or the equivalent. See `spearhead env --help` for details.'
|
'or the equivalent. See `triton env --help` for details.'
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ do_edit.options = [
|
|||||||
|
|
||||||
do_edit.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
do_edit.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
||||||
do_edit.help = [
|
do_edit.help = [
|
||||||
'Edit a Spearhead CLI profile in your $EDITOR.',
|
'Edit a Triton CLI profile in your $EDITOR.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
|
@ -76,7 +76,7 @@ do_get.options = [
|
|||||||
|
|
||||||
do_get.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
do_get.synopses = ['{{name}} {{cmd}} [PROFILE]'];
|
||||||
do_get.help = [
|
do_get.help = [
|
||||||
'Get a Spearhead CLI profile.',
|
'Get a Triton CLI profile.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
|
@ -31,8 +31,7 @@ function _listProfiles(cli, opts, args, cb) {
|
|||||||
try {
|
try {
|
||||||
profiles = mod_config.loadAllProfiles({
|
profiles = mod_config.loadAllProfiles({
|
||||||
configDir: cli.configDir,
|
configDir: cli.configDir,
|
||||||
log: cli.log,
|
log: cli.log
|
||||||
profileOverrides: cli._cliOptsAsProfile()
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
@ -82,12 +81,12 @@ function _listProfiles(cli, opts, args, cb) {
|
|||||||
if (profiles.length === 0) {
|
if (profiles.length === 0) {
|
||||||
process.stderr.write('\nWarning: There is no current profile. '
|
process.stderr.write('\nWarning: There is no current profile. '
|
||||||
+ 'Use "triton profile create" to create one,\n'
|
+ 'Use "triton profile create" to create one,\n'
|
||||||
+ 'or set the required "SC_*" environment '
|
+ 'or set the required "SDC_*/TRITON_*" environment '
|
||||||
+ 'variables: see "spearhead --help".\n');
|
+ 'variables: see "triton --help".\n');
|
||||||
} else {
|
} else {
|
||||||
process.stderr.write('\nWarning: There is no current profile. '
|
process.stderr.write('\nWarning: There is no current profile. '
|
||||||
+ 'Use "spearhead profile set-current ..."\n'
|
+ 'Use "triton profile set-current ..."\n'
|
||||||
+ 'to set one or "spearhead profile create" to create one.\n');
|
+ 'to set one or "triton profile create" to create one.\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,15 +117,15 @@ do_list.options = [
|
|||||||
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
||||||
do_list.help = [
|
do_list.help = [
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
'List Spearhead CLI profiles.',
|
'List Triton CLI profiles.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
'{{options}}',
|
'{{options}}',
|
||||||
'A profile is a configured Spearhead Datacenter endpoint and associated info.',
|
'A profile is a configured Triton CloudAPI endpoint and associated info.',
|
||||||
'I.e. the URL, account name, SSH key fingerprint, etc. information required',
|
'I.e. the URL, account name, SSH key fingerprint, etc. information required',
|
||||||
'to call a CloudAPI endpoint in a Spearhead datacenter. You can then switch',
|
'to call a CloudAPI endpoint in a Triton datacenter. You can then switch',
|
||||||
'between profiles with `spearhead -p PROFILE`, the SC_PROFILE environment',
|
'between profiles with `triton -p PROFILE`, the TRITON_PROFILE environment',
|
||||||
'variable, or by setting your current profile.',
|
'variable, or by setting your current profile.',
|
||||||
'',
|
'',
|
||||||
'The "CURR" column indicates which profile is the current one.'
|
'The "CURR" column indicates which profile is the current one.'
|
||||||
|
@ -34,7 +34,7 @@ do_set_current.options = [
|
|||||||
|
|
||||||
do_set_current.synopses = ['{{name}} {{cmd}} PROFILE'];
|
do_set_current.synopses = ['{{name}} {{cmd}} PROFILE'];
|
||||||
do_set_current.help = [
|
do_set_current.help = [
|
||||||
'Set the current Spearhead CLI profile.',
|
'Set the current Triton CLI profile.',
|
||||||
'',
|
'',
|
||||||
'{{usage}}',
|
'{{usage}}',
|
||||||
'',
|
'',
|
||||||
@ -43,7 +43,7 @@ do_set_current.help = [
|
|||||||
'previously set profile.',
|
'previously set profile.',
|
||||||
'',
|
'',
|
||||||
'The "current" profile is the one used by default, unless overridden by',
|
'The "current" profile is the one used by default, unless overridden by',
|
||||||
'`spearhead -p PROFILE-NAME ...` or the SC_PROFILE environment variable.'
|
'`triton -p PROFILE-NAME ...` or the TRITON_PROFILE environment variable.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
do_set_current.aliases = ['set'];
|
do_set_current.aliases = ['set'];
|
||||||
|
@ -23,12 +23,12 @@ function ProfileCLI(top) {
|
|||||||
name: top.name + ' profile',
|
name: top.name + ' profile',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List, get, create and update Spearhead CLI profiles.',
|
'List, get, create and update Triton CLI profiles.',
|
||||||
'',
|
'',
|
||||||
'A profile is a configured Spearhead Datacenter endpoint. I.e. the',
|
'A profile is a configured Triton CloudAPI endpoint. I.e. the',
|
||||||
'url, account, key, etc. information required to call a CloudAPI.',
|
'url, account, key, etc. information required to call a CloudAPI.',
|
||||||
'You can then switch between profiles with `triton -p PROFILE`',
|
'You can then switch between profiles with `triton -p PROFILE`',
|
||||||
'or the SC_PROFILE environment variable.'
|
'or the TRITON_PROFILE environment variable.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
|
@ -24,7 +24,6 @@ var rimraf = require('rimraf');
|
|||||||
var semver = require('semver');
|
var semver = require('semver');
|
||||||
var sshpk = require('sshpk');
|
var sshpk = require('sshpk');
|
||||||
var mod_url = require('url');
|
var mod_url = require('url');
|
||||||
var crypto = require('crypto');
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
var which = require('which');
|
var which = require('which');
|
||||||
var wordwrap = require('wordwrap')(78);
|
var wordwrap = require('wordwrap')(78);
|
||||||
@ -129,6 +128,7 @@ function setCurrentProfile(opts, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the given profile for Docker usage. This means checking the cloudapi
|
* Setup the given profile for Docker usage. This means checking the cloudapi
|
||||||
* has a Docker service (ListServices), finding the user's SSH *private* key,
|
* has a Docker service (ListServices), finding the user's SSH *private* key,
|
||||||
@ -143,21 +143,14 @@ function setCurrentProfile(opts, cb) {
|
|||||||
* implicit, we silently skip if ListServices shows no Docker service.
|
* implicit, we silently skip if ListServices shows no Docker service.
|
||||||
* - {Boolean} yes: Optional. Boolean indicating if confirmation prompts
|
* - {Boolean} yes: Optional. Boolean indicating if confirmation prompts
|
||||||
* should be skipped, assuming a "yes" answer.
|
* should be skipped, assuming a "yes" answer.
|
||||||
* - {Number} lifetime: Optional. Number of days to make the Docker
|
|
||||||
* certificate valid for. Defaults to 3650 (10 years).
|
|
||||||
*/
|
*/
|
||||||
function profileDockerSetup(opts, cb) {
|
function profileDockerSetup(opts, cb) {
|
||||||
assert.object(opts.cli, 'opts.cli');
|
assert.object(opts.cli, 'opts.cli');
|
||||||
assert.string(opts.name, 'opts.name');
|
assert.string(opts.name, 'opts.name');
|
||||||
assert.optionalBool(opts.implicit, 'opts.implicit');
|
assert.optionalBool(opts.implicit, 'opts.implicit');
|
||||||
assert.optionalBool(opts.yes, 'opts.yes');
|
assert.optionalBool(opts.yes, 'opts.yes');
|
||||||
assert.optionalNumber(opts.lifetime, 'opts.lifetime');
|
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
/* Default to a 10 year certificate. */
|
|
||||||
if (!opts.lifetime)
|
|
||||||
opts.lifetime = 3650;
|
|
||||||
|
|
||||||
var cli = opts.cli;
|
var cli = opts.cli;
|
||||||
var tritonapi = cli.tritonapiFromProfileName({profileName: opts.name});
|
var tritonapi = cli.tritonapiFromProfileName({profileName: opts.name});
|
||||||
|
|
||||||
@ -172,22 +165,18 @@ function profileDockerSetup(opts, cb) {
|
|||||||
function dockerKeyWarning(arg, next) {
|
function dockerKeyWarning(arg, next) {
|
||||||
console.log(wordwrap('WARNING: Docker uses authentication via ' +
|
console.log(wordwrap('WARNING: Docker uses authentication via ' +
|
||||||
'client TLS certificates that do not support encrypted ' +
|
'client TLS certificates that do not support encrypted ' +
|
||||||
'(passphrase protected) keys or SSH agents.\n'));
|
'(passphrase protected) keys or SSH agents. If you continue, ' +
|
||||||
console.log(wordwrap('If you continue, this profile setup will ' +
|
'this profile setup will attempt to write a copy of your ' +
|
||||||
'create a fresh private key to be written unencrypted to ' +
|
'SSH private key formatted as an unencrypted TLS certificate ' +
|
||||||
'disk in "~/.spearhead/docker" for use by the Docker client. ' +
|
'in "~/.triton/docker" for use by the Docker client.\n'));
|
||||||
'This key will be useable only for Docker.\n'));
|
|
||||||
if (yes) {
|
if (yes) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
console.log(wordwrap('If you do not specifically want to use ' +
|
|
||||||
'Docker, you can answer "no" here.\n'));
|
|
||||||
}
|
}
|
||||||
common.promptYesNo({msg: 'Continue? [y/n] '}, function (answer) {
|
common.promptYesNo({msg: 'Continue? [y/n] '}, function (answer) {
|
||||||
if (answer !== 'y') {
|
if (answer !== 'y') {
|
||||||
console.error('Skipping Docker setup (you can run '
|
console.error('Skipping Docker setup (you can run '
|
||||||
+ '"spearhead profile docker-setup" later).');
|
+ '"triton profile docker-setup" later).');
|
||||||
next(true);
|
next(true);
|
||||||
} else {
|
} else {
|
||||||
console.log();
|
console.log();
|
||||||
@ -278,7 +267,7 @@ function profileDockerSetup(opts, cb) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
console.log(wordwrap('\nNote: No "docker" was found on '
|
console.log(wordwrap('\nNote: No "docker" was found on '
|
||||||
+ 'your PATH. It is not needed for this setup, but '
|
+ 'your PATH. It is not needed for this setup, but '
|
||||||
+ 'will be to run docker commands against Spearhead. '
|
+ 'will be to run docker commands against Triton. '
|
||||||
+ 'You can find out how to install it at '
|
+ 'You can find out how to install it at '
|
||||||
+ '<https://docs.docker.com/engine/installation/>.'));
|
+ '<https://docs.docker.com/engine/installation/>.'));
|
||||||
} else {
|
} else {
|
||||||
@ -322,143 +311,79 @@ function profileDockerSetup(opts, cb) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
function getSigningKey(arg, next) {
|
/*
|
||||||
var kr = new auth.KeyRing();
|
* We need the private key to format as a client cert. If this profile's
|
||||||
var profileFp = sshpk.parseFingerprint(profile.keyId);
|
* key was found in the SSH agent (and by default it prefers to take
|
||||||
kr.findSigningKeyPair(profileFp,
|
* it from there), then we can't use `tritonapi.keyPair`, because
|
||||||
function unlockAndStash(findErr, keyPair) {
|
* the SSH agent protocol will not allow us access to the private key
|
||||||
|
* data (by design).
|
||||||
|
*
|
||||||
|
* As a fallback we'll look (via KeyRing) for a local copy of the
|
||||||
|
* private key to use, and then unlock it if necessary.
|
||||||
|
*/
|
||||||
|
function getPrivKey(arg, next) {
|
||||||
|
// If the key pair already works, then use that...
|
||||||
|
try {
|
||||||
|
arg.privKey = tritonapi.keyPair.getPrivateKey();
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
} catch (_) {
|
||||||
|
// ... else fall through.
|
||||||
|
}
|
||||||
|
|
||||||
|
var kr = new auth.KeyRing();
|
||||||
|
var profileFp = sshpk.parseFingerprint(tritonapi.profile.keyId);
|
||||||
|
kr.find(profileFp, function (findErr, keyPairs) {
|
||||||
if (findErr) {
|
if (findErr) {
|
||||||
next(findErr);
|
next(findErr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
arg.signKeyPair = keyPair;
|
/*
|
||||||
if (!keyPair.isLocked()) {
|
* If our keyId was found, and with the 'homedir' plugin, then
|
||||||
next();
|
* we should have access to the private key (modulo unlocking).
|
||||||
return;
|
*/
|
||||||
|
var homedirKeyPair;
|
||||||
|
for (var i = 0; i < keyPairs.length; i++) {
|
||||||
|
if (keyPairs[i].plugin === 'homedir') {
|
||||||
|
homedirKeyPair = keyPairs[i];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (homedirKeyPair) {
|
||||||
common.promptPassphraseUnlockKey({
|
common.promptPassphraseUnlockKey({
|
||||||
/* Fake the `tritonapi` object, only `.keyPair` is used. */
|
// Fake the `tritonapi` object, only `.keyPair` is used.
|
||||||
tritonapi: { keyPair: keyPair }
|
tritonapi: {keyPair: homedirKeyPair}
|
||||||
}, next);
|
}, function (unlockErr) {
|
||||||
});
|
if (unlockErr) {
|
||||||
},
|
next(unlockErr);
|
||||||
function generateAndSignCert(arg, next) {
|
|
||||||
var key = arg.signKeyPair;
|
|
||||||
var pubKey = key.getPublicKey();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* There isn't a particular reason this has to be ECDSA, but
|
|
||||||
* Docker supports it, and ECDSA keys are much easier to
|
|
||||||
* generate from inside node than RSA ones (since sshpk will
|
|
||||||
* do them for us instead of us shelling out and mucking with
|
|
||||||
* temporary files).
|
|
||||||
*/
|
|
||||||
arg.privKey = sshpk.generatePrivateKey('ecdsa');
|
|
||||||
|
|
||||||
var id = sshpk.identityFromDN('CN=' + profile.account);
|
|
||||||
var parentId = sshpk.identityFromDN('CN=' +
|
|
||||||
pubKey.fingerprint('md5').toString('base64'));
|
|
||||||
var serial = crypto.randomBytes(8);
|
|
||||||
/*
|
|
||||||
* Backdate the certificate by 5 minutes to account for clock
|
|
||||||
* sync -- we only allow 5 mins drift in cloudapi generally so
|
|
||||||
* using the same amount here seems fine.
|
|
||||||
*/
|
|
||||||
var validFrom = new Date();
|
|
||||||
validFrom.setTime(validFrom.getTime() - 300*1000);
|
|
||||||
var validUntil = new Date();
|
|
||||||
validUntil.setTime(validFrom.getTime() +
|
|
||||||
24*3600*1000*opts.lifetime);
|
|
||||||
/*
|
|
||||||
* Generate it self-signed for now -- we will clear this
|
|
||||||
* signature out and replace it with the real one below.
|
|
||||||
*/
|
|
||||||
var cert = sshpk.createCertificate(id, arg.privKey, parentId,
|
|
||||||
arg.privKey, { validFrom: validFrom, validUntil: validUntil,
|
|
||||||
purposes: ['clientAuth', 'joyentDocker'], serial: serial });
|
|
||||||
|
|
||||||
var algo = pubKey.type + '-' + pubKey.defaultHashAlgorithm();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This code is using private API in sshpk because there is
|
|
||||||
* no public API as of 1.14.x for async signing of certificates.
|
|
||||||
*
|
|
||||||
* If the sshpk version in package.json is updated (even a
|
|
||||||
* patch bump) this code could break! This will be fixed up
|
|
||||||
* eventually, but for now we just have to be careful.
|
|
||||||
*/
|
|
||||||
var x509 = require('sshpk/lib/formats/x509');
|
|
||||||
cert.signatures = {};
|
|
||||||
cert.signatures.x509 = {};
|
|
||||||
cert.signatures.x509.algo = algo;
|
|
||||||
var signer = key.createSign({
|
|
||||||
user: profile.account,
|
|
||||||
algorithm: algo
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
* The smartdc-auth KeyPair signer produces an object with
|
|
||||||
* strings on it intended for http-signature instead of just a
|
|
||||||
* Signature instance (which is what the x509 format module
|
|
||||||
* expects). We wrap it up here to convert it.
|
|
||||||
*/
|
|
||||||
var signerConv = function (buf, ccb) {
|
|
||||||
signer(buf, function convertSignature(signErr, sigData) {
|
|
||||||
if (signErr) {
|
|
||||||
ccb(signErr);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var algparts = sigData.algorithm.split('-');
|
try {
|
||||||
var sig = sshpk.parseSignature(sigData.signature,
|
arg.privKey = homedirKeyPair.getPrivateKey();
|
||||||
algparts[0], 'asn1');
|
} catch (homedirErr) {
|
||||||
sig.hashAlgorithm = algparts[1];
|
next(new errors.SetupError(homedirErr, format(
|
||||||
sig.curve = pubKey.curve;
|
'could not obtain SSH private key for keyId ' +
|
||||||
ccb(null, sig);
|
'"%s" to create Docker certificate',
|
||||||
});
|
profile.keyId)));
|
||||||
};
|
|
||||||
/*
|
|
||||||
* Sign a "test" string first to double-check the hash algo
|
|
||||||
* it's going to use. The SSH agent may not support SHA256
|
|
||||||
* signatures, for example, and we will only find out by
|
|
||||||
* testing like this.
|
|
||||||
*/
|
|
||||||
signer('test', function afterTestSig(testErr, testSigData) {
|
|
||||||
|
|
||||||
if (testErr) {
|
|
||||||
next(new errors.SetupError(testErr, format(
|
|
||||||
'failed to sign Docker certificate using key ' +
|
|
||||||
'"%s"', profile.keyId)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cert.signatures.x509.algo = testSigData.algorithm;
|
|
||||||
|
|
||||||
x509.signAsync(cert, signerConv,
|
|
||||||
function afterCertSign(signErr) {
|
|
||||||
|
|
||||||
if (signErr) {
|
|
||||||
next(new errors.SetupError(signErr, format(
|
|
||||||
'failed to sign Docker certificate using key ' +
|
|
||||||
'"%s"', profile.keyId)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cert.issuerKey = undefined;
|
|
||||||
/* Double-check that it came out ok. */
|
|
||||||
assert.ok(cert.isSignedByKey(pubKey));
|
|
||||||
arg.cert = cert;
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
next(new errors.SetupError(format('could not obtain SSH ' +
|
||||||
|
'private key for keyId "%s" to create Docker ' +
|
||||||
|
'certificate', profile.keyId)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function makeClientCertDir(arg, next) {
|
|
||||||
|
function genClientCert_dir(arg, next) {
|
||||||
arg.dockerCertPath = path.resolve(cli.configDir,
|
arg.dockerCertPath = path.resolve(cli.configDir,
|
||||||
'docker', common.profileSlug(profile));
|
'docker', common.profileSlug(profile));
|
||||||
mkdirp(arg.dockerCertPath, next);
|
mkdirp(arg.dockerCertPath, next);
|
||||||
},
|
},
|
||||||
function writeClientCertKey(arg, next) {
|
function genClientCert_key(arg, next) {
|
||||||
arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem');
|
arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem');
|
||||||
var data = arg.privKey.toBuffer('pkcs1');
|
var data = arg.privKey.toBuffer('pkcs1');
|
||||||
fs.writeFile(arg.keyPath, data, function (err) {
|
fs.writeFile(arg.keyPath, data, function (err) {
|
||||||
@ -470,9 +395,12 @@ function profileDockerSetup(opts, cb) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function writeClientCert(arg, next) {
|
function genClientCert_cert(arg, next) {
|
||||||
arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem');
|
arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem');
|
||||||
var data = arg.cert.toBuffer('pem');
|
|
||||||
|
var id = sshpk.identityFromDN('CN=' + profile.account);
|
||||||
|
var cert = sshpk.createSelfSignedCertificate(id, arg.privKey);
|
||||||
|
var data = cert.toBuffer('pem');
|
||||||
|
|
||||||
fs.writeFile(arg.certPath, data, function (err) {
|
fs.writeFile(arg.certPath, data, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -550,10 +478,10 @@ function profileDockerSetup(opts, cb) {
|
|||||||
'Successfully setup profile "%s" to use Docker%s.',
|
'Successfully setup profile "%s" to use Docker%s.',
|
||||||
'',
|
'',
|
||||||
'To setup environment variables to use the Docker client, run:',
|
'To setup environment variables to use the Docker client, run:',
|
||||||
' eval "$(spearhead env --docker %s)"',
|
' eval "$(triton env --docker %s)"',
|
||||||
' docker%s info',
|
' docker%s info',
|
||||||
'Or you can place the commands in your shell profile, e.g.:',
|
'Or you can place the commands in your shell profile, e.g.:',
|
||||||
' spearhead env --docker %s >> ~/.profile'
|
' triton env --docker %s >> ~/.profile'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
profile.name,
|
profile.name,
|
||||||
(arg.dockerVersion ? format(' (v%s)', arg.dockerVersion) : ''),
|
(arg.dockerVersion ? format(' (v%s)', arg.dockerVersion) : ''),
|
||||||
|
@ -20,7 +20,7 @@ function do_profiles(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_profiles.help = 'A shortcut for "spearhead profile list".\n' + targ.help;
|
do_profiles.help = 'A shortcut for "triton profile list".\n' + targ.help;
|
||||||
do_profiles.synopses = targ.synopses;
|
do_profiles.synopses = targ.synopses;
|
||||||
do_profiles.options = targ.options;
|
do_profiles.options = targ.options;
|
||||||
do_profiles.completionArgtypes = targ.completionArgtypes;
|
do_profiles.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -198,7 +198,7 @@ do_apply.options = [
|
|||||||
{
|
{
|
||||||
names: ['dev-create-keys-and-profiles'],
|
names: ['dev-create-keys-and-profiles'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Convenient option to generate keys and Spearhead CLI profiles ' +
|
help: 'Convenient option to generate keys and Triton CLI profiles ' +
|
||||||
'for all users. For experimenting only. See section below.'
|
'for all users. For experimenting only. See section below.'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -214,7 +214,7 @@ do_apply.help = [
|
|||||||
'{{options}}',
|
'{{options}}',
|
||||||
'If "--file FILE" is not specified, this defaults to using "./rbac.json".',
|
'If "--file FILE" is not specified, this defaults to using "./rbac.json".',
|
||||||
'The RBAC configuration is loaded from FILE and compared to the live',
|
'The RBAC configuration is loaded from FILE and compared to the live',
|
||||||
'RBAC state (see `spearhead rbac info`). It then calculates necessary updates,',
|
'RBAC state (see `triton rbac info`). It then calculates necessary updates,',
|
||||||
'confirms, and applies them.',
|
'confirms, and applies them.',
|
||||||
'',
|
'',
|
||||||
'Warning: Currently, RBAC state updates can take a few seconds to appear',
|
'Warning: Currently, RBAC state updates can take a few seconds to appear',
|
||||||
@ -223,10 +223,10 @@ do_apply.help = [
|
|||||||
'',
|
'',
|
||||||
'The "--dev-create-keys-and-profiles" option is provided for **experimenting',
|
'The "--dev-create-keys-and-profiles" option is provided for **experimenting',
|
||||||
'with, developing, or testing** Triton RBAC. It will create a key and setup a ',
|
'with, developing, or testing** Triton RBAC. It will create a key and setup a ',
|
||||||
'Spearhead CLI profile for each user (named "$currprofile-user-$login"). This ',
|
'Triton CLI profile for each user (named "$currprofile-user-$login"). This ',
|
||||||
'simplies using the CLI as that user:',
|
'simplies using the CLI as that user:',
|
||||||
' spearhead -p coal-user-bob create ...',
|
' triton -p coal-user-bob create ...',
|
||||||
' spearhead -p coal-user-sarah imgs',
|
' triton -p coal-user-sarah imgs',
|
||||||
'Note that proper production usage of RBAC should have the administrator',
|
'Note that proper production usage of RBAC should have the administrator',
|
||||||
'never seeing each user\'s private key.',
|
'never seeing each user\'s private key.',
|
||||||
'',
|
'',
|
||||||
|
@ -125,8 +125,12 @@ function _addUserKey(opts, cb) {
|
|||||||
if (opts.file !== '-') {
|
if (opts.file !== '-') {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
ctx.data = stdin;
|
ctx.data = stdin;
|
||||||
ctx.from = '<stdin>';
|
ctx.from = '<stdin>';
|
||||||
next();
|
next();
|
||||||
|
@ -291,8 +291,12 @@ function _addPolicy(opts, cb) {
|
|||||||
if (opts.file !== '-') {
|
if (opts.file !== '-') {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(stdin);
|
data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -287,8 +287,12 @@ function _addRole(opts, cb) {
|
|||||||
if (opts.file !== '-') {
|
if (opts.file !== '-') {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(stdin);
|
data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -282,8 +282,12 @@ function _addUser(opts, cb) {
|
|||||||
if (opts.file !== '-') {
|
if (opts.file !== '-') {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
var stdin = '';
|
||||||
common.readStdin(function gotStdin(stdin) {
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(stdin);
|
data = JSON.parse(stdin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -25,7 +25,7 @@ function RbacCLI(top) {
|
|||||||
desc: [
|
desc: [
|
||||||
'Role-based Access Control (RBAC) commands.',
|
'Role-based Access Control (RBAC) commands.',
|
||||||
'See <https://docs.joyent.com/public-cloud/rbac> for a general start.',
|
'See <https://docs.joyent.com/public-cloud/rbac> for a general start.',
|
||||||
'**Warning: `spearhead rbac ...` is experimental, not well tested and in flux.**'
|
'**Warning: `triton rbac ...` is experimental, not well tested and in flux.**'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
|
@ -20,7 +20,7 @@ function do_reboot(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_reboot.help = 'A shortcut for "spearhead instance reboot".\n' + targ.help;
|
do_reboot.help = 'A shortcut for "triton instance reboot".\n' + targ.help;
|
||||||
do_reboot.synopses = targ.synopses;
|
do_reboot.synopses = targ.synopses;
|
||||||
do_reboot.options = targ.options;
|
do_reboot.options = targ.options;
|
||||||
do_reboot.completionArgtypes = targ.completionArgtypes;
|
do_reboot.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -20,7 +20,7 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_ssh.help = 'A shortcut for "spearhead instance ssh".\n' + targ.help;
|
do_ssh.help = 'A shortcut for "triton instance ssh".\n' + targ.help;
|
||||||
do_ssh.synopses = targ.synopses;
|
do_ssh.synopses = targ.synopses;
|
||||||
do_ssh.interspersedOptions = targ.interspersedOptions;
|
do_ssh.interspersedOptions = targ.interspersedOptions;
|
||||||
do_ssh.options = targ.options;
|
do_ssh.options = targ.options;
|
||||||
|
@ -20,7 +20,7 @@ function do_start(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_start.help = 'A shortcut for "spearhead instance start".\n' + targ.help;
|
do_start.help = 'A shortcut for "triton instance start".\n' + targ.help;
|
||||||
do_start.synopses = targ.synopses;
|
do_start.synopses = targ.synopses;
|
||||||
do_start.options = targ.options;
|
do_start.options = targ.options;
|
||||||
do_start.completionArgtypes = targ.completionArgtypes;
|
do_start.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -20,7 +20,7 @@ function do_stop(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_stop.help = 'A shortcut for "spearhead instance stop".\n' + targ.help;
|
do_stop.help = 'A shortcut for "triton instance stop".\n' + targ.help;
|
||||||
do_stop.synopses = targ.synopses;
|
do_stop.synopses = targ.synopses;
|
||||||
do_stop.options = targ.options;
|
do_stop.options = targ.options;
|
||||||
do_stop.completionArgtypes = targ.completionArgtypes;
|
do_stop.completionArgtypes = targ.completionArgtypes;
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan create ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var format = require('util').format;
|
|
||||||
var jsprim = require('jsprim');
|
|
||||||
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.optionalString(opts.description, 'opts.description');
|
|
||||||
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 === 0) {
|
|
||||||
cb(new errors.UsageError('missing VLAN argument'));
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
cb(new errors.UsageError('incorrect number of arguments'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var vlanId = jsprim.parseInteger(args[0], { allowSign: false });
|
|
||||||
if (typeof (vlanId) !== 'number') {
|
|
||||||
cb(new errors.UsageError('VLAN must be an integer'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.name) {
|
|
||||||
cb(new errors.UsageError('must provide a --name (-n)'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var createOpts = {
|
|
||||||
vlan_id: vlanId,
|
|
||||||
name: opts.name
|
|
||||||
};
|
|
||||||
|
|
||||||
if (opts.description) {
|
|
||||||
createOpts.description = opts.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cloudapi = cli.tritonapi.cloudapi;
|
|
||||||
cloudapi.createFabricVlan(createOpts, function onCreate(err, vlan) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
console.log(JSON.stringify(vlan));
|
|
||||||
} else {
|
|
||||||
if (vlan.name) {
|
|
||||||
console.log('Created vlan %s (%d)', vlan.name,
|
|
||||||
vlan.vlan_id);
|
|
||||||
} else {
|
|
||||||
console.log('Created vlan %d', vlan.vlan_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_create.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: 'Create options'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['name', 'n'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'NAME',
|
|
||||||
help: 'Name of the VLAN.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['description', 'D'],
|
|
||||||
type: 'string',
|
|
||||||
helpArg: 'DESC',
|
|
||||||
help: 'Description of the VLAN.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: 'Other options'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['json', 'j'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'JSON stream output.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
|
||||||
|
|
||||||
do_create.help = [
|
|
||||||
'Create a VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Example:',
|
|
||||||
' triton vlan create -n "dmz" -D "Demilitarized zone" 73'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_create.helpOpts = {
|
|
||||||
helpCol: 16
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = do_create;
|
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan 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 VLAN argument(s)'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
var vlanIds = args;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vasync.forEachParallel({
|
|
||||||
inputs: vlanIds,
|
|
||||||
func: function deleteOne(id, next) {
|
|
||||||
cli.tritonapi.deleteFabricVlan({ vlan_id: id },
|
|
||||||
function onDelete(err) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Deleted vlan %s', id);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_delete.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
do_delete.synopses = ['{{name}} {{cmd}} VLAN [VLAN ...]'];
|
|
||||||
|
|
||||||
do_delete.help = [
|
|
||||||
'Remove a VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where VLAN is a VLAN id or name.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_delete.aliases = ['rm'];
|
|
||||||
|
|
||||||
do_delete.completionArgtypes = ['tritonvlan'];
|
|
||||||
|
|
||||||
module.exports = do_delete;
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan 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 VLAN argument'));
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
cb(new errors.UsageError('incorrect number of arguments'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = args[0];
|
|
||||||
var cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.tritonapi.getFabricVlan(id, function onGet(err, vlan) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
console.log(JSON.stringify(vlan));
|
|
||||||
} else {
|
|
||||||
console.log(JSON.stringify(vlan, 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}} VLAN'];
|
|
||||||
|
|
||||||
do_get.help = [
|
|
||||||
'Show a specific VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where VLAN is a VLAN id or name.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_get.completionArgtypes = ['tritonvlan', 'none'];
|
|
||||||
|
|
||||||
module.exports = do_get;
|
|
@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan list ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
|
||||||
var tabula = require('tabula');
|
|
||||||
|
|
||||||
var common = require('../common');
|
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
|
|
||||||
var COLUMNS_DEFAULT = 'vlan_id,name,description';
|
|
||||||
var SORT_DEFAULT = 'vlan_id';
|
|
||||||
var VALID_FILTERS = ['vlan_id', 'name', 'description'];
|
|
||||||
|
|
||||||
|
|
||||||
function do_list(subcmd, opts, args, cb) {
|
|
||||||
assert.object(opts, 'opts');
|
|
||||||
assert.array(args, 'args');
|
|
||||||
assert.func(cb, 'cb');
|
|
||||||
|
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var filters = common.objFromKeyValueArgs(args, {
|
|
||||||
validKeys: VALID_FILTERS,
|
|
||||||
disableDotted: true
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
cb(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.vlan_id !== undefined) {
|
|
||||||
filters.vlan_id = +filters.vlan_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cli = this.top;
|
|
||||||
|
|
||||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
|
||||||
if (setupErr) {
|
|
||||||
cb(setupErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cloudapi = cli.tritonapi.cloudapi;
|
|
||||||
cloudapi.listFabricVlans({}, function onList(err, vlans) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do filtering
|
|
||||||
Object.keys(filters).forEach(function doFilter(key) {
|
|
||||||
var val = filters[key];
|
|
||||||
vlans = vlans.filter(function (vlan) {
|
|
||||||
return vlan[key] === val;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
common.jsonStream(vlans);
|
|
||||||
} else {
|
|
||||||
var columns = COLUMNS_DEFAULT;
|
|
||||||
|
|
||||||
if (opts.o) {
|
|
||||||
columns = opts.o;
|
|
||||||
}
|
|
||||||
|
|
||||||
columns = columns.split(',');
|
|
||||||
var sort = opts.s.split(',');
|
|
||||||
|
|
||||||
tabula(vlans, {
|
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_list.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
}
|
|
||||||
].concat(common.getCliTableOptions({
|
|
||||||
sortDefault: SORT_DEFAULT
|
|
||||||
}));
|
|
||||||
|
|
||||||
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
|
|
||||||
|
|
||||||
do_list.help = [
|
|
||||||
'List VLANs.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'Filters:',
|
|
||||||
' FIELD=<integer> Number filter. Supported fields: vlan_id',
|
|
||||||
' FIELD=<string> String filter. Supported fields: name, description',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Filters are applied client-side (i.e. done by the triton command itself).'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_list.aliases = ['ls'];
|
|
||||||
|
|
||||||
module.exports = do_list;
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan networks ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
|
|
||||||
function do_networks(subcmd, opts, args, cb) {
|
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length === 0) {
|
|
||||||
cb(new errors.UsageError('missing VLAN argument'));
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
cb(new errors.UsageError('incorrect number of arguments'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.vlan_id = args[0];
|
|
||||||
|
|
||||||
this.top.handlerFromSubcmd('network').dispatch({
|
|
||||||
subcmd: 'list',
|
|
||||||
opts: opts,
|
|
||||||
args: []
|
|
||||||
}, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
do_networks.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
|
||||||
|
|
||||||
do_networks.help = [
|
|
||||||
'Show all networks on a VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
'Where VLAN is a VLAN id or name.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_networks.options = require('../do_network/do_list').options;
|
|
||||||
|
|
||||||
module.exports = do_networks;
|
|
@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan 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_VLAN_FIELDS
|
|
||||||
= require('../cloudapi2').CloudApi.prototype.UPDATE_VLAN_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 VLAN argument'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = args.shift();
|
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
|
||||||
function gatherDataArgs(ctx, next) {
|
|
||||||
if (opts.file) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ctx.data = common.objFromKeyValueArgs(args, {
|
|
||||||
disableDotted: true,
|
|
||||||
typeHintFromKey: UPDATE_VLAN_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 vlan update in "%s": %s',
|
|
||||||
opts.file, err)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
|
|
||||||
function gatherDataStdin(ctx, next) {
|
|
||||||
if (opts.file !== '-') {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdin = '';
|
|
||||||
|
|
||||||
process.stdin.resume();
|
|
||||||
process.stdin.on('data', function (chunk) {
|
|
||||||
stdin += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stdin.on('error', console.error);
|
|
||||||
|
|
||||||
process.stdin.on('end', function () {
|
|
||||||
try {
|
|
||||||
ctx.data = JSON.parse(stdin);
|
|
||||||
} catch (err) {
|
|
||||||
log.trace({stdin: stdin},
|
|
||||||
'invalid VLAN update JSON on stdin');
|
|
||||||
next(new errors.TritonError(format(
|
|
||||||
'invalid JSON for VLAN update on stdin: %s',
|
|
||||||
err)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
function validateIt(ctx, next) {
|
|
||||||
assert.object(ctx.data, 'ctx.data');
|
|
||||||
|
|
||||||
var keys = Object.keys(ctx.data);
|
|
||||||
|
|
||||||
if (keys.length === 0) {
|
|
||||||
console.log('No fields given for VLAN update');
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var key = keys[i];
|
|
||||||
var value = ctx.data[key];
|
|
||||||
var type = UPDATE_VLAN_FIELDS[key];
|
|
||||||
if (!type) {
|
|
||||||
next(new errors.UsageError(format('unknown or ' +
|
|
||||||
'unupdateable field: %s (updateable fields are: %s)',
|
|
||||||
key,
|
|
||||||
Object.keys(UPDATE_VLAN_FIELDS).sort().join(', '))));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof (value) !== type) {
|
|
||||||
next(new errors.UsageError(format('field "%s" must be ' +
|
|
||||||
'of type "%s", but got a value of type "%s"', key,
|
|
||||||
type, typeof (value))));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
|
|
||||||
function updateAway(ctx, next) {
|
|
||||||
var data = ctx.data;
|
|
||||||
data.vlan_id = id;
|
|
||||||
|
|
||||||
tritonapi.updateFabricVlan(data, function onUpdate(err) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data.vlan_id;
|
|
||||||
console.log('Updated vlan %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}} VLAN [FIELD=VALUE ...]',
|
|
||||||
'{{name}} {{cmd}} -f JSON-FILE VLAN'
|
|
||||||
];
|
|
||||||
|
|
||||||
do_update.help = [
|
|
||||||
'Update a VLAN.',
|
|
||||||
'',
|
|
||||||
'{{usage}}',
|
|
||||||
'',
|
|
||||||
'{{options}}',
|
|
||||||
|
|
||||||
'Updateable fields:',
|
|
||||||
' ' + Object.keys(UPDATE_VLAN_FIELDS).sort().map(function (f) {
|
|
||||||
return f + ' (' + UPDATE_VLAN_FIELDS[f] + ')';
|
|
||||||
}).join(', '),
|
|
||||||
'',
|
|
||||||
'Where VLAN is a VLAN id or name.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
do_update.completionArgtypes = ['tritonvlan', 'tritonupdatevlanfield'];
|
|
||||||
|
|
||||||
module.exports = do_update;
|
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 vlan ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Cmdln = require('cmdln').Cmdln;
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---- CLI class
|
|
||||||
|
|
||||||
function VlanCLI(top) {
|
|
||||||
this.top = top;
|
|
||||||
|
|
||||||
Cmdln.call(this, {
|
|
||||||
name: top.name + ' vlan',
|
|
||||||
desc: 'List and manage Triton fabric VLANs.',
|
|
||||||
helpSubcmds: [
|
|
||||||
'help',
|
|
||||||
'list',
|
|
||||||
'get',
|
|
||||||
'create',
|
|
||||||
'update',
|
|
||||||
'delete',
|
|
||||||
{ group: '' },
|
|
||||||
'networks'
|
|
||||||
],
|
|
||||||
helpOpts: {
|
|
||||||
minHelpCol: 23
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
util.inherits(VlanCLI, Cmdln);
|
|
||||||
|
|
||||||
VlanCLI.prototype.init = function init(opts, args, cb) {
|
|
||||||
this.log = this.top.log;
|
|
||||||
Cmdln.prototype.init.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
VlanCLI.prototype.do_list = require('./do_list');
|
|
||||||
VlanCLI.prototype.do_create = require('./do_create');
|
|
||||||
VlanCLI.prototype.do_get = require('./do_get');
|
|
||||||
VlanCLI.prototype.do_update = require('./do_update');
|
|
||||||
VlanCLI.prototype.do_delete = require('./do_delete');
|
|
||||||
VlanCLI.prototype.do_networks = require('./do_networks');
|
|
||||||
|
|
||||||
module.exports = VlanCLI;
|
|
@ -78,7 +78,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
if (volCreateErr &&
|
if (volCreateErr &&
|
||||||
volCreateErr.name === 'VolumeSizeNotAvailableError') {
|
volCreateErr.name === 'VolumeSizeNotAvailableError') {
|
||||||
next(new Error('volume size not available, use ' +
|
next(new Error('volume size not available, use ' +
|
||||||
'spearhead volume sizes command for available sizes'));
|
'triton volume sizes command for available sizes'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,13 +96,14 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
var created;
|
var created;
|
||||||
var volume = volumes[i];
|
var volume = volumes[i];
|
||||||
|
|
||||||
created = new Date(volume.created);
|
created = new Date(volume.create_timestamp);
|
||||||
|
|
||||||
if (volume.filesystem_path !== undefined) {
|
if (volume.filesystem_path !== undefined) {
|
||||||
volume.resource = volume.filesystem_path;
|
volume.resource = volume.filesystem_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume.shortid = volume.id.split('-', 1)[0];
|
volume.shortid = volume.id.split('-', 1)[0];
|
||||||
|
volume.created = volume.create_timestamp;
|
||||||
volume.age = common.longAgo(created, now);
|
volume.age = common.longAgo(created, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ function VolumeCLI(top) {
|
|||||||
name: top.name + ' volume',
|
name: top.name + ' volume',
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
desc: [
|
desc: [
|
||||||
'List and manage Spearhead volumes.'
|
'List and manage Triton volumes.'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
|
@ -20,7 +20,7 @@ function do_volumes(subcmd, opts, args, callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_volumes.help = 'A shortcut for "spearhead volumes list".\n' + targ.help;
|
do_volumes.help = 'A shortcut for "triton volumes list".\n' + targ.help;
|
||||||
do_volumes.aliases = ['vols'];
|
do_volumes.aliases = ['vols'];
|
||||||
do_volumes.hidden = true;
|
do_volumes.hidden = true;
|
||||||
do_volumes.options = targ.options;
|
do_volumes.options = targ.options;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user