Compare commits

...

820 Commits

Author SHA1 Message Date
6606b65c9b v8.0.0 2018-10-19 17:31:41 +02:00
61160fd2f5 Merge pull request #991 from resin-io/v8-meta-branch
Release CLI v8
2018-10-19 17:29:40 +02:00
bf71f9ea16 Merge pull request #981 from resin-io/local-mode-v2
Local mode v2
2018-10-19 17:09:28 +02:00
fe751fdb23 Check supervisor version before attempting to do a local push
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:45:23 +02:00
947f91d570 Support multicontainer local mode in resin push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:56 +02:00
c5d4e30e24 logger: Add logs logging function
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:53 +02:00
f560aa7523 export resolveProject function from compose module
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:49 +02:00
6bcfb2dd51 logs: Add log build function to logger
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:44 +02:00
bf062124f7 compose: Add compose typings
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:39 +02:00
221666f59a Stop accepting resin-compose.yml as a build composition definition
These files are not supported by any other part of the resin
infrastructure, and it could cause confusion with it not being
supported everywhere. The idea was originally added because we
thought we might need to make extensions on docker-compose, but
that hasn't happened.

Change-type: major
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:49 +02:00
4369a2d161 tconfig: Add skipLibCheck to tsconfig
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:46 +02:00
cd6ee4ef5e Send push source packages as gzipped data
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:39 +02:00
872b17cf24 refactor: Allow setting of a remote build error message
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:32 +02:00
88e11347bc tests: Add tests for ignore files
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:28 +02:00
a3dd489c70 Respect ignore files when tarring sources
This commit brings in the ignore and dockerignore libraries, which when
provided with the patterns in the aforementioned files will ignore them.

Change-type: major
Closes: 889
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:22 +02:00
0c1c108b2b Check for correct architecture when preloading, instead of correct device type
Preload will now propose to preload any app that matches the image
architecture.

Change-type: major
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2018-10-19 16:43:02 +02:00
f02ed43f33 Default preload boolean parameters to false
Change-type: patch
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2018-10-19 16:42:51 +02:00
63c3d7ceee fix: Apply prettier to merged files
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:48 +02:00
dac45a884e dev: Add fast test npm task, to speed development
Currently running the tests is painfully slow, this commit adds a task
which will run the bare minimum build, and then the tests, speeding up
the process by an order of magnitude.

I had to repeat `gulp test`, instead of reusing `npm run test`, so that
the pretest task isn't ran too.

Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:42 +02:00
ec589c2639 Correctly error out on failed remote builds
The push command was relying on the output from the builder to indicate
the build status, but this isn't helpful for CI. This commit makes the
remote build module respect the `isError` flag which the builder sends
in any errors. Any errors which come from the builder indicate the
release will not be deployed.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:14 +02:00
f65e777d1b Bump tsconfig target to es6
Change-type: major
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:06 +02:00
684ac9fa24 v7.10.9 2018-10-18 21:08:35 +02:00
330cbc6a68 Merge pull request #989 from resin-io/sdk-references-update
Update sdk references in wizzard.coffee
2018-10-18 21:06:38 +02:00
14bfca8c3a v7.10.8 2018-10-18 20:14:43 +02:00
20c07d31b2 Merge pull request #987 from resin-io/sdk-references-update
Update sdk references in device.coffee
2018-10-18 20:12:08 +02:00
64b4f67477 Update sdk references in wizzard.coffee
Change-type:patch
2018-10-18 18:53:03 +02:00
a8ceadc300 v7.10.7 2018-10-18 17:25:57 +02:00
973d25f467 Merge pull request #986 from resin-io/sdk-references-update
Update sdk sdk references in auth.coffee
2018-10-18 17:24:02 +02:00
0d06701e2f Update sdk references in notes.coffee
Change-type:patch
2018-10-18 16:22:35 +02:00
379f1cc217 Update sdk references in device.coffee
Change-type:patch
2018-10-18 16:08:29 +02:00
7b7ae4ff89 Update sdk sdk references in auth.coffee
Change-type:patch
2018-10-18 14:51:03 +02:00
8e83a401eb v7.10.6 2018-10-03 06:58:41 -07:00
2d1891a182 Merge pull request #976 from resin-io/975-fix-preload-examples
Fix formatting of preload examples
2018-10-03 15:56:56 +02:00
8df066df12 Fix formatting of preload examples
Based on https://github.com/resin-io/docs/pull/915 from @drjasonharrison-vp-eio

Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-10-03 15:31:24 +02:00
bd59f95e1a v7.10.5 2018-09-25 07:09:26 -07:00
2b982a1c0c Merge pull request #972 from resin-io/readme-typo
README: Fix typo
2018-09-25 15:08:01 +01:00
ab64fbc904 README: Fix typo
Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2018-09-25 13:35:35 +01:00
733b98f072 v7.10.4 2018-09-24 10:08:55 -07:00
7c538a3658 Merge pull request #967 from resin-io/966-register-print-uuid
device: When registering, print the uuid
2018-09-24 19:07:15 +02:00
8298ba5765 device: When registering, print the uuid
This restores the behavior from before #911,
which is useful from some users.

Closes #966

Change-type: patch
Signed-off-by: Pablo Carranza Velez <pablocarranza@gmail.com>
2018-09-24 15:18:40 +02:00
33a23773d8 v7.10.3 2018-09-19 09:17:52 -07:00
21a3b82845 Merge pull request #971 from resin-io/add-emulated-to-build-docs
Include --emulated in the example resin build parameters
2018-09-19 18:16:28 +02:00
8688eb5da0 Include --emulated in the example resin build parameters
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-09-19 15:34:29 +02:00
5b0ea9673f v7.10.2 2018-09-18 09:17:50 -07:00
44fd8adeba Merge pull request #970 from resin-io/969-resin-semver
dependencies: Update resin-semver version to support Balena OS
2018-09-18 17:15:09 +01:00
a5e03d55c3 dependencies: Update resin-semver version to support Balena OS
Connects to #969

Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2018-09-18 14:23:10 +01:00
80629322ea v7.10.1 2018-09-11 05:29:41 -07:00
946efbcb7f Merge pull request #965 from resin-io/964-drop-npm-deploy
Stop Travis deploying to npm (now handled by concourse)
2018-09-11 14:28:11 +02:00
be8a314d2b Stop Travis deploying to npm (now handled by concourse)
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-09-11 13:58:06 +02:00
0a7203cafe v7.10.0 2018-09-11 04:21:19 -07:00
786fed0151 Merge pull request #963 from resin-io/update-resin-cli-form
Update resin-cli-form to 2.x
2018-09-11 12:17:22 +01:00
9cd8228a20 Update resin-cli-form to 2.x
Change-type: minor
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-09-10 18:31:51 +01:00
652b5f22dd v7.9.4 2018-09-10 06:34:48 -07:00
eed3c06789 Merge pull request #911 from resin-io/fix_pre_provision
Device api keys are no longer used in the registration process
2018-09-10 15:33:37 +02:00
3b283d4a98 Device api keys are no longer used in the registration process
Change-type: patch
Signed-off-by: Theodor Gherzan <theodor@resin.io>
2018-09-10 12:30:51 +01:00
bc6b5ba7b3 Auto-merge for PR #952 via VersionBot
Fix configuration hangs with some images using a larger threadpool
2018-08-20 15:37:22 +00:00
74789ae88f v7.9.3 2018-08-20 15:29:02 +00:00
295d6dee74 Fix configuration hangs with some images by expanding the threadpool
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-20 17:06:26 +02:00
5010a1e312 Auto-merge for PR #946 via VersionBot
Add warning about re-enabling automatic updates
2018-08-15 21:39:23 +00:00
3c2f7ea622 v7.9.2 2018-08-15 21:31:24 +00:00
94f02f0ad8 Add warning about re-enabling automatic updates
Change-type: patch
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-08-15 14:20:11 -07:00
375f84b24e Auto-merge for PR #942 via VersionBot
Fix errors in `getRequestStream` not being propogated
2018-08-15 18:08:59 +00:00
06c649dfd0 v7.9.1 2018-08-15 17:59:46 +00:00
71eca70a22 Fix errors in getRequestStream not being propogated
Change-type: patch
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-08-14 18:21:10 -07:00
53c7bc622c Auto-merge for PR #903 via VersionBot
Support emulated and nocache options for remote builds
2018-08-09 14:52:03 +00:00
975ae45e49 v7.9.0 2018-08-09 14:42:30 +00:00
e7c68c1a5c Support emulated and nocache options for remote builds
Change-type: minor
Closes: #901
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-08-09 14:36:34 +01:00
5beeb78220 Auto-merge for PR #939 via VersionBot
Fix bug where the sudo helper failed in os initialize
2018-08-09 10:37:53 +00:00
c90ba7aa0f v7.8.6 2018-08-09 10:29:50 +00:00
802ccc1b9a Fix bug where the sudo helper failed in os initialize
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 12:11:26 +02:00
b6ef251625 Auto-merge for PR #934 via VersionBot
Add an env vars example config to the local push docs
2018-08-09 10:09:37 +00:00
fd707d6a07 v7.8.5 2018-08-09 10:01:55 +00:00
392cd8569f Make build trigger hash examples clearer
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:47:21 +02:00
e32eda26d9 Update .resin-sync.yml docs for local push and include example env vars
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:21:45 +02:00
d8aaccf80c Update typed-error to fix some TS complaints
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:21:45 +02:00
d5fd5f5f2d Auto-merge for PR #936 via VersionBot
Update klaw now that the fork changes has been finished & released
2018-08-02 10:37:40 +00:00
2cb69c12f1 v7.8.4 2018-08-02 10:29:33 +00:00
7c75346a1a Update klaw
The changes from our fork have now been completed and released

Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-01 16:43:26 +02:00
148d15b6d9 Auto-merge for PR #931 via VersionBot
Follow links found during builds
2018-07-25 14:07:19 +00:00
a46a79df59 v7.8.3 2018-07-25 13:58:28 +00:00
e350f9b335 Follow links found during builds
Change-Type: patch
2018-07-25 12:38:17 +02:00
bd00773f1b Auto-merge for PR #929 via VersionBot
Update reconfix to fix volume signature errors in local configure
2018-07-25 10:23:37 +00:00
ef3c7f0fd6 v7.8.2 2018-07-25 10:13:48 +00:00
f4f44f978e Update reconfix to fix volume signature errors in local configure
Change-Type: patch
2018-07-24 20:57:40 +02:00
442416efc3 Auto-merge for PR #930 via VersionBot
Be explicit about how much initial history log tailing includes
2018-07-20 18:07:00 +00:00
ef33ffedcf v7.8.1 2018-07-20 17:38:09 +00:00
430d4aeaa7 Be explicit about how much initial history log tailing includes
Change-Type: patch
2018-07-20 16:32:31 +02:00
171632f83f Auto-merge for PR #895 via VersionBot
Add join/leave commands to promote and move devices between platforms
2018-07-20 12:36:20 +00:00
1fa7141b58 v7.8.0 2018-07-20 10:40:22 +00:00
916cc36430 Lazily import resin-image-fs
If for whatever reason resin-image-fs is not importable — eg. if it’s built for another arch — any command that imports `helpers.ts` will just quit without any error/traceback.
2018-07-20 13:04:26 +03:00
27b877dd33 Forward root CA to device config if one is present 2018-07-19 22:34:31 +03:00
5cbe1c410f Add join/leave commands to promote and move devices between platforms
Both commands work with local devices by remotely invoking the `os-config` executable via SSH. This requires an as of yet unreleased resinOS (that will most likely be v2.14) and the commands ascertain compatibility merely by looking for the `os-config` executable in the device, and bail out if it’s not present.

`join` and `leave` accept a couple of optional arguments and implement a wizard-style interface if these are not given. They allow to interactively select the device and the application to promote to. If the user has no apps, `join` will offer the user to create one. `join` will also offer the user to login or create an account if they’re not logged in already without exiting the wizard.

`resin-sync` (that's used internally to discover local devices) requires admin privileges. If no device has been specified as an argument, the commands will launch the device scanning process in a privileged subprocess via two new internal commands: `internal sudo` and `internal scanDevices`. This avoids having the user to invoke the commands with sudo and only request escalation if truly needed. This commit also removes the dependency to “president”, implementing “sudo” functionality within the CLI.

Change-Type: minor
2018-07-19 22:18:02 +03:00
7846af390e Improve selectFromList function signature to be much more reusable 2018-07-19 21:53:43 +03:00
79d9ebc805 Auto-merge for PR #923 via VersionBot
Update OS & config actions to the MC SDK, and add a --version option
2018-07-17 15:43:30 +00:00
25b853c535 v7.7.4 2018-07-17 15:35:26 +00:00
a93141343f Update TypeScript to 2.8.1
Change-Type: patch
2018-07-17 16:48:14 +02:00
9a467c5ecd Pin all type modules 2018-07-17 15:59:31 +02:00
70be2ae596 Tweaks to config options handling after review 2018-07-17 15:38:38 +02:00
36eb0a108e Post-review tweaks to OS actions 2018-07-13 19:34:59 +02:00
0bf6fb1739 Add --version options to os configure & config generate
This is used to ensure the correct type of API key is used in all
configuration.

Change-Type: patch
2018-07-13 19:34:59 +02:00
892adf4c47 Update OS & config actions to the latest SDK
Fixes #915
Change-Type: patch
2018-07-13 19:34:59 +02:00
5d1d004b72 Auto-merge for PR #927 via VersionBot
Update the CLI deploy key since npm invalidated the old one
2018-07-13 17:21:18 +00:00
dea5a60b2d v7.7.3 2018-07-13 17:05:32 +00:00
652a1b7650 Update the deploy key since npm invalidated the old one
Change-Type: patch
2018-07-13 16:39:56 +02:00
350843af1e Auto-merge for PR #926 via VersionBot
Pin ext2fs to 1.0.7 to avoid temporary deployment issues
2018-07-13 11:40:25 +00:00
e04c4a8ee3 v7.7.2 2018-07-13 11:33:13 +00:00
9d0c3f7535 Pin ext2fs to 1.0.7 to avoid temporary deployment issues
Change-Type: patch
2018-07-13 13:20:53 +02:00
9561d4da2e Auto-merge for PR #925 via VersionBot
Update logs to use new v10 MC SDK
2018-07-12 13:59:28 +00:00
8296dcf946 v7.7.1 2018-07-12 13:52:10 +00:00
e62e8b88c2 Simplify logs promises after review 2018-07-12 15:38:27 +02:00
4388a248b9 Make sure we don't duplicate historical logs when streaming 2018-07-12 15:23:33 +02:00
f9cf0aaf23 Remove a couple of artifacts of the pubnub logs implementation 2018-07-12 15:10:16 +02:00
dc9ee09838 Update CLI to SDK v10 (include new API logs)
Change-Type: patch
2018-07-12 01:03:16 +02:00
7cb27283c5 Update logs action to use the MC SDK 2018-07-12 01:03:16 +02:00
10a9840b34 Auto-merge for PR #921 via VersionBot
Add --generate-device-api-key parameter to config generate
2018-07-11 04:28:25 +00:00
ce3e04bfe8 v7.7.0 2018-07-11 04:21:42 +00:00
52f93f8f12 Add --generate-device-api-key parameter to config generate
Change-Type: minor
2018-07-10 19:57:56 +02:00
af9e1a122d Auto-merge for PR #910 via VersionBot
Make local commands more resilient to unnamed containers
2018-06-28 16:26:11 +00:00
9017b8ec11 v7.6.2 2018-06-28 12:55:34 +00:00
bf4f687a2a Make local commands more resilient to unnamed containers
Change-Type: patch
2018-06-28 12:34:31 +02:00
9d4e6eb825 Auto-merge for PR #907 via VersionBot
Make sure 'resin push' is included in the CLI docs
2018-06-26 17:22:44 +00:00
fba4afb7d2 v7.6.1 2018-06-26 17:15:20 +00:00
8c74f784f7 Make sure 'resin push' is included in the docs
Fixes #906
Change-Type: patch
2018-06-26 19:00:20 +02:00
69ca1ffa59 Auto-merge for PR #896 via VersionBot
Support pinned release preloading
2018-06-20 17:00:12 +00:00
7d1b00877e v7.6.0 2018-06-20 16:50:01 +00:00
1a48fed1f7 Support pinned release preloading
Change-type: minor
Closes: #886
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-06-13 12:29:30 +01:00
bc86359e63 Auto-merge for PR #893 via VersionBot
Document Python native build dependency
2018-06-12 18:33:47 +00:00
f6822f1502 v7.5.2 2018-06-12 18:26:12 +00:00
398c34d842 Includes new prettier changes, and pin prettier to stop more appearing 2018-06-12 17:43:15 +02:00
72a893be95 Document Pyhton native build dependency
Change-Type: patch
2018-06-12 17:11:45 +02:00
7b23b0e103 Auto-merge for PR #887 via VersionBot
Add a multicontainer caveat to the env var commands
2018-06-01 11:10:33 +00:00
0ce7878042 v7.5.1 2018-06-01 10:49:15 +00:00
da8483e6a6 Add a multicontainer caveat to the env var commands
Change-Type: patch
2018-06-01 12:37:29 +02:00
16f70fd946 Auto-merge for PR #883 via VersionBot
Update resin-compose-parse dependency version
2018-05-31 16:16:47 +00:00
78aa898b37 v7.5.0 2018-05-31 16:07:38 +00:00
b7f94a222d Update resin-compose-parse dependency version to 1.10.2
Change-type: minor
2018-05-30 11:57:04 -03:00
7bea2c26b8 Auto-merge for PR #879 via VersionBot
Update SDK for device commands, so we show new device dashboard URLs
2018-05-24 14:13:01 +00:00
7c178b8095 v7.4.1 2018-05-24 14:03:02 +00:00
865f085094 Make sure we still show the device commit, despite API changes 2018-05-24 14:43:45 +01:00
28fe69fe94 Update to latest SDK in lots of easy device commands 2018-05-18 20:05:24 +02:00
232cf8d426 Update SDK in resin device(s) to ensure the dashboard URL is correct
Fixes #768

Change-Type: patch
2018-05-18 20:00:40 +02:00
22e74983b0 Auto-merge for PR #868 via VersionBot
Add push command which starts a build on remote resin servers
2018-05-10 12:44:43 +00:00
c88dd2257a v7.4.0 2018-05-10 12:28:32 +00:00
439d8d396f Add push command which starts a build on remote resin servers
Change-type: minor
Connects-to: #843
2018-05-10 11:43:45 +01:00
6d8086c09b Auto-merge for PR #874 via VersionBot
Handle failed requires & missing bindings
2018-05-03 17:56:53 +00:00
e85f252f29 v7.3.8 2018-05-03 17:49:06 +00:00
4b818ad51c Style improvements after review 2018-05-03 18:59:28 +02:00
c2518448a3 Catch require errors and provide helpful instructions
Change-Type: patch
2018-05-03 16:01:40 +02:00
e7a8deed05 Inline the entire resin-cli-errors module
It's awkward that error handling requires you to go to a different
package, it makes things more complicated, and there's nowhere else that
really should be reusing this logic. Let's inline it, so we can
deprecate the module entirely.

Change-Type: patch
2018-05-03 15:15:03 +02:00
0ac599d20c Auto-merge for PR #871 via VersionBot
Pin node types to v9.0.0 to avoid build errors with transient dependencies
2018-04-30 15:25:23 +00:00
7d7074e6b7 v7.3.7 2018-04-30 15:18:31 +00:00
35ca34d07d Pin node types to v9.0.0 to avoid build errors with transient dependencies
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-04-30 16:09:12 +01:00
90d7316b4c Auto-merge for PR #870 via VersionBot
Update resin-image-fs to stop non-config commands failing in node 10
2018-04-30 09:54:09 +00:00
904b4e96d9 v7.3.6 2018-04-30 09:34:40 +00:00
2c46c59a79 Update resin-image-fs to stop non-config commands failing in node 10
This doesn't fix actual usage of image fs, just makes it possible to
stop commands that don't use it from failing entirely.

Connects-To: #869
Change-Type: patch
2018-04-30 11:14:39 +02:00
297ff86895 Auto-merge for PR #858 via VersionBot
Don't show Docker container status from devices, as it can be wrong
2018-04-18 19:08:16 +00:00
a154401424 v7.3.5 2018-04-18 19:00:21 +00:00
ad2713fc00 Don't show Docker container status from devices, as it can be wrong
The status includes a description of how long the device has been in
this state (Up 6 weeks), which is frequently wrong as when the device
first starts up its clock isn't up to date. It's confusing and messy,
best to just remove it entirely.

Fixes #828
Change-Type: patch
2018-04-18 20:16:44 +02:00
6388cfaf40 Auto-merge for PR #865 via VersionBot
Include resin compose schemas in the standalone build
2018-04-18 16:41:50 +00:00
167f38e342 v7.3.4 2018-04-18 16:27:52 +00:00
919b3c3435 Include resin compose schemas in the standalone build
Fixes #844
Change-Type: patch
2018-04-18 13:34:35 +02:00
2e1ab22173 Auto-merge for PR #861 via VersionBot
727 sentry improvements
2018-04-17 14:46:13 +00:00
0a23563d7e v7.3.3 2018-04-17 14:01:51 +00:00
37e4ec6364 Rename expectedError to exitWithExpectedError 2018-04-17 15:18:06 +02:00
6a8b947c2e Don't report lots of user input errors
Change-Type: patch
2018-04-17 15:18:06 +02:00
a16ac37625 Include Sentry breadcrumbs for context in error reports
Change-Type: patch
2018-04-17 15:18:06 +02:00
cf4c7826b2 Update to Sentry 2.x
Change-Type: patch
2018-04-17 15:18:06 +02:00
a0a26f0a1e Auto-merge for PR #862 via VersionBot
Update Dockerode to fix local push issue in standalone builds
2018-04-16 16:21:22 +00:00
a921139a12 v7.3.2 2018-04-16 15:21:33 +00:00
36da7b66c8 Update Dockerode to fix local push issue in standalone builds
Connects-To: #824
Change-Type: patch
2018-04-16 16:43:17 +02:00
3aa87544eb Auto-merge for PR #849 via VersionBot
Update resin-compose-parse to v1.8.1 to fix a problem parsing ports
2018-04-13 19:43:58 +00:00
6121fa505e v7.3.1 2018-04-13 19:38:01 +00:00
a5ba5befd1 Update resin-compose-parse to v1.8.1 to fix a problem parsing ports
Connects-to: https://github.com/resin-io/resin-supervisor/issues/618

Change-Type: patch
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
2018-04-13 11:17:18 -07:00
b7214a306c Auto-merge for PR #854 via VersionBot
Add 'api-key generate' command
2018-04-12 10:24:39 +00:00
d7616e941a v7.3.0 2018-04-12 10:06:09 +00:00
834a2f1e4d Warn user that api keys will not be shown again in future 2018-04-11 19:31:03 +02:00
0e5f2fe748 Remove now-unused stream-to-promise dependency 2018-04-11 19:30:29 +02:00
e0bcb5e0b9 Always call done() for api key generation, not just if we're successful 2018-04-11 19:27:58 +02:00
59d4890eae Add 'api-key generate' command
Change-Type: minor
2018-04-10 19:21:37 +02:00
51da5360da Auto-merge for PR #852 via VersionBot
Explicitly depend on tar-stream
2018-04-10 14:14:38 +00:00
2655aef28b v7.2.4 2018-04-10 13:49:09 +00:00
45d3a7a124 Explicitly depend on tar-stream
Change-Type: patch
2018-04-10 13:10:25 +02:00
662e4f8940 Merge pull request #853 from resin-io/document-version-rec
Correct documented node version requirement to 6+
2018-04-10 13:10:02 +02:00
c06993cb8e Correct documented node version requirement to 6+
Change-Type: patch
2018-04-09 16:55:36 +02:00
a650f30ce8 Auto-merge for PR #847 via VersionBot
Add a fast build script to package.json
2018-04-06 17:11:31 +00:00
0a924b2dcb v7.2.3 2018-04-06 16:27:32 +00:00
89f62683ce Add a fast build script to package.json
This doesn't run a linter or any documentation generation, aiding in
quick development time.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-04-06 15:40:08 +01:00
143d88f3df Auto-merge for PR #846 via VersionBot
Throw a clear error when logging in with an invalid token
2018-04-04 19:34:56 +00:00
d166a65422 v7.2.2 2018-04-04 18:56:26 +00:00
dd268993b3 Throw a clear error when logging in with an invalid token
Change-Type: patch
2018-04-04 15:43:34 +02:00
13a35b288f Auto-merge for PR #839 via VersionBot
Update docker-qemu-transpose to avoid the broken 0.4.1 release
2018-03-29 14:47:30 +00:00
81e653d31b v7.2.1 2018-03-29 13:52:06 +00:00
875ec8b8bd Update docker-qemu-transpose to avoid the broken 0.4.1 release
Change-Type: patch
2018-03-29 15:28:56 +02:00
989df9b857 Auto-merge for PR #835 via VersionBot
Initial support for api keys in the CLI
2018-03-29 10:15:31 +00:00
0829d3c176 v7.2.0 2018-03-29 10:09:08 +00:00
ce64889b04 Clarify isTokenValid logic 2018-03-29 11:11:25 +02:00
d3a0bfc5f6 Fix auth utils tests to work with new SDK 2018-03-29 11:11:25 +02:00
e965c603d2 Use spec test reporter, so we can debug with output 2018-03-29 11:11:25 +02:00
0e2fb8c96c Promisify auth utils tests 2018-03-29 11:11:25 +02:00
2db1d84d3c Do not require a login for builds
Fixes: #578
Change-Type: patch
2018-03-29 11:11:25 +02:00
12a1916007 Allow (experimental!) login with API keys
Change-Type: minor
2018-03-29 11:11:25 +02:00
b4526e9895 Auto-merge for PR #838 via VersionBot
Fix build emulation for multi-stage builds
2018-03-29 09:03:40 +00:00
a2d867c860 v7.1.6 2018-03-29 08:56:07 +00:00
05b1c37379 Fix build emulation for multi-stage builds
Fixes #814
Change-Type: patch
2018-03-29 10:18:31 +02:00
906cfe9268 Auto-merge for PR #834 via VersionBot
Fix crash when an app is not specified for build command
2018-03-28 12:01:10 +00:00
3c8054faa7 v7.1.5 2018-03-27 17:51:36 +00:00
c6c9046826 Fix crash when an app is not specified for build command
This is a regression introduced in #818

Change-Type: patch
2018-03-27 19:12:31 +03:00
2bbbbf6fdd Auto-merge for PR #832 via VersionBot
Upgrade resin-sync to pull in the fix for #824
2018-03-26 16:31:46 +00:00
9cce4001af v7.1.4 2018-03-26 16:09:22 +00:00
2e944cf2f4 Upgrade resin-sync to pull in the fix for #824
Change-Type: patch
2018-03-26 17:39:47 +02:00
2b0143775c Auto-merge for PR #831 via VersionBot
Prefix all pine options with '$' in preload to avoid pine warnings.
2018-03-23 15:57:54 +00:00
49fec7d8f2 v7.1.3 2018-03-23 15:49:16 +00:00
ca1ac2bb83 Prefix all pine options with '$' in preload to avoid pine warnings.
Change-Type: patch
2018-03-23 15:20:18 +00:00
50b1a7e6b0 Auto-merge for PR #830 via VersionBot
Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16
2018-03-23 13:56:14 +00:00
69ce2c0473 v7.1.2 2018-03-23 13:49:24 +00:00
a3b446dbe7 Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16
Change-Type: patch
2018-03-23 13:41:16 +00:00
1032d9927f Auto-merge for PR #827 via VersionBot
Remove explicit anchor links in CLI docs
2018-03-22 18:03:07 +00:00
12e8a50abc v7.1.1 2018-03-22 17:06:07 +00:00
a4142097f8 Merge branch 'master' into doc-headings 2018-03-22 09:17:32 -05:00
b388ccb6f3 Auto-merge for PR #818 via VersionBot
Restore legacy deployment method
2018-03-22 11:43:59 +00:00
e011502b7e v7.1.0 2018-03-22 11:36:41 +00:00
4f167cb836 Address review feedback 2018-03-22 13:26:47 +02:00
9455d438e2 Formatting fixes 2018-03-22 13:26:47 +02:00
a356ecf9b6 Remove unused code 2018-03-22 13:26:47 +02:00
066ac591ac Warn early if deploying a multicontainer project to an incompatible app
Change-Type: patch
2018-03-22 13:26:47 +02:00
62f006b89a Add legacy deploy method back
This mostly reverts the removal of the legacy deploy code that pushed image tars via the builder. It’s needed for users to avoid having to switch between CLI versions in order to push to legacy apps as well.

Note: this pins resin-sdk to 9.0.0-beta14 as I couldn’t get it to install otherwise — npm would always install 9.0.0-beta9 instead.

Change-Type: minor
2018-03-22 13:26:47 +02:00
ee75ff2753 Remove explicit anchor links in CLI docs
Our docs markdown renderer doesn't process explicit anchor tags, as it generates its own. The script that generates the markdown has been updated to not include these tags and to properly build the TOC links.

Change-type: patch
2018-03-20 13:10:07 -05:00
e4c9defb70 Auto-merge for PR #821 via VersionBot
Update resin-preload to 6.1.2
2018-03-20 15:54:28 +00:00
bb102c1918 v7.0.7 2018-03-20 15:44:13 +00:00
24ebe2946c Update resin-preload to 6.1.2
Connects-To: #820

Change-Type: patch
2018-03-20 15:22:59 +00:00
ba82b1fa27 Auto-merge for PR #815 via VersionBot
Build/deploy commands improvements
2018-03-20 10:43:31 +00:00
e3b145e7b7 v7.0.6 2018-03-20 10:33:01 +00:00
242c3731ee Remove redundant import 2018-03-19 20:52:51 +02:00
5f7eee8eac Make sure image name is all lowercase
Change-Type: patch
2018-03-19 20:52:51 +02:00
1833f6ff0a Improve handling of build log output
This makes sure build logs don’t leak escape sequences and new lines and they don’t break the output. Also improved “inline” logs by normalising the stream before passing it to “transpose build stream”.

Fixes: #808
Change-Type: patch
2018-03-19 20:52:51 +02:00
e5fb954645 Auto-merge for PR #801 via VersionBot
add bash completions
2018-03-15 20:03:33 +00:00
13f76dc020 v7.0.5 2018-03-15 18:51:46 +00:00
b409bdcc73 add blurb about bash completion
Add brief information about tab completions for bash and instructions to enable it.
2018-03-15 18:06:04 +01:00
8c3cb3f585 Add bash completions
This contains bash completion functionality for the resin CLI, including completion for sub-commands.

Change-type: patch
2018-03-15 18:05:50 +01:00
76a8b4df50 Auto-merge for PR #813 via VersionBot
Properly generate consistent working anchors for both our md output & resin docs
2018-03-15 12:09:16 +00:00
a03680311d v7.0.4 2018-03-15 12:01:05 +00:00
6ee36cb5c7 Generate consistent working anchors for both our md output & resin docs
Change-Type: patch
2018-03-15 11:40:29 +01:00
5625326c65 Auto-merge for PR #812 via VersionBot
Fix getting window size when there’s no TTY attached
2018-03-15 08:54:19 +00:00
b912419839 v7.0.3 2018-03-15 08:47:28 +00:00
fe01ead023 Fix getting window size when there’s no TTY attached
Change-Type: patch
2018-03-15 10:30:54 +02:00
229c105d0c Auto-merge for PR #807 via VersionBot
Update full CLI docs with recent installation improvements too
2018-03-13 12:00:31 +00:00
b6e044345f v7.0.2 2018-03-13 10:47:55 +00:00
d9906121e1 Update full CLI docs with recent installation improvements too
Change-Type: patch
2018-03-12 22:17:20 +01:00
3e019f7f34 Remove leftover capitanodoc.coffee file (it's now TS) 2018-03-12 20:06:44 +01:00
eb34cb6f27 Auto-merge for PR #805 via VersionBot
Recommend unsafe-perm to fix some install issues and cleanup dependencies after MC
2018-03-12 16:36:28 +00:00
3a3178bcb9 v7.0.1 2018-03-12 15:36:10 +00:00
cdf6580ecc Recommend using unsafe-prem to avoid permission issues on install
Change-Type: patch
2018-03-12 13:36:24 +01:00
c42bc74f1f Remove unnecessary resin-cli-auth dependency
Change-Type: patch
2018-03-12 11:41:58 +01:00
35fd79f577 Remove (duplicated) runtime ts-node dependency 2018-03-12 11:41:14 +01:00
4ef0682e5a Auto-merge for PR #792 via VersionBot
Multicontainer
2018-03-09 22:12:00 +00:00
d0b7047189 v7.0.0 2018-03-09 22:04:51 +00:00
ae3f936b66 Update resin-preload to v6.0.0 2018-03-09 21:53:34 +00:00
1ef492809b Update resin-preload to v6.0.0-beta11 2018-03-09 20:40:13 +00:00
5bf9dd3a9d Update resin-preload to v6.0.0-beta10 2018-03-09 17:50:20 +00:00
b18a66f66b Update resin-preload to v6.0.0-beta9 2018-03-09 17:02:44 +00:00
1dadfdc699 Fix some formatting to make prettier+resin-lint happy 2018-03-07 16:16:07 +01:00
14a3f51b73 Add docker-compose-aware builds and deployments
Legacy behaviour is mostly retained. The most notable change in behaviour is that invoking `resin deploy` without options is now allowed (see help string how it behaves).

In this commit there are also the following notable changes:

- Deploy/Build are promoted to primary commands
- Extracts QEMU-related code to a new file
- Adds a utility file to retrieve the CLI version and its parts
- Adds a helper that can be used to manipulate display on capable clients
- Declares several new dependencies. Most are already indirectly installed via some dependency

Change-Type: minor
2018-03-07 14:48:05 +00:00
96116aeaec Fix invoking undefined method
Have no idea how this used to work.
2018-03-07 14:47:16 +00:00
7fd31b6a64 Update YAML parser
New version is 3.10.0
2018-03-07 14:47:16 +00:00
299bc0db13 Update docker-toolbelt
New version is 3.1.0.

The updated version is not backwards compatible as it removes all *Async methods that are in wide use in the CLI. The workaround for now is to manually promisify the client and replace all `new Docker()` calls with a shared function that returns a promisified client.
2018-03-07 14:47:15 +00:00
4b9ccae442 Update bundle-resolve and docker-build to latest
This brings in maintainance improvements.

New versions are:

- resin-bundle-resolve: 0.5.1
- resin-docker-build: 0.6.2
2018-03-07 14:46:35 +00:00
079ce552e3 *BREAKING*: Remove support for plugins entirely
There are very few plugins in real-world use, we're not actively working
on this at all, and the current approach won't work once we move to
standalone node-less binary installation anyway.

Change-Type: major
2018-03-07 14:46:35 +00:00
163684e3a9 Update dashboard login to use the multicontainer SDK
Change-Type: patch
2018-03-07 14:46:35 +00:00
f698f561c9 Multicontainer preload: Update resin-preload to 6.0.0-beta4
Change-Type: minor
2018-03-07 14:46:35 +00:00
cb207f18a5 Update the keys action to use the multicontainer SDK
Change-Type: patch
2018-03-07 14:46:34 +00:00
76a5cdc977 Require multicontainer SDK
* require('resin-sdk') => multicontainer SDK
 * require('resin-sdk-preconfigured') => 6.15.0 SDK
 * all 'resin-sdk' requires replaced with 'resin-sdk-preconfigured'
 * resin-sdk-preconfigured TS typings are copy pasted from the current resin-sdk master

The idea is to progressively replace all 'resin-sdk-preconfigured'
requires with 'resin-sdk' (multicontainer sdk) and eventually remove
resin-sdk-preconfigured from package.json.

Change-Type: patch
2018-03-07 14:46:31 +00:00
a82af1d2d1 Auto-merge for PR #802 via VersionBot
Fix CLI prettier configuration to avoid linting errors
2018-03-07 14:46:08 +00:00
ac7d51ad80 v6.13.5 2018-03-07 14:38:49 +00:00
797a739c92 Fix prettier configuration to avoid linting errors
Change-Type: patch
2018-03-05 16:02:09 +01:00
666b59b463 Auto-merge for PR #796 via VersionBot
Fix issue where emulated builds broke Docker `ENV` commands
2018-02-22 18:30:14 +00:00
a83d9a070c v6.13.4 2018-02-22 18:23:29 +00:00
7637377471 Fix issue where emulated builds broke Docker ENV commands
Connects-to: #795
Change-type: patch
2018-02-22 18:12:17 +00:00
6515f88d92 Auto-merge for PR #793 via VersionBot
Tweak TS & add missing deps that may cause build failures in some envs
2018-02-20 22:07:21 +00:00
92534b9c82 v6.13.3 2018-02-20 21:30:39 +00:00
c12360daa8 Tweak TS & add missing deps that may cause build failures in some envs
Connects-To: #765
Change-Type: patch
2018-02-20 20:26:18 +01:00
3d28118f3e Auto-merge for PR #794 via VersionBot
Ensure login does not wait for the browser process to close
2018-02-20 19:00:32 +00:00
04adfde064 v6.13.2 2018-02-20 17:12:45 +00:00
d8aabfd448 Ensure login does not wait for the browser process to close
Unclear why, but for some reason this only actually blocked on the
browser on OSX.

Connects-To: #791
Change-Type: patch
2018-02-16 17:28:19 +01:00
cf95870d9d Auto-merge for PR #786 via VersionBot
Assorted tiny fixes
2018-02-07 12:07:01 +00:00
55f8876bcc v6.13.1 2018-02-07 11:59:58 +00:00
9fb66186f0 Move to the correct coffeescript (no hyphen) dependency
Change-Type: patch
2018-02-07 11:20:49 +01:00
da8fe99ca4 Add typings for 'ent'
Change-Type: patch
2018-02-07 11:20:08 +01:00
20374fde36 Auto-merge for PR #777 via VersionBot
Add support for Balena in local ssh
2018-02-06 11:40:40 +00:00
5131f722a7 v6.13.0 2018-02-06 11:34:29 +00:00
1ef0a1028f Add support for Balena in local ssh
Change-Type: minor
2018-02-06 12:05:28 +01:00
0fd1f04eda Auto-merge for PR #781 via VersionBot
Switch back to upstream global-tunnel-ng
2018-02-05 19:07:08 +00:00
5c0ba5d06c v6.12.9 2018-02-05 19:00:36 +00:00
d9532b6fa0 Switch back to upstream global-tunnel-ng
Connects-To: #780

Change-Type: patch
2018-02-05 15:55:26 +00:00
b96065514f Auto-merge for PR #774 via VersionBot
Fix uuid params being parsed a numbers
2018-02-03 15:58:03 +00:00
0e9b8e4140 v6.12.8 2018-02-03 15:50:09 +00:00
d1c773360f Fix uuid params being parsed a numbers
Connects-To: #489
Change-Type: patch
2018-02-01 17:48:01 +02:00
74538bba8d Auto-merge for PR #767 via VersionBot
Add 'or mounted resinOS image'
2018-01-30 18:10:12 +00:00
64c95e3811 v6.12.7 2018-01-30 17:29:59 +00:00
33fd70291a Add 'or mounted resinOS image'
Connects-To: #764

Change-Type: patch
2018-01-30 17:30:18 +01:00
0cb4bc951a Auto-merge for PR #759 via VersionBot
Don't use the deprecated 'os configure' format in internal calls
2018-01-29 14:56:00 +00:00
3761ab9610 v6.12.6 2018-01-29 14:44:44 +00:00
8c29bba108 Don't use the deprecated 'os configure' format in internal calls
Change-Type: patch
2018-01-16 17:54:46 +01:00
4e41261237 Auto-merge for PR #754 via VersionBot
Fix breakage in deploy command
2018-01-11 11:32:17 +00:00
77529ef3b1 v6.12.5 2018-01-11 10:16:56 +00:00
0ba96adbbc Fix breakage in deploy command from recent TS conversion
Change-Type: patch
2018-01-11 10:33:08 +01:00
7df277c0bc Auto-merge for PR #753 via VersionBot
Add prettier
2018-01-10 10:18:18 +00:00
c94f7b10bd v6.12.4 2018-01-10 09:48:26 +00:00
83a76f7d6f Start using Prettier
Change-Type: patch
2018-01-10 09:23:00 +01:00
6c988241eb Move capitanodoc into the automation folder 2018-01-10 08:41:19 +01:00
29145dfc2d Auto-merge for PR #743 via VersionBot
Start seriously converting the CLI to TypeScript
2018-01-09 22:37:41 +00:00
4b74e8ec70 v6.12.3 2018-01-09 21:00:55 +00:00
612012aff8 Lots of small TypeScript tweaks & clarifications from review 2018-01-09 17:14:49 +01:00
6ab60d0ccd Avoid awkward multiline strings in doc generation code 2018-01-09 17:14:49 +01:00
6daed83d88 Lint TypeScript and CoffeeScript with resin-lint
Change-Type: patch
2018-01-09 17:14:49 +01:00
f25442c036 Move documentation generation to TypeScript
Change-Type: patch
2018-01-09 17:14:49 +01:00
ffffd447f2 Convert most of utils to TypeScript
Change-Type: patch
2018-01-09 17:14:48 +01:00
4b511c47f0 Start on some easy TS conversion 2018-01-09 17:14:48 +01:00
158d471a98 Auto-merge for PR #751 via VersionBot
Convert windows paths to posix when passing to tar
2018-01-09 15:33:28 +00:00
107a90395c v6.12.2 2018-01-09 14:03:45 +00:00
ce5fd53822 convert windows paths to posix when passing to tar
Due to https://github.com/mafintosh/tar-stream/issues/3, the tar module
needs posix style paths but system-specific paths are being supplied

Change-Type: patch
2018-01-08 22:50:11 +00:00
810ca78215 Auto-merge for PR #744 via VersionBot
Fix deprecation warning for os configure, when passing a bare UUID
2018-01-02 10:58:30 +00:00
eb945b3315 v6.12.1 2018-01-02 10:49:06 +00:00
34f24fe331 Fix deprecation warning for os configure, when passing a bare UUID
Change-Type: patch
2017-12-22 16:07:19 +01:00
743392017d Auto-merge for PR #737 via VersionBot
Add ssh option for direct host OS access
2017-12-19 08:45:56 +00:00
15b877f005 v6.12.0 2017-12-19 08:37:19 +00:00
0653769156 fixed example 2017-12-18 17:03:16 +01:00
3ed319872a refactored 2017-12-18 17:03:16 +01:00
ee124671d8 mention Resin OS version requirement 2017-12-18 17:03:16 +01:00
1b4dabd37c Add ssh option for direct host OS access
Use the `--host` (short `-H`) option in the ssh command to access
the host OS of the device.
Direct host OS is enabled for devices with Resin OS >= 2.7.5.

Change-Type: minor
Connects-To: #736
Signed-off-by: Andreas Fitzek <andreas@resin.io>
2017-12-18 17:03:16 +01:00
fdd253f042 Auto-merge for PR #729 via VersionBot
Standalone binary for Resin-CLI
2017-12-18 15:06:13 +00:00
1a15fdd2f0 v6.11.0 2017-12-18 14:48:09 +00:00
2c66280b3f Build standalone zips into a separate folder 2017-12-18 15:03:17 +01:00
778c39d947 Ensure MDNS service definitions are included in standalone binaries 2017-12-18 14:55:07 +01:00
fa15addfb2 Add standalone install instructions to the readme 2017-12-18 14:55:07 +01:00
afbb9474b7 Use proper strict settings for automation TS 2017-12-18 14:55:07 +01:00
0acb4f8cb1 Fix docs generation when building on windows
Change-Type: patch
2017-12-18 14:55:07 +01:00
08de0938a0 Autodeploy built standalone binaries for all platforms to github
Change-Type: minor
2017-12-18 14:55:07 +01:00
2c9b80c177 Add manual script to deploy built CLI binaries to GitHub 2017-12-18 14:55:07 +01:00
e8c19df8c9 Set up a script to automate builds, and support native extensions 2017-12-18 14:55:07 +01:00
7681003512 Package the CLI into a standalone runnable binary
This has no native modules yet, which means it works on Linux,
but ignoring any ext4 image data. Drivelist will fail for
some windows operations, but most other things should work.

This is only building a folder with a runnable binary, this needs
packaging before it can be distributable.

Change-Type: minor
2017-12-18 14:55:07 +01:00
dba8db19cb Move from open to opn
Change-Type: patch
2017-12-18 14:55:07 +01:00
d199cdf088 Auto-merge for PR #730 via VersionBot
Ensure logout works even with invalid credentials, or if not logged in
2017-12-15 17:52:38 +00:00
f2840c5ca4 v6.10.3 2017-12-15 17:48:25 +00:00
1c7a0ba4e1 Ensure logout works even with invalid credentials, or if not logged in
Before this point, if you had an invalid token, an expired token, or a
token for a different site, you couldn't log out to clear it properly.

Not a big deal, but awkward and messy, and easily fixed.

Change-Type: patch
2017-12-14 15:34:36 +01:00
41ff793372 Auto-merge for PR #721 via VersionBot
Inline the entire resin-cli-auth module
2017-11-27 17:28:43 +00:00
e4432d1a90 v6.10.2 2017-11-27 17:25:41 +00:00
bd6cb04a2b Replace underscore.string usage with lodash 2017-11-27 12:03:04 +02:00
001c8f9601 Inline the entire resin-cli-auth module
This is part of a general push to demodularize any code that isn't
realistically reusable outside resin-cli, to make the codebase easier to
manage and understand. Once this is done, we'll deprecate the original
module itself.

Change-Type: patch
2017-11-27 12:02:57 +02:00
f106b95be2 Auto-merge for PR #720 via VersionBot
Start adding TypeScript to the CLI and stop committing build output
2017-11-27 09:35:42 +00:00
6fbe602b77 v6.10.1 2017-11-27 09:32:58 +00:00
dc549a665b Update to latest latest typescript 2017-11-27 11:25:20 +02:00
46ca62db3e Include lib/ in published package too, to enable sourcemaps 2017-11-23 19:29:07 +02:00
eb68bb1a1a Set up TypeScript compilation, and make a small start on converting the CLI
Change-Type: patch
2017-11-23 19:28:15 +02:00
93d1e3a4a1 Rename gulp build step to coffee 2017-11-23 19:24:03 +02:00
ff2ee59dae Don't commit raw JS build output
This lets us avoid lots of potential conflicts, issues, and confusion,
and keeps reviews simpler and cleaner.

Change-Type: patch
2017-11-23 19:23:59 +02:00
6217b4a6b5 Auto-merge for PR #718 via VersionBot
Allow configuring images for both device and just applications
2017-11-17 10:46:33 +00:00
67fcc6791c v6.10.0 2017-11-17 10:43:54 +00:00
49d78c56fa Print a deprecation message if you use the old os configure format 2017-11-16 19:51:34 +01:00
e38a0c0047 Allow os configure to configure for an app, not just a specific device
This moves to --app and --uuid options, and deprecates the previous
format, but doesn't immediately remove it so this is not a breaking
change.

Connects-To: #691
Change-Type: minor
2017-11-16 19:51:34 +01:00
eef0d9cdbe Print help even for expected errors
Change-Type: patch
2017-11-16 19:51:17 +01:00
08c40195e5 Make sure everything uses the same shared deviceApiKey option 2017-11-16 19:09:20 +01:00
7e306fbce8 Auto-merge for PR #714 via VersionBot
Allow generating device configurating non-interactively
2017-11-16 14:13:08 +00:00
656656bec1 v6.9.0 2017-11-16 14:09:40 +00:00
87f46cb957 Allow non-interactice config generate for simple network settings
Fixes #695
Fixes #410
Change-Type: minor
2017-11-16 15:04:58 +01:00
f7075d7db9 Fix issue where network settings were not used by config generate
Change-Type: patch
2017-11-16 15:03:49 +01:00
2a2d621d6a Auto-merge for PR #717 via VersionBot
Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates
2017-11-16 13:59:18 +00:00
2db6cdd063 v6.8.3 2017-11-16 13:55:44 +00:00
1fafe64579 Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates
This would be a major change if the command was ever successful, but it
appears it hasn't ever worked for any available published version of
ResinOS, so it's not possible that there are users relying on it.

Change-Type: patch
2017-11-15 14:46:22 +01:00
6562eb544c Auto-merge for PR #713 via VersionBot
Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3
2017-11-14 10:30:30 +00:00
3763bf0712 v6.8.2 2017-11-14 10:27:31 +00:00
890a02e2c8 Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3
Change-Type: patch
2017-11-11 12:29:49 +01:00
427664c729 Auto-merge for PR #711 via VersionBot
Avoid AmbiguousApplication errors in device register when an id is used
2017-11-09 15:20:12 +00:00
727a245715 v6.8.1 2017-11-09 15:16:17 +00:00
a2635f47ee Avoid AmbiguousApplication errors in device register when an id is used
Change-Type: patch
Connects-To: #665
2017-11-09 16:03:06 +01:00
d316f67367 Auto-merge for PR #706 via VersionBot
Update resin-preload to 5.0.0 to handle jetson-tx2
2017-10-27 14:39:30 +00:00
ebd1d7e370 v6.8.0 2017-10-27 14:32:34 +00:00
eef192ff68 Allow preloading jetson-tx2 images, improve flasher images detection and remove the --dont-detect-flasher-type-images option.
* update resin-preload to 5.0.0

Connects-To: #705

Change-Type: minor
2017-10-27 12:14:27 +02:00
36d3c92006 Auto-merge for PR #702 via VersionBot
Add preload to the CLI docs
2017-10-25 12:25:50 +00:00
68e31468cc v6.7.4 2017-10-25 11:52:10 +00:00
dfd8b6717d Add preload to the CLI docs
Change-Type: patch
2017-10-25 13:17:07 +02:00
10d688c02d Auto-merge for PR #701 via VersionBot
Allow specifying `--commit=latest` for `resin preload`
2017-10-25 10:21:02 +00:00
737e961979 v6.7.3 2017-10-25 10:17:42 +00:00
3bca36c277 Allow specifying --commit=latest for resin preload
Connects-To: #700

Depends-On: https://github.com/resin-io/resin-preload/pull/137

Change-Type: patch
2017-10-25 12:09:08 +02:00
24b2c83e92 Auto-merge for PR #699 via VersionBot
Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER
2017-10-24 18:01:07 +00:00
266870018a v6.7.2 2017-10-24 17:57:39 +00:00
80bc044415 Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER, by updating it
Connects-To: #698
Change-Type: patch
2017-10-24 19:31:16 +02:00
563628a5a9 Auto-merge for PR #697 via VersionBot
Respect the --dont-check-device-type option, fix error message
2017-10-24 14:59:21 +00:00
385e2c7f7a v6.7.1 2017-10-24 14:56:48 +00:00
19ce4c4cdb Respect the -dont-check-device-type option, fix error message
Connects-To: #696

Change-Type: patch
2017-10-24 16:45:36 +02:00
0b26fda89c Auto-merge for PR #487 via VersionBot
Device api keys
2017-10-18 18:20:15 +00:00
3f692ecbb0 v6.7.0 2017-10-18 18:18:01 +00:00
2d43e47610 Add device api keys warning on device register and os configure 2017-10-18 13:43:16 +02:00
a8f1d16b26 Make resin os configure safe with device keys for all ResinOS versions 2017-10-18 13:43:16 +02:00
8e95757f47 Make resin config generate safe for all ResinOS versions 2017-10-18 13:43:16 +02:00
3fd4f328ab Added a device api key parameter to the os configure command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
97eaf174ec Added a --device-api-key option to the config generate command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
2ef56a9a3f Added a --device-api-key option to the device register command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
93818b1a98 Auto-merge for PR #689 via VersionBot
Fix issue where `os download` would always download prod images
2017-10-18 10:46:19 +00:00
ce70102378 v6.6.13 2017-10-18 10:41:28 +00:00
0e4c6c459c Fix issue where os download would always download prod images
This also adds support for submodules using resin-sdk shared options
rather than resin-sdk-preconfigured.

Change-Type: patch
Connects-To: #688
2017-10-17 21:20:35 +02:00
6b96fe37ba Auto-merge for PR #687 via VersionBot
Update resin-preload to 4.0.2 to support preloading Edison images
2017-10-16 18:55:22 +00:00
e9c7e0e924 v6.6.12 2017-10-16 18:51:54 +00:00
119fa78927 Update resin-preload to 4.0.2 to support preloading Edison images
Change-Type: patch
2017-10-16 19:27:12 +02:00
dad655c9ec Auto-merge for PR #685 via VersionBot
Document how to `resin deploy` to an app as a collaborator
2017-10-13 14:29:14 +00:00
8af392029f v6.6.11 2017-10-13 14:26:20 +00:00
82888de036 Document how to resin deploy to an app as a collaborator
Change-Type: patch
2017-10-13 16:13:46 +02:00
b4a56e1541 Auto-merge for PR #676 via VersionBot
Ensure hostname truly is optional when configuring device images
2017-10-09 10:14:35 +00:00
19b0ec7f8b v6.6.10 2017-10-09 10:12:22 +00:00
3df7bfe700 Ensure hostname truly is optional when configuring device images
Change-Type: patch
2017-10-09 12:03:31 +02:00
d1fd5a6bff Auto-merge for PR #678 via VersionBot
Fix resin preload --splash-image argument handling
2017-10-06 09:22:01 +00:00
43a7e3ddf4 v6.6.9 2017-10-06 09:19:54 +00:00
10976bed43 Fix resin preload --splash-image argument handling
Connects-To: #677

Change-Type: patch
2017-10-06 11:16:06 +02:00
c187c113d9 Auto-merge for PR #675 via VersionBot
Ensure whoami failures (i.e. present but broken tokens) at startup don't break commands
2017-10-06 07:45:00 +00:00
f7c0258145 v6.6.8 2017-10-06 07:42:39 +00:00
eb729d149e Ensure analytics failures (e.g. from broken tokens) at startup don't break commands
Change-Type: patch
2017-10-05 19:03:01 +02:00
39ac28d4ef Auto-merge for PR #666 via VersionBot
Add windows instructions to fix node-gyp installs
2017-09-22 17:17:51 +00:00
989877d541 v6.6.7 2017-09-22 17:14:37 +00:00
3f3af216fd Add instructions for an admin console 2017-09-22 20:10:50 +03:00
9aef632afd Update to resin-sync, which fixes local push on windows
Change-Type: patch
2017-09-22 19:17:28 +03:00
ef6e00bcea Add windows instructions to fix node-gyp installs
Change-Type: patch
2017-09-22 16:27:24 +03:00
55a7dccc74 Merge pull request #659 from resin-io/catch-uncommitted
Move to using the catch-uncommitted npm package
2017-09-15 19:45:45 +02:00
62035fac83 Move to using the catch-uncommitted npm package 2017-09-15 14:36:33 +02:00
950201973b Auto-merge for PR #655 via VersionBot
Create ISSUE_TEMPLATE.md
2017-09-11 14:53:51 +00:00
e431083e84 v6.6.6 2017-09-11 14:45:51 +00:00
1ff9cf02d7 Create ISSUE_TEMPLATE.md
Fixes #529
Change-Type: patch
2017-09-04 20:18:23 +03:00
bd3b9e32a9 Auto-merge for PR #643 via VersionBot
Fix lodash bugs in device move & quickstart
2017-08-31 12:30:48 +00:00
5a620d6c9e v6.6.5 2017-08-31 12:28:28 +00:00
492e35e5c2 Fix lodash bugs in device move & quickstart
Change-Type: patch
2017-08-31 20:22:36 +08:00
8980bc704a Auto-merge for PR #644 via VersionBot
Check for uncommitted output
2017-08-31 12:21:32 +00:00
8b9e78d645 v6.6.4 2017-08-31 12:18:41 +00:00
8f0131cf50 Tweak catch-uncommitted-output script for clarity, after review 2017-08-31 20:12:06 +08:00
47407a84fb Use only npm 4, for now 2017-08-31 20:12:06 +08:00
d858f3fd90 Make sure the catch-uncommitted-output script fails if the build fails 2017-08-31 20:12:06 +08:00
a36f765f1b Catch uncommitted build output automatically in Travis
Connects-To: #573
Change-Type: patch
2017-08-31 20:12:06 +08:00
fd308a5131 Auto-merge for PR #641 via VersionBot
Update README to link to the full CLI command documentation
2017-08-31 11:47:53 +00:00
3052100973 v6.6.3 2017-08-31 11:44:39 +00:00
cfdd4d3d69 Update README to link to the full CLI command documentation
Also update description to match package.json & repo, and remove
quickstart (since it's unstable at the moment).

Change-Type: patch
2017-08-31 19:37:19 +08:00
0a3123a9cf Auto-merge for PR #642 via VersionBot
Use DOCKER_HOST from env if possible, and no connection options are available
2017-08-31 07:24:30 +00:00
5474666f9e v6.6.2 2017-08-31 07:22:43 +00:00
2bbd45e867 Use DOCKER_HOST from env if possible, and no connection options are available
Connects-to: #625
Change-Type: patch
2017-08-31 15:18:32 +08:00
e8c4a9abfd Auto-merge for PR #650 via VersionBot
Update resin-preload to 3.1.4
2017-08-28 09:38:08 +00:00
710a938b3f v6.6.1 2017-08-28 09:34:59 +00:00
be7c1d278e Update resin-preload to 3.1.4
Connects-To: #649

Change-Type: patch
2017-08-28 10:49:08 +02:00
dfcac4a532 Auto-merge for PR #647 via VersionBot
Add a --dont-check-device-type option for `resin preload`
2017-08-28 08:45:41 +00:00
a5128cd49e v6.6.0 2017-08-28 08:42:44 +00:00
223432406d Add a --dont-check-device-type option for resin preload
Connects-To: #646

Change-Type: minor
2017-08-25 12:27:38 +02:00
cafde01886 Auto-merge for PR #645 via VersionBot
Remove resin-preload build filtering workaround.
2017-08-24 12:28:34 +00:00
0158d1da48 v6.5.3 2017-08-24 12:25:11 +00:00
e0d661a1da Remove resin-preload build filtering workaround.
Connects to #640

Change-Type: patch
2017-08-24 12:39:17 +02:00
b152482133 Reformat changelog for versionbot 2017-08-22 19:30:34 +02:00
4cdf3acf42 v6.5.2 2017-08-22 19:22:05 +02:00
314fcd3919 Merge pull request #639 from resin-io/628-add-progress-bars-for-resin-preload
Add progress bars and spinners for resin preload.
2017-08-22 18:44:56 +02:00
b07a394592 Add progress bars and spinners for resin preload.
Connects-To: #628

Change-Type: minor
2017-08-22 18:42:20 +02:00
14c5d938a6 Merge pull request #627 from resin-io/626-dont-preload-the-same-image-twice
Don't try preloading the same build twice in an image.
2017-08-21 19:26:53 +02:00
c6c2f0bedc Don't try preloading the same build twice in an image.
Connects to #626

Change-Type: patch
2017-08-21 19:24:30 +02:00
6882f4bbe8 Fix missed changelog link 2017-08-21 11:37:23 +02:00
74d6cfb8d2 v6.5.1 2017-08-21 11:33:12 +02:00
3b30a7c4b0 Merge pull request #638 from resin-io/636-replace-lodash-object-with-fromPairs
Use _.fromPairs instead of _.object which was removed in lodash 4.0.0
2017-08-21 10:13:05 +02:00
115e46573b Use _.fromPairs instead of _.object which was removed in lodash 4.0.0
Connects-To: #636

Change-Type: patch
2017-08-21 10:01:25 +02:00
2ad789457f Merge pull request #635 from resin-io/prepublishOnly
Move to prepublishOnly to speed up npm install a little
2017-08-18 17:37:46 +02:00
9beb6de7d8 Move to prepublishOnly to speed up npm install a little 2017-08-18 15:56:35 +02:00
74743745c4 v6.5.0 2017-08-18 15:45:45 +02:00
6fae5a2dd9 Merge pull request #631 from resin-io/autodeploy
Set up Travis npm autopublish
2017-08-18 15:40:18 +02:00
4320f33d8e Set up Travis npm autodeploy 2017-08-18 12:41:30 +02:00
435fedfa07 Merge pull request #630 from resin-io/629-fix-resin-preload-when-a-commit-is-provided
Fix resin preload when a commit is provided.
2017-08-18 09:38:40 +02:00
999f269e36 Fix resin preload when a commit is provided.
Connects-To #629

Change-Type: patch
2017-08-18 09:37:37 +02:00
224dfce4a8 Merge pull request #624 from resin-io/remove-babel
Remove unused babel dependency
2017-08-17 11:40:15 +02:00
60634a5ebd Merge pull request #623 from resin-io/upgrade-lodash
Upgrade to lodash v4
2017-08-17 11:38:56 +02:00
f8f1f52662 Remove unused babel dependency 2017-08-16 19:41:56 +02:00
e204707ee0 Upgrade to lodash v4 2017-08-16 18:58:46 +02:00
b28a4a5f99 Merge pull request #621 from resin-io/update-package-description
Update npm package description
2017-08-16 12:45:09 +02:00
340b2d5572 Update npm package description 2017-08-16 11:47:43 +02:00
4dec846256 Merge pull request #617 from resin-io/594-shared-app-lookup
Allow the looking up of applications with the owner username
2017-08-14 22:43:56 +01:00
dc1b3c3239 Allow the looking up of applications with the owner
change-type: minor
Connects-to: #594
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-08-14 17:00:33 +01:00
2fecb8d3e9 Merge pull request #619 from resin-io/618-use-patched-global-tunnel-ng
Use forked global-scoket-ng that doesn't proxy connection to socket files.
2017-08-11 17:18:52 +02:00
4665a72baf Use forked global-scoket-ng that doesn't proxy connection to socket files.
Connects to #618

Change-Type: patch
2017-08-11 17:02:17 +02:00
b208d601c9 Merge pull request #610 from resin-io/609-integrate-docker-preload
Integrate resin-preload
2017-08-11 15:45:05 +02:00
22b3c39b2b Integrate resin-preload
* split docker connection options from lib.utils.docker.appendOptions

Connects to #609
Connects to https://github.com/resin-io/resin-preload/pull/81

Change-Type: minor
2017-08-11 15:43:07 +02:00
30cca93283 v6.4.0 2017-08-11 13:31:53 +02:00
f0d9b615a7 Merge pull request #615 from resin-io/612-remove-inconsistent-tagging-message
Remove inconsistent (and now unneccesary) 'Tagging image as' message
2017-08-11 13:31:16 +02:00
b7e2c2571f Remove inconsistent (and now unneccesary) 'Tagging image as' message
Change-Type: patch
2017-08-11 13:30:44 +02:00
25538a9afb Merge pull request #616 from resin-io/no-docker-warning
Provide a helpful warning when Docker is not installed
2017-08-11 11:34:04 +02:00
5daa682183 Provide a helpful warning when Docker is not installed
Change-Type: patch
2017-08-10 21:40:05 +02:00
bad4493867 Merge pull request #614 from resin-io/603-node-upgrade-help
Help users using old node versions to upgrade
2017-08-10 14:43:29 +02:00
9e6dd57a5c Help users using old node versions to upgrade 2017-08-09 15:20:50 +02:00
1b86741fa2 v6.3.1 2017-08-08 11:37:22 +03:00
6cca43a09e v6.3.0 2017-08-03 18:05:27 +03:00
b622995a5a Merge pull request #606 from resin-io/534-update-resin-sync
update resin-sync
2017-08-03 18:04:13 +03:00
4329857a16 update resin-sync 2017-08-03 18:01:45 +03:00
d803cfab3a v6.2.0 2017-07-27 14:07:05 +03:00
e7d7ca807f Merge pull request #601 from resin-io/597-resin-wifi-config
Support the new OS with resin-sample.ignore connection file
2017-07-27 14:02:29 +03:00
22e0b4b9dc Support the new OS with resin-sample.ignore connection file 2017-07-27 14:01:49 +03:00
759baf3eda v6.1.1 2017-07-18 18:09:32 +02:00
e6dc7f8075 Merge pull request #595 from resin-io/simplify-login-message
Hide the intro quickstart message, for now (until it gets renovated)
2017-07-18 17:02:18 +02:00
1f0bec39d9 Hide the intro quickstart message for now (until it gets renovated) 2017-07-18 16:57:16 +02:00
64b6549fde v6.1.0 2017-06-30 20:49:08 +02:00
fc7e08c886 Merge pull request #586 from resin-io/543-fix-cwd-string
Upgrade resin-sync to fix node 8 bug
2017-06-30 20:45:59 +02:00
4aadfe9326 Upgrade resin-sync to fix node 8 bug
Connects-To: #543
Change-Type: patch
2017-06-30 19:49:35 +02:00
a65868cbbf Tiny patch to move a CHANGELOG line to the right place 2017-06-29 12:44:14 +02:00
8ab528aae4 Merge pull request #570 from resin-io/bump-resin-sync
Bump resin-sync@8.0.0
2017-06-29 11:56:57 +03:00
d93b82a269 Bump resin-sync@8.0.0
- resin sync: do not explicitly disable ControlMaster SSH option
- resin sync: whitelist collaborators

fixes #422
change-type: minor
2017-06-29 11:56:01 +03:00
3095938b0e Merge pull request #569 from resin-io/permit-ssh-controlmaster
Do not explicitly disable ControlMaster option for device SSH connections
2017-06-29 11:54:19 +03:00
91b3442fc9 Do not explicitly disable ControlMaster option for device SSH connections
The backend server that handles `resin ssh` now supports it.

Also removed the option from local ssh connections to devices, where it
basically has no effect (dropbear on devices supports it)

change-type: minor
fixes #568
2017-06-29 11:53:31 +03:00
e660c6ae90 Merge pull request #557 from resin-io/539-fix-clearLine
Fix issue when using resin deploy with non-standard stdin
2017-06-28 20:30:48 +02:00
e2a165ce80 Show a correct [Info] tag with the deploying progress bar 2017-06-28 18:57:49 +02:00
ce5685551d Remove unused function 2017-06-28 18:52:44 +02:00
15e677e9f1 Refactor stream logger to keep streams as state 2017-06-28 18:52:40 +02:00
5ccde3db8e Fix issue when using resin deploy with non-standard stdin
This fixes issues when piping `resin deploy` to a non-TTY, and should
solve issues on Windows too.

Connects-To: #539
2017-06-28 18:40:00 +02:00
cb550f65bb Merge pull request #582 from resin-io/580-fix-emulated-build-arg
Fix issue where emulated builds broke Docker ARG commands
2017-06-27 16:07:05 +02:00
8d3987fc70 Fix issue where emulated builds broke Docker ARG commands 2017-06-27 15:06:06 +02:00
4fa8d86f02 v6.0.0 2017-06-26 13:38:22 +02:00
6b22166887 Merge pull request #577 from jacintoArias/master
Allow --squash option on resin build command
2017-06-26 13:35:35 +02:00
9e555b3dba Updated CHANGELOG.md 2017-06-26 13:34:22 +02:00
bea7b2035a Added --squash option to resin build command 2017-06-26 13:33:50 +02:00
abebf6b4f4 Merge pull request #576 from resin-io/567-require-node-6
Remove Buffer polyfill, require Node v6+, and print warnings in older versions
2017-06-26 13:28:20 +02:00
6182e7c98a Make Node warning a multi-line string 2017-06-26 13:24:54 +02:00
410390a9ae Remove Buffer polyfill, require Node v6+, and print warnings in older versions 2017-06-22 18:59:50 +02:00
11079caf26 v5.11.1 2017-06-22 18:19:53 +02:00
0c6545218a Merge pull request #574 from resin-io/565-log-node-version
Include node version in sentry logs
2017-06-22 18:17:33 +02:00
7fc806c75f Merge pull request #571 from resin-io/566-fix-v4-compat
Add a polyfill to fix local configure in older (<6) Node versions
2017-06-22 18:12:39 +02:00
18533de3da Include node version in sentry logs 2017-06-22 14:48:46 +02:00
c5afe5f2c2 Update build output, after missed recompile 2017-06-22 14:10:16 +02:00
2875bd672e Add a polyfill to fix local configure in older (<6) Node versions 2017-06-22 13:38:38 +02:00
26d123b33d Merge pull request #563 from resin-io/562-fix-tls-connect
Read ca files and convert to string before passing to the docker daemon
2017-06-22 08:41:10 +01:00
5000febf72 Read ca files and convert to string before passing to the docker daemon
Before this commit, the docker daemon would recieve the filename of the
.pem files, which would be interpreted as the body and would fail. This
commit ensures that the actual body of the pem files are sent to the
daemon.

Change-type: patch
Connects-to: #562
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-06-21 11:11:24 +01:00
378f894da3 v5.11.0 2017-06-19 01:16:04 +03:00
c891d690ec update lockfile 2017-06-19 01:14:08 +03:00
8efaec63ff Merge pull request #559 from resin-io/558-absolute-qemu
Make emulated builds reliable in the presence for WORKDIR comands
2017-06-15 19:41:52 +02:00
b756f2a597 Make emulated builds reliable in the presence for WORKDIR comands
Connects-To: #558
Change-Type: patch
2017-06-15 16:36:44 +02:00
c986e142e2 Merge pull request #548 from resin-io/462-noninteractive-device-init
non-interactive device init
2017-06-15 17:06:41 +03:00
8bdacbb11e dedupe the shared options 2017-06-15 16:43:12 +03:00
d2a9aee685 improve available drives listing
make the config a path to the file and not the stringified JSON
2017-06-15 16:43:02 +03:00
77a4c6fdc2 move available-drives command to util 2017-06-15 16:43:02 +03:00
4d935d62fc improve wording 2017-06-15 16:42:46 +03:00
e8b44d7250 Forced update to the newest resin-sdk 2017-06-15 16:40:46 +03:00
5c5cfde49f automated init doc 2017-06-15 16:39:55 +03:00
97480d3aa4 Improve the supported device types listing 2017-06-15 16:39:55 +03:00
4ac8cb1003 rebuild docs 2017-06-15 16:39:55 +03:00
2e7e033bb9 allow prebuilding the device config and reusing it 2017-06-15 16:39:55 +03:00
9fb5b52069 update dependencies 2017-06-15 16:39:55 +03:00
ad940824a6 list detected drives with resin os available-drives 2017-06-15 16:39:55 +03:00
ed83514a2f allow passing --drive to resin device init 2017-06-15 16:39:55 +03:00
8d91a5732a tolerate the --yes param to device init 2017-06-15 16:39:55 +03:00
1cfe64e4a7 choose version during device init, and list versions with resin os versions 2017-06-15 16:39:55 +03:00
24388811ad remove the user requirement for the supported devices list 2017-06-15 16:38:55 +03:00
f465e74a87 Merge pull request #556 from resin-io/deploy-ux-improvements
Resin deploy ux improvements
2017-06-15 14:31:27 +02:00
c8d51d92e7 Show a clear message immediately as the deploy starts, if we're deploying an image.
Change-Type: patch
2017-06-15 12:36:40 +02:00
5a28d4c92f Make cleanup reliable if a local build fails 2017-06-15 12:36:40 +02:00
66a4faeea5 Small improvement to resin deploy docs
Change-Type: patch
2017-06-15 12:36:40 +02:00
2fce0e964b Merge pull request #554 from resin-io/549-gzip-image-deploy
Gzip images when deploying
2017-06-15 12:35:54 +02:00
a29b40eefa Move promise.spread to promise.join for clarity (from review) 2017-06-15 12:34:26 +02:00
cf7bf2cb7d Fix the gzip level for image uploads to a good perf/size balance 2017-06-15 12:01:22 +02:00
df3c5ca07f Gzip while streaming, rather than gzipping the buffer up front
Connects-To: #549
2017-06-15 12:01:22 +02:00
e584dc43f7 Gzip images when uploading in resin deploy
Change-Type: minor
Connects-To: #549
2017-06-15 12:01:22 +02:00
90a5b15dbc Refactor docker stream buffering before start gzipping 2017-06-15 12:01:22 +02:00
390332cd6c Merge pull request #555 from resin-io/553-fix-qemu-path
Ensure emulated builds use the correct relative path to qemu
2017-06-15 12:00:58 +02:00
37ec11bf25 Ensure emulated builds use the correct relative path to qemu
Change-Type: patch
Connects-To: #553
2017-06-14 20:50:24 +02:00
08ce1abd20 Merge pull request #511 from resin-io/emulated-builds
Add emulated build option to resin build
2017-06-14 11:06:34 +01:00
f2862f7fe2 Add emulated build option to resin build
This commit adds the ability to run a Docker build for an architecture
which is not the host architecture, using qemu-linux-user. Currently
this is only supported for linux.

Added:
* Installation of qemu which supports propagated execve flags
* Copying of qemu binary into the build context
* Transposing the given Dockerfile to use the qemu binary
* Intercepting of the build stream, so the output looks *almost* exactly
  the same.

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-06-14 12:06:03 +01:00
e0673c98fc Remove leftover action/util build output 2017-06-09 03:24:34 +02:00
4ab67ed71d changelog 2017-06-08 13:53:41 +03:00
5ea263ef2e Add package-lock.json 2017-06-08 13:52:47 +03:00
31419b399e v5.10.2 2017-05-31 13:08:04 +01:00
9e8b09010f Merge pull request #540 from resin-io/args
resin build: fix mismatch in command line argument signature
2017-05-31 13:05:48 +01:00
974be5cc13 resin build: fix mismatch in command line argument signature
The command line arg was taking `devicetype`, but the rest of the code
uses `deviceType`. Thus it was impossible to specify a device type
in practice to build a `Dockerfile.template`.

Change-type: patch
Signed-off-by: Gergely Imreh <imrehg@gmail.com>
2017-05-30 10:34:32 +01:00
ec386b807f Merge pull request #518 from resin-io/485-update-interval
Change update check interval to once a day
2017-05-24 11:12:54 -07:00
abc183a729 Change update check interval to once a day
This gives the user enough notice to stay well updated, but won't spam
them if they're using resin-cli frequently.

Connects-to: #485
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-23 17:29:23 -07:00
e5ed6fab85 v5.10.1 2017-05-22 21:59:12 +02:00
eb9152255e Merge pull request #536 from resin-io/535-fix-local-ssh
Fix breaking bug in resin local ssh
2017-05-22 21:55:59 +02:00
78ab47b584 Fix breaking bug in resin local ssh 2017-05-22 20:49:34 +02:00
5b651c7821 v5.10.0 2017-05-22 15:44:28 +02:00
95fc5d7785 Merge pull request #517 from resin-io/ssh-proxy
resin ssh proxy support
2017-05-22 13:04:49 +02:00
4d18e92686 changelog 2017-05-20 00:20:22 +03:00
2d729a82a0 fix valid-email path 2017-05-20 00:19:31 +03:00
4b5240d8cd check for proxytunnel presence 2017-05-19 02:10:14 +03:00
6ae59654a0 fix 2017-05-19 01:44:30 +03:00
b88f7a993c escape params 2017-05-19 01:25:01 +03:00
880fb43fd9 some fixes 2017-05-18 15:12:52 +03:00
fa71df7c70 use upstream dependency 2017-05-12 18:09:43 +03:00
bc79832e1d resin ssh proxy support 2017-05-12 18:09:43 +03:00
1d06bc1b4f Merge pull request #524 from resin-io/520-include-command-line
Include full command line arguments in Sentry errors
2017-05-12 13:27:53 +02:00
88d5ec0c94 Merge pull request #525 from resin-io/508-fix-docs
Get docs and the code generating them back in sync
2017-05-12 13:27:35 +02:00
ebd9e92b10 Merge pull request #523 from resin-io/522-username-in-errors
Include the username used in Sentry errors
2017-05-12 12:04:51 +02:00
a5b535753f Include the username used in Sentry errors, to help us debug them 2017-05-12 12:02:26 +02:00
6e5e4bd8a6 Get docs and the code generating them back in sync (from #515, #508) 2017-05-12 12:01:37 +02:00
6e034acf23 Include full command line arguments in Sentry errors, to help us debug them 2017-05-12 11:48:43 +02:00
3a44782c38 Merge pull request #515 from resin-io/build-time-vars
Add ability to specify built-time variables for local build
2017-05-11 14:31:45 +03:00
654ec75598 Renamed build arg option to —buildArg/-B 2017-05-11 14:20:24 +03:00
66876a2c85 Add ability to specify built-time variables for local build
Change-Type: patch
2017-05-11 14:14:43 +03:00
8cb862359b Merge pull request #514 from resin-io/upload-progress-bar
Add progress bar for upload progress
2017-05-11 14:12:21 +03:00
172fa37bd4 Added CHANGELOG entry 2017-05-10 23:06:35 +03:00
fc5640c79d Draw a progress bar for upload progress
Change-Type: patch
2017-05-10 22:28:51 +03:00
db9225f00a Merge pull request #398 from resin-io/395-show-device-dashboard-link
Show device dashboard url in 'resin device' output
2017-05-08 14:05:40 +03:00
c12b59b978 Show device dashboard url in 'resin device' output
change-type: minor
2017-05-08 13:51:06 +03:00
7eeafa935a Merge pull request #510 from resin-io/highlight-cache-usage
Highlight cache usage in resin build
2017-05-04 14:12:40 +01:00
404348f92e Highlight cache usage in resin build
This commit will highlight the usage of the cache when doing a docker
build via `resin build`, which not only helps the user understand what
the build is doing, but also achieves more parity with the cloud
builder.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 14:09:23 +01:00
4d2af251b2 Merge pull request #502 from resin-io/not-enough-unicorns
Fix the not-enough-unicorns bug
2017-05-04 13:22:42 +01:00
d249ac168a Fix the not-enough-unicorns bug
Add successful build indicator in the form of a unicorn.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 13:18:25 +01:00
65eaad2ed5 Merge pull request #493 from resin-io/local-upload-logs
Upload logs when doing both build + deploy
2017-05-04 13:16:01 +01:00
3ff5880ae3 Allow resin-cli deploy to also upload build logs if present
If build is ran through `resin deploy`, then logs will be stored and
uploaded to the database, where the dashboard can display them

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 13:11:50 +01:00
511d2abe1d Merge pull request #508 from resin-io/507-add-device-types-note
Add note on how to get resin device types
2017-05-02 10:37:30 +01:00
029b7c7164 Add note on how to get resin device types
Connects to #507

Change-Type: patch
2017-05-01 17:14:58 +01:00
afafa22694 v5.9.1 2017-05-01 14:19:21 +03:00
2df4422748 v5.9.0 2017-05-01 14:14:37 +03:00
e099f0b8b3 Merge pull request #505 from resin-io/proxy
proxy support
2017-05-01 14:12:49 +03:00
8866f47805 proxy support 2017-05-01 14:00:50 +03:00
1d8382e91d v5.8.1 2017-04-27 16:24:57 +03:00
110e18bc88 Merge pull request #500 from resin-io/fix-ssh
fix ssh proxy URL retrieval
2017-04-27 16:24:08 +03:00
3df30c8b5a fix ssh proxy URL retrieval 2017-04-27 16:20:32 +03:00
ebfd842ae5 Merge pull request #499 from resin-io/add-local-build-docs
Add documentation for new resin build and resin deploy commands
2017-04-26 14:15:34 +01:00
39b171fd2a Add documentation for new resin build and resin deploy commands
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-26 13:38:35 +01:00
04c2333a54 v5.8.0 2017-04-26 12:20:30 +01:00
65884c81a4 Merge pull request #498 from resin-io/prettify-build
Add cloud-builder builder output parity to build and deploy
2017-04-25 13:37:26 +01:00
f50ae65560 Add cloud-builder builder output parity to build and deploy
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-25 13:13:11 +01:00
be0d6d5a99 Merge pull request #497 from resin-io/share-build-opts
Share nocache and tag build options between build and deploy
2017-04-24 20:56:41 +01:00
4fa1a9c1c6 Share nocache and tag build options between build and deploy
`resin build` had access to the `--nocache` and `--tag` options for
building with docker, but `resin deploy` did not. This commit adds the
options to the shared dockerUtils.appendOptions function.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-24 20:05:18 +01:00
284030d83d Merge pull request #495 from resin-io/fix-build-source
Respect source parameter in resin build
2017-04-24 20:04:22 +01:00
9050cb1975 Respect source parameter in resin build
Upon changing the name of the source parameter from `context`, some
places weren't changed, this commit fixes that.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-24 16:41:15 +01:00
d55ce853a7 Merge pull request #466 from resin-io/local-build
Add ability to build and deploy image locally using resin-cli
2017-04-24 10:39:46 +01:00
d3772386bf Add ability to build and deploy image locally using resin-cli
Using `resin build` a user can now build an image on their own docker
daemon. The daemon can be accessed via a local socket, a remote host and
a remote host over a TLS socket. Project type resolution is supported.
Nocache and tagging of images is also supported.

Using `resin deploy` a user can now deploy an image to their fleet. The
image can either be built by `resin-cli`, plain Docker, or from a remote
source.

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-23 14:31:45 +01:00
2e042499af Merge pull request #490 from resin-io/486-capture-init-errors
log unhandled exceptions
2017-04-21 23:11:56 +03:00
225d3acf9e log unhandled exceptions 2017-04-21 13:10:30 +03:00
75d10286ad v5.7.2 2017-04-18 12:15:11 +03:00
d6e0616c58 Merge pull request #482 from resin-io/node-v4-support
update to modules that have native node v4 support
2017-04-18 12:12:52 +03:00
380a94f0f8 update to modules that have native node v4 support 2017-04-14 14:48:19 +03:00
9da8c5f09a ignore .DS_Store 2017-04-14 11:41:19 +03:00
89b88315e4 Merge pull request #480 from resin-io/479-resin-local-docs
Add resin local docs
2017-04-11 16:15:30 +01:00
11e8ca178c Add resin local docs
Connects to #479

Change-Type: patch
2017-04-11 14:25:54 +01:00
e583e6f04c Merge pull request #476 from resin-io/fix-peer-dep-error
Move to a consistent resin-token by upgrading to new v6 SDK and resin-cli-auth
2017-04-10 12:42:06 +02:00
0cce2a7ab7 Move to a consistent resin-token by upgrading to new v6 SDK and resin-cli-auth 2017-04-07 21:26:56 +02:00
965aa7e4d4 v5.7.1 2017-04-03 16:07:32 +02:00
0ca64b18a2 Merge pull request #471 from resin-io/470-non-semver-os
Handle non-semver OS versions
2017-04-03 16:06:07 +02:00
63e1313f44 Handle non-semver OS versions 2017-04-03 15:36:04 +02:00
96a7a24738 v5.7.0 2017-03-28 14:43:31 +03:00
dede4bb329 Merge pull request #461 from resin-io/452-fix-permissions
isolate the sudo-runnable command
2017-03-28 14:40:51 +03:00
77b30409bb update resin-device-init to pull in etcher-image-write 2017-03-28 14:37:13 +03:00
137473353c remove username request from the signup process 2017-03-28 14:37:11 +03:00
08b3db717e use individual methods promisification instead of promisifyAll 2017-03-27 12:14:55 +03:00
6cf32e445a isolate the sudo-runnable command 2017-03-27 11:43:35 +03:00
857c5204b9 v5.6.1 2017-03-23 15:52:06 +03:00
169609620a Merge pull request #459 from resin-io/fix-unneeded-warning
Fix unneeded warning
2017-03-23 15:51:13 +03:00
8149172eb0 changelog 2017-03-23 15:32:06 +03:00
cba105a41b suppress warning during the device init OS download 2017-03-23 15:27:54 +03:00
69dff0c603 Merge pull request #450 from resin-io/420-add-sentry
Add sentry error tracking
2017-03-23 13:11:15 +01:00
6ffa4070ff Merge pull request #457 from resin-io/replace-gitter-with-forums
Point to forums instead of gitter
2017-03-23 14:10:18 +02:00
f05b04a6a1 Move sentry DSN to config 2017-03-23 13:10:15 +01:00
88d8112402 Add sentry error tracking 2017-03-23 12:59:42 +01:00
f940d7428c Point to forums instead of gitter
change-type: patch
2017-03-23 12:19:59 +02:00
697779868e Merge pull request #437 from alisondavis17/patch-1
Update README.md
2017-03-23 12:19:24 +02:00
d90874dbef v5.6.0 2017-03-23 01:36:08 +03:00
65b2347b57 Merge pull request #453 from resin-io/41-select-os-version
Closes #451
2017-03-23 01:32:15 +03:00
b25034978b use the published depdendencies 2017-03-23 01:24:25 +03:00
a817bb2135 update gitignore 2017-03-22 22:47:26 +03:00
b629c3601e implement the version menu 2017-03-22 15:45:05 +03:00
3619b2f117 allow specifying the version 2017-03-22 13:28:46 +03:00
4231f50c4c download the non-preview version by default 2017-03-22 12:55:55 +03:00
95fff4b7c4 build bare modules 2017-03-22 12:46:06 +03:00
b3aa3d35f7 fix resin local push help message and lint errors 2017-03-21 12:06:05 +03:00
9f3108c5e7 5.5.0 2017-03-10 18:19:45 +00:00
c95a01e34e Merge pull request #444 from resin-io/scan-sudo-and-fixed-latency
Scan sudo and fixed latency
2017-03-10 18:04:56 +00:00
19c51929a9 Generate JS 2017-03-10 17:59:54 +00:00
73dd625ede Require superuser for scan commands, also introduce docker timeout 2017-03-10 17:59:54 +00:00
08db3ace03 Bump resin-sync@7.0.0: use experimental rds which requires superuser permissions 2017-03-10 17:59:54 +00:00
2125cf9649 5.4.0 2017-03-09 23:56:10 +00:00
e5a7fa5617 Merge pull request #439 from resin-io/resin-local-stop
Resin local stop
2017-03-09 23:50:58 +00:00
3324ff4dee Generate JS 2017-03-09 23:49:59 +00:00
7ad468dc54 Implement 'resin local stop' 2017-03-09 23:49:56 +00:00
8474ee726c Update README.md
Change Support section to point to Forums instead of Gitter
2017-03-09 14:46:20 -08:00
29b2f4afa8 Merge pull request #433 from resin-io/filter-missing-dockerinfo-devices
Filter our devices that do not expose docker socket
2017-03-09 21:19:23 +00:00
7aee4d6d7f Filter our devices that do not expose docker socket 2017-03-09 20:54:11 +00:00
7af004501a Merge pull request #430 from resin-io/rdt-merge
Rdt merge
2017-03-09 11:21:25 +00:00
2d09c18d6b Build JS 2017-03-08 23:41:55 +00:00
53bf314820 Remove app create from primary commands 2017-03-08 23:41:35 +00:00
1ae1a15259 Implement 'resin local' 2017-03-08 23:41:35 +00:00
20ed8c9169 Implement 'resin local push' 2017-03-08 23:41:32 +00:00
977e3fb0ff Implement 'resin local ssh' 2017-03-08 23:41:29 +00:00
c5df32f952 Implement 'resin local scan' 2017-03-08 22:43:23 +00:00
f5cd3375f2 Implement 'resin local promote' 2017-03-08 22:43:23 +00:00
3b4c8f2a01 Implement 'resin local logs' 2017-03-08 22:43:23 +00:00
356042557e Implement 'resin local flash' 2017-03-08 22:43:21 +00:00
00753a5776 Implement 'resin local configure' 2017-03-08 18:43:34 +00:00
eea9a2f723 5.3.0 2017-03-03 18:14:48 +02:00
5ebdf4a8ac Merge pull request #424 from resin-io/ssh-handle-undefined-uuid
resin ssh: handle undefined uuid parameter
2017-03-03 17:29:21 +02:00
fb06249b08 resin ssh: handle undefined uuid parameter
change-type: patch
2017-03-03 17:25:51 +02:00
9214cb0d90 Merge pull request #416 from resin-io/support-aufs-resin-sync
Support aufs resin sync
2017-02-21 15:03:59 +02:00
a04c3b9c7b Generate JS and add gulpfile option to suppress diff 2017-02-21 14:45:47 +02:00
2fde6241c2 Support resin sync for remote resin.io AUFS devices
change-type: minor
2017-02-01 15:52:17 +02:00
ffa645f85c Merge pull request #413 from resin-io/sdk-browser
Use the updated SDK
2017-01-27 13:41:17 +01:00
b629ee6164 Move to preconfigured resin sdk 2017-01-25 19:33:43 +01:00
7a4de5357e Fix some issues with Resin-SDK usage after initial testing 2017-01-25 19:25:12 +01:00
5bbb055cd9 Formally depend on the new resin-sdk release 2017-01-25 19:25:12 +01:00
553b96e48f add the CS preamble to the generated files 2017-01-25 19:25:12 +01:00
7a0e8beb07 update for the new resin.models.device.generateUniqueKey 2017-01-25 19:25:12 +01:00
f17cbb1205 use the new SDK factory 2017-01-25 19:25:12 +01:00
b690060bc4 update deps and remove unused 2017-01-25 19:25:12 +01:00
72b2058d27 Merge pull request #418 from resin-io/lekkas-patch-1
docs: rename 'Git Bash' to 'Git for Windows'
2017-01-18 19:46:40 +02:00
bbd617ea76 docs: rename 'Git Bash' to 'Git for Windows' 2017-01-18 19:33:49 +02:00
f9a4f8c375 v5.2.4 2017-01-18 19:14:52 +02:00
5c289a6e00 Merge pull request #417 from resin-io/fix-cmd-exe-doc-error
Fix requirement docs for resin sync and resin ssh
2017-01-18 19:07:36 +02:00
3b439282ae Regenerate JS and add header to suppress diff 2017-01-18 18:04:42 +02:00
d473509675 Docs: fix requirements for resin ssh and resin sync
change-type: patch
2017-01-18 18:04:37 +02:00
49664b815d v5.2.3 2017-01-04 13:54:09 -04:00
06cc863f02 Merge pull request #412 from resin-io/411-fix-add-js-yaml
add missing dependency - fix #411
2016-12-16 17:38:57 +03:00
099cf997cb add missing dependency - fix #411 2016-12-16 17:21:05 +03:00
e8183d4031 v5.2.2 2016-11-01 14:38:55 -04:00
6954da4a24 Merge pull request #407 from resin-io/add-shutdown-command
Actually add the shutdown command
2016-11-01 12:42:21 -04:00
c18e8f1dbd Actually add the shutdown command 2016-11-01 12:42:29 -03:00
741b53ad7e Merge pull request #406 from resin-io/rebuild-coffeescript
Rebuild CoffeeScript
2016-10-28 16:45:56 -04:00
8282785b2a Rebuild CoffeeScript
See https://github.com/resin-io/resin-cli/pull/405#issuecomment-256998739
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-10-28 16:45:18 -04:00
e013986dba v5.2.1 2016-10-28 12:17:25 -04:00
3083ff0640 Merge pull request #405 from resin-io/fix-capitano-error
Fix `Boolean options can't have parameters` error
2016-10-28 12:16:09 -04:00
01cad3c048 Fix Boolean options can't have parameters error
This error was introduced as part of
`9cf42462c029e038e09efc961736946be8bfcb9b`, since the `forceUpdateLock`
option being used in the `reboot` command contains a `parameter`
property despite being declared a boolean.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-10-28 12:11:57 -04:00
c9919a90a8 v5.2.0 2016-10-27 17:47:43 -04:00
0bf6bcc430 Merge pull request #404 from resin-io/reboot-shutdown-force
Add a device shutdown command, and allow forcing reboot and shutdown
2016-10-27 18:42:30 -03:00
9cf42462c0 Add a device shutdown command, and allow forcing reboot and shutdown 2016-10-27 18:39:31 -03:00
0f4eca2ff0 Update resin-sdk to 5.4.0 2016-10-27 12:02:37 -03:00
d414d1c5e3 Merge pull request #403 from resin-io/coffee-script-1.11.1
Update coffee-script to 1.11.1
2016-10-26 12:04:18 -04:00
afe98ff37d Update coffee-script to 1.11.1 2016-10-26 11:55:39 -03:00
ce026ea387 v5.1.0 2016-09-25 19:55:55 -04:00
25f8f3d740 Merge pull request #397 from resin-io/regenerate-docs
Regenerate markdown documentation
2016-09-25 19:52:41 -04:00
f719f5c948 Regenerate markdown documentation
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-25 19:51:32 -04:00
e2cb79edb1 Merge pull request #396 from resin-io/devices-supported
Bring back `devices supported` command
2016-09-25 19:51:02 -04:00
c6e669fa6b Bring back devices supported command
Fixes: https://github.com/resin-io/resin-cli/issues/394
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-25 19:49:06 -04:00
7953f6f690 5.0.0 2016-09-15 19:45:23 +03:00
baf367b276 Merge pull request #367 from resin-io/features/resin-sync-ssh-improvements
resin sync/ssh improvements
2016-09-15 09:21:24 -07:00
7fecb53cdf resin sync/ssh: update docs 2016-09-15 13:09:25 +03:00
b4edb7ed7f resin sync/ssh: generate JS 2016-09-15 13:09:25 +03:00
16a1741374 resin sync: always display device selection dialog when uuid is not passed as an argument
Closes #375
2016-09-15 13:09:25 +03:00
e0a2217b94 resin sync/ssh: always display selection dialog, even for a single online device
Closes #373
2016-09-15 13:09:25 +03:00
7bd8a683b2 resin sync: remove quotes from --source/--destination help as it implies they're required
Closes #372
2016-09-15 13:09:25 +03:00
6b00bbc73a resin sync: add --after option 2016-09-15 13:09:25 +03:00
42d0b52df7 resin ssh: disable ControlMaster ssh option
This change was necessary because our ssh gateway does not
support ControlMaster mode.

Closes #366
2016-09-15 13:09:25 +03:00
97c768edcd resin sync: add --skip-gitignore option. Improve help section wording 2016-09-15 13:09:25 +03:00
10a0924cd7 resin sync: load uuid from .resin-sync.yml if possible 2016-09-15 13:09:25 +03:00
fdb8bf6967 resin sync: add --destination option and require --source if .resin-sync.yml is missing
Closes #359
2016-09-15 13:09:25 +03:00
81d8974213 resin-sync: update to resin-sync@3.0.0 2016-09-15 13:09:25 +03:00
af8d20ea3f resin sync/ssh: only accept uuid as destination
Also change --port option alias to '-p' from '-t'
2016-09-15 13:09:25 +03:00
7d606568f6 v4.5.0 2016-09-14 11:09:43 -07:00
de13337655 Merge pull request #392 from resin-io/manifest-first-partition
Attempt to get device type from the image first partition
2016-09-14 11:08:19 -07:00
8b485b5ad5 Attempt to get device type from the image first partition
New images will ship a `device-type.json` file in the first partition,
which we can use instead of querying the API for certain configuration
and initialisation commands.

If the file is not found, or is malformed, we still fallback to the API.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-14 11:06:17 -07:00
fbccf8a465 Merge pull request #393 from resin-io/upgrade-resin-device-init
Upgrade `resin-device-init` to v2.1.0
2016-09-14 11:05:28 -07:00
ce50d8b73d Upgrade resin-device-init to v2.1.0
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-14 11:03:51 -07:00
2088cbe896 v4.4.0 2016-08-11 10:35:19 -04:00
870ce974e0 Merge pull request #387 from resin-io/Add_OS_VERSION
Add OS version to devices,device commands and Supervisor Version to devices command
2016-08-11 10:24:00 -04:00
5f8c261288 v4.3.0 2016-08-11 10:21:41 -04:00
cb386d15aa Add OS version to devices,device commands and Supervisor Version to devices command 2016-08-10 18:42:34 +05:30
5ed26250d2 Merge pull request #384 from resin-io/features/deviceUrl
Implement `device enableDeviceUrl/disableDeviceUrl/hasDeviceUrl/getDeviceUrl`
2016-08-09 16:45:46 -04:00
7b0415a270 Switch to more human-like command name and follow convention from dashboard 2016-08-09 22:44:40 +05:30
3adb8f19bd Implement device enableDeviceUrl/disableDeviceUrl/hasDeviceUrl/getDeviceUrl 2016-08-09 18:45:24 +05:30
d81fbad6f3 Merge pull request #376 from resin-io/feature/global-help-option
Add global --help option
2016-07-31 20:41:28 -04:00
a70e38ef12 Add global --help option 2016-07-29 15:32:12 +03:00
aeba64b1ee v4.2.1 2016-07-26 13:04:21 -04:00
e6e44474a4 Merge pull request #371 from resin-io/upgrade-sdk
Upgrade `resin-sdk` to v5.3.5
2016-07-26 13:03:18 -04:00
ea44c0571b Upgrade resin-sdk to v5.3.5
This version contains a fix for `undefined` logs. See:

- https://github.com/resin-io/resin-sdk/pull/217
- https://github.com/resin-io/resin-device-logs/pull/14

Fixes: https://github.com/resin-io/resin-cli/issues/370
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-26 13:01:25 -04:00
f68364b695 Merge pull request #365 from resin-io/feature/sync-ssh-device-autochoose
Feature/sync ssh device autochoose
2016-07-07 22:21:37 +03:00
81a6843c93 resin ssh: Implement device inference and autoselect if there is a single one 2016-07-07 21:51:53 +03:00
b672ff1fa1 resin sync: change argument/help to a more meaningful 'resin sync [destination]' 2016-07-07 20:37:11 +03:00
b4c89e812c Merge pull request #364 from resin-io/feature/sync-filter-offline-devices
resin sync: filter out offline devices in interactive choosing dialog
2016-07-07 13:14:57 -04:00
68808e760e resin sync: filter out offline devices in interactive choosing dialog 2016-07-07 19:12:39 +03:00
69c98f4afb v4.2.0 2016-06-22 16:19:31 -04:00
509fef754e Merge pull request #356 from resin-io/feature/resin-sync-verbose
Support verbose flag for resin sync
2016-06-22 16:18:34 -04:00
6d1d4dc173 Support verbose flag for resin sync 2016-06-22 17:59:14 +03:00
0d7d6de7cd v4.1.0 2016-06-22 09:46:18 -04:00
c5452f9304 Merge pull request #354 from resin-io/feature/resin-ssh-verbose
Support --verbose/-v flag in resin ssh
2016-06-22 09:37:03 -04:00
12854db923 Support --verbose/-v flag in resin ssh
Closes https://github.com/resin-io/resin-cli/issues/353
2016-06-22 14:57:43 +03:00
51f9e18f6a Merge pull request #352 from resin-io/feature/reword-config-help
Reword config help instructions
2016-06-14 12:10:05 -04:00
29c20e32f6 Reword config help instructions
Closes #351
2016-06-14 19:08:25 +03:00
995194fe2c Merge pull request #346 from resin-io/misc/ssh-sync-cmd-exe-warning
Clarify that sync and ssh only support cmd.exe on Windows
2016-05-19 10:17:28 -04:00
7c784909fd Merge pull request #345 from resin-io/change-resin-ssh-loglevel
Change resin ssh loglevel from QUIET to ERROR
2016-05-19 10:17:20 -04:00
a90d568d5c Clarify that sync and ssh only support cmd.exe on Windows
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-05-19 10:10:45 -04:00
a265063fa1 Change resin ssh loglevel from QUIET to ERROR 2016-05-19 16:52:23 +03:00
1c5945d3ae v4.0.3 2016-05-17 10:00:04 -04:00
6062c7a306 Merge pull request #344 from resin-io/feat/ssh-windows-support
Make resin ssh work in Windows cmd.exe
2016-05-09 12:06:31 -04:00
b061644b19 Make resin ssh work in Windows cmd.exe
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-05-06 13:12:55 -04:00
38b97baf02 Add v4.0.2 entry to CHANGELOG
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-27 11:19:35 -04:00
8315bcb7db Improve CHANGELOG v4.0.0 entry prose
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-27 11:19:21 -04:00
e8fa76266f 4.0.2 2016-04-27 01:33:09 +03:00
3db3baeb7c Merge pull request #343 from resin-io/update-resin-sync-version
Update resin sync version to 2.0.2
2016-04-27 01:31:41 +03:00
17550f9bc9 Update resin sync version to 2.0.2 2016-04-27 01:29:45 +03:00
44f80f7a39 v4.0.1 2016-04-26 12:40:20 -04:00
372fa01cf3 Merge pull request #342 from resin-io/fix/unhandled-ssh-exceptions
Return control to capitano at the end of the ssh command
2016-04-26 12:39:01 -04:00
9a515ef4e3 Return control to capitano at the end of the ssh command
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-26 12:37:39 -04:00
6beb6ff44e 4.0.0 2016-04-26 16:17:01 +03:00
c2cd10f6a7 Update changelog 2016-04-26 16:16:02 +03:00
4d3769def8 Merge pull request #337 from resin-io/enter-container-command
Implement resin enter <uuid> command
2016-04-26 16:13:00 +03:00
42bfb3b0cc Implement resin ssh <uuid> command 2016-04-26 16:00:54 +03:00
d9c2717fe4 Merge pull request #340 from resin-io/upgrade-sync-packages
Use resin-sync v2.0.1 and resin-sdk v5.3.0
2016-04-26 15:57:23 +03:00
8e93577f90 Use resin-sync v2.0.1 and resin-sdk v5.3.0 2016-04-26 15:55:21 +03:00
5b7e1b656e Merge pull request #339 from resin-io/feat/infer-or-select-device
Implement a function to attempt to infer a device selection
2016-04-25 14:30:36 -04:00
4a05ce3f53 Attempt to infer the device uuid in resin sync
This PR adds functionality to `resin sync` to try to infer what the
device uuid is as follows:

- If the argument to `resin sync` is an app, get all the devices from
that application. If there is only one, auto-select it, otherwise show
an interactive drive selection widget.

- If the argument to `resin sync` is a uuid, use it directly, without
trying to infer anything.

- If no argument is passed to `resin sync`, display an interactive
selection widget showing all your devices from all your applications.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-25 08:57:19 -04:00
61574a8522 Merge pull request #338 from resin-io/change-sync-description
Change sync description
2016-04-25 08:37:33 -04:00
9400d4027a Update resin-settings-client version 2016-04-25 14:47:15 +03:00
b5ec49dda1 Remove resin sync 'exec after rsync' feature as it's not supported by
ssh gateway
2016-04-25 14:47:05 +03:00
1c66efb4fa v3.0.2 2016-04-08 13:30:40 -04:00
68fca3d030 Merge pull request #336 from resin-io/fix/os-configure-shorter-uuids
Fix shorter uuids not working with the `os configure` command
2016-04-08 13:29:35 -04:00
325304aebe Fix shorter uuids not working with the os configure command
`resin-device-init`, which is used by the `os configure` command was
still running an older SDK version, that didn't support shorter uuids.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-08 13:28:21 -04:00
a50cf5b198 Merge pull request #335 from resin-io/doc/plugin-development
Link to resin-plugin-hello from README
2016-03-31 10:19:48 -04:00
1b7aeeafc1 Link to resin-plugin-hello from README 2016-03-31 10:11:05 -04:00
6e3c2ef168 v3.0.1 2016-03-29 11:30:11 -04:00
d9b4753690 Merge pull request #334 from resin-io/fix/event-explosion
Log CLI events based on original command signature
2016-03-29 11:29:17 -04:00
ca40d7ca65 Log CLI events based on original command signature
Currently we log a CLI event with the passed command, however this might
include usr params, like a uuid, and therefore cause thousands of
different event names in Mixpanel.
2016-03-29 11:27:53 -04:00
4aa8362be9 v3.0.0 2016-03-28 09:29:31 -04:00
d68b61a913 Merge pull request #333 from resin-io/misc/regenerate-docs
Regenerate docs and include sync
2016-03-28 09:26:08 -04:00
20969ef249 Regenerate docs and include sync 2016-03-28 09:25:40 -04:00
469d35fcc1 Merge pull request #332 from resin-io/feat/sync
Integrate sync plugin in the Resin CLI
2016-03-28 09:22:36 -04:00
e9b8c38eeb Integrate sync plugin in the Resin CLI 2016-03-28 09:21:25 -04:00
d4c44bf350 Merge pull request #331 from resin-io/feat/config-generate-application
Allow generating a config.json from an application with config generate
2016-03-21 15:46:52 -04:00
2d8cf7c479 Allow generating a config.json from an application with config generate
Currently, `config generate` requires a device uuid. The command now
accepts either a uuid or an application name, and generates a
config.json accordingly.
2016-03-21 15:42:54 -04:00
ca6e715bfa Merge pull request #330 from resin-io/upgrade/device-config
Upgrade resin-device-config to v3.0.0
2016-03-21 15:05:28 -04:00
3a839c947e Upgrade resin-device-config to v3.0.0 2016-03-21 15:01:59 -04:00
9896de3c34 Merge pull request #329 from resin-io/feat/config-inject
Implement config inject command
2016-03-18 09:13:19 -04:00
03d7520de2 Implement config inject command
This command allows to user to inject a whole `config.json` file to a
provisioned device.
2016-03-17 16:07:19 -04:00
4fc8b130f8 Merge pull request #328 from resin-io/fix/innacurate-invalid-2fa-code
Only throw "Invalid 2FA code" if we're sure that's the cause
2016-03-17 15:38:15 -04:00
c5692f8b13 Only throw "Invalid 2FA code" if we're sure that's the cause
Currently, such error will be thrown when
`resin.auth.twoFactor.challenge()` rejects, but an invalid code is not
the only thing this function can reject for.
2016-03-17 15:37:02 -04:00
85561b5d50 Merge pull request #326 from resin-io/doc/eaccess-login
Document the case where EACCES is thrown during login
2016-03-09 11:04:17 -04:00
88c0833ae2 Document the case where EACCES is thrown during login 2016-03-09 11:03:45 -04:00
7df78a0c9e Merge pull request #325 from resin-io/misc/update
Force update alert to be always shown
2016-03-08 09:26:22 -04:00
30663b0301 Force update alert to be always shown
If `updateCheckInterval` has any meanginful value, the alert will be
shown one out of ten times, or something like that, making the user
likely to miss updates.

The underlying issue is that `update-notifier`, if it detects a cached
update notification, it deletes it, and only attempts to show it back if
`updateCheckInterval` is greater than `Date.now() - lastUpdateCheck`.
2016-03-08 09:23:03 -04:00
4ffba0ed56 Merge pull request #324 from resin-io/upgrade/dependencies
Upgrade most outdated dependencies
2016-03-07 11:42:10 -04:00
a522c70f92 Upgrade most outdated dependencies 2016-03-07 08:44:10 -04:00
153 changed files with 11527 additions and 3264 deletions

View File

@ -9,4 +9,8 @@ trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
trim_trailing_whitespace = false
[package.json]
indent_style = space
indent_size = 2

2
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,2 @@
- **resin-cli version:**
- **Operating system and architecture:**

17
.gitignore vendored
View File

@ -13,9 +13,6 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
@ -27,10 +24,16 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
bin/node
release/build
npm-shrinkwrap.json
package-lock.json
.resinconf
resinrc.yml
.idea
.vscode
.DS_Store
/tmp
build/
build-bin/
build-zip/

View File

@ -1,4 +0,0 @@
tests
doc
lib
extras

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"useTabs": true
}

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: node_js
os:
- linux
- osx
node_js:
- "6"
before_install:
- npm -g install npm@4
script: npm run ci
notifications:
email: false
deploy:
- provider: script
script: npm run release
skip_cleanup: true
on:
tags: true
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
repo: resin-io/resin-cli

View File

@ -1,22 +1,901 @@
# Change Log
All notable changes to this project will be documented in this file.
All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.7.0] - 2016-03-07
## v8.0.0 - 2018-10-19
* Support multicontainer local mode in resin push [Cameron Diver]
* Stop accepting resin-compose.yml as a build composition definition [Cameron Diver]
* Send push source packages as gzipped data [Cameron Diver]
* Respect ignore files when tarring sources [Cameron Diver]
* Check for correct architecture when preloading, instead of correct device type [Alexis Svinartchouk]
* Default preload boolean parameters to false [Alexis Svinartchouk]
* Correctly error out on failed remote builds [Cameron Diver]
* Bump tsconfig target to es6 [Cameron Diver]
## v7.10.9 - 2018-10-18
* Update sdk references in wizzard.coffee [Scott Lowe]
## v7.10.8 - 2018-10-18
* Update sdk references in notes.coffee [Scott Lowe]
* Update sdk references in device.coffee [Scott Lowe]
## v7.10.7 - 2018-10-18
* Update sdk sdk references in auth.coffee [Scott Lowe]
## v7.10.6 - 2018-10-03
* Fix formatting of preload examples [Tim Perry]
## v7.10.5 - 2018-09-25
* README: Fix typo [Lucian Buzzo]
## v7.10.4 - 2018-09-24
* Device: When registering, print the uuid [Pablo Carranza Velez]
## v7.10.3 - 2018-09-19
* Include --emulated in the example resin build parameters [Tim Perry]
## v7.10.2 - 2018-09-18
* Dependencies: Update resin-semver version to support Balena OS [Lucian Buzzo]
## v7.10.1 - 2018-09-11
* Stop Travis deploying to npm (now handled by concourse) [Tim Perry]
## v7.10.0 - 2018-09-11
* Update resin-cli-form to 2.x [Pagan Gazzard]
## v7.9.4 - 2018-09-10
* Device api keys are no longer used in the registration process [Theodor Gherzan]
## v7.9.3 - 2018-08-20
* Fix configuration hangs with some images by expanding the threadpool #952 [Tim Perry]
## v7.9.2 - 2018-08-15
* Add warning about re-enabling automatic updates #946 [Pagan Gazzard]
## v7.9.1 - 2018-08-15
* Fix errors in `getRequestStream` not being propogated #942 [Pagan Gazzard]
## v7.9.0 - 2018-08-09
* Support emulated and nocache options for remote builds #903 [Cameron Diver]
## v7.8.6 - 2018-08-09
* Fix bug where the sudo helper failed in os initialize #939 [Tim Perry]
## v7.8.5 - 2018-08-09
* Update .resin-sync.yml docs for local push and include example env vars #934 [Tim Perry]
## v7.8.4 - 2018-08-02
* Update klaw #936 [Tim Perry]
## v7.8.3 - 2018-07-25
* Follow links found during builds #931 [Tim Perry]
## v7.8.2 - 2018-07-25
* Update reconfix to fix volume signature errors in local configure #929 [Tim Perry]
## v7.8.1 - 2018-07-20
* Be explicit about how much initial history log tailing includes #930 [Tim Perry]
## v7.8.0 - 2018-07-20
* Add join/leave commands to promote and move devices between platforms #895 [Akis Kesoglou]
## v7.7.4 - 2018-07-17
* Update TypeScript to 2.8.1 #923 [Tim Perry]
* Add --version options to os configure & config generate #923 [Tim Perry]
* Update OS & config actions to the latest SDK #923 [Tim Perry]
## v7.7.3 - 2018-07-13
* Update the deploy key since npm invalidated the old one #927 [Tim Perry]
## v7.7.2 - 2018-07-13
* Pin ext2fs to 1.0.7 to avoid temporary deployment issues #926 [Tim Perry]
## v7.7.1 - 2018-07-12
* Update CLI to SDK v10 (include new API logs) #925 [Tim Perry]
## v7.7.0 - 2018-07-11
* Add --generate-device-api-key parameter to config generate #921 [Tim Perry]
## v7.6.2 - 2018-06-28
* Make local commands more resilient to unnamed containers #910 [Tim Perry]
## v7.6.1 - 2018-06-26
* Make sure 'resin push' is included in the docs #907 [Tim Perry]
## v7.6.0 - 2018-06-20
* Support pinned release preloading #896 [Cameron Diver]
## v7.5.2 - 2018-06-12
* Document Pyhton native build dependency #893 [Tim Perry]
## v7.5.1 - 2018-06-01
* Add a multicontainer caveat to the env var commands #887 [Tim Perry]
## v7.5.0 - 2018-05-31
* Update resin-compose-parse dependency version to 1.10.2 #883 [Ariel Flesler]
## v7.4.1 - 2018-05-24
* Update SDK in resin device(s) to ensure the dashboard URL is correct #879 [Tim Perry]
## v7.4.0 - 2018-05-10
* Add push command which starts a build on remote resin servers #868 [Cameron Diver]
## v7.3.8 - 2018-05-03
* Catch require errors and provide helpful instructions #874 [Tim Perry]
* Inline the entire resin-cli-errors module #874 [Tim Perry]
## v7.3.7 - 2018-04-30
* Pin node types to v9.0.0 to avoid build errors with transient dependencies #871 [Cameron Diver]
## v7.3.6 - 2018-04-30
* Update resin-image-fs to stop non-config commands failing in node 10 #870 [Tim Perry]
## v7.3.5 - 2018-04-18
* Don't show Docker container status from devices, as it can be wrong #858 [Tim Perry]
## v7.3.4 - 2018-04-18
* Include resin compose schemas in the standalone build #865 [Tim Perry]
## v7.3.3 - 2018-04-17
* Don't report lots of user input errors #861 [Tim Perry]
* Include Sentry breadcrumbs for context in error reports #861 [Tim Perry]
* Update to Sentry 2.x #861 [Tim Perry]
## v7.3.2 - 2018-04-16
* Update Dockerode to fix local push issue in standalone builds #862 [Tim Perry]
## v7.3.1 - 2018-04-13
* Update resin-compose-parse to v1.8.1 to fix a problem parsing ports #849 [Pablo Carranza Velez]
## v7.3.0 - 2018-04-12
* Add 'api-key generate' command #854 [Tim Perry]
## v7.2.4 - 2018-04-10
* Explicitly depend on tar-stream #852 [Tim Perry]
* Correct documented node version requirement to 6+ #852 [Tim Perry]
## v7.2.3 - 2018-04-06
* Add a fast build script to package.json #847 [Cameron Diver]
## v7.2.2 - 2018-04-04
* Throw a clear error when logging in with an invalid token #846 [Tim Perry]
## v7.2.1 - 2018-03-29
* Update docker-qemu-transpose to avoid the broken 0.4.1 release #839 [Tim Perry]
## v7.2.0 - 2018-03-29
* Do not require a login for builds #835 [Tim Perry]
* Allow (experimental!) login with API keys #835 [Tim Perry]
## v7.1.6 - 2018-03-29
* Fix build emulation for multi-stage builds #838 [Tim Perry]
## v7.1.5 - 2018-03-27
* Fix crash when an app is not specified for build command #834 [Akis Kesoglou]
## v7.1.4 - 2018-03-26
* Upgrade resin-sync to pull in the fix for #824 #832 [Tim Perry]
## v7.1.3 - 2018-03-23
* Prefix all pine options with '$' in preload to avoid pine warnings. #831 [Alexis Svinartchouk]
## v7.1.2 - 2018-03-23
* Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16 #830 [Alexis Svinartchouk]
## v7.1.1 - 2018-03-22
* Remove explicit anchor links in CLI docs #827 [Zach Walchuk]
## v7.1.0 - 2018-03-22
* Warn early if deploying a multicontainer project to an incompatible app #818 [Akis Kesoglou]
* Add legacy deploy method back #818 [Akis Kesoglou]
## v7.0.7 - 2018-03-20
* Update resin-preload to 6.1.2 #821 [Alexis Svinartchouk]
## v7.0.6 - 2018-03-20
* Make sure image name is all lowercase #815 [Akis Kesoglou]
* Improve handling of build log output #815 [Akis Kesoglou]
## v7.0.5 - 2018-03-15
* Add bash completions #801 [Ronald McCollam]
## v7.0.4 - 2018-03-15
* Generate consistent working anchors for both our md output & resin docs #813 [Tim Perry]
## v7.0.3 - 2018-03-15
* Fix getting window size when theres no TTY attached #812 [Akis Kesoglou]
## v7.0.2 - 2018-03-13
* Update full CLI docs with recent installation improvements too #807 [Tim Perry]
## v7.0.1 - 2018-03-12
* Recommend using unsafe-prem to avoid permission issues on install #805 [Tim Perry]
* Remove unnecessary resin-cli-auth dependency #805 [Tim Perry]
## v7.0.0 - 2018-03-09
* Add docker-compose-aware builds and deployments #792 [Akis Kesoglou]
* *BREAKING*: Remove support for plugins entirely #792 [Tim Perry]
* Update dashboard login to use the multicontainer SDK #792 [Alexis Svinartchouk]
* Multicontainer preload: Update resin-preload to 6.0.0-beta4 #792 [Alexis Svinartchouk]
* Update the keys action to use the multicontainer SDK #792 [Alexis Svinartchouk]
* Require multicontainer SDK #792 [Alexis Svinartchouk]
## v6.13.5 - 2018-03-07
* Fix prettier configuration to avoid linting errors #802 [Tim Perry]
## v6.13.4 - 2018-02-22
* Fix issue where emulated builds broke Docker `ENV` commands #796 [Gergely Imreh]
## v6.13.3 - 2018-02-20
* Tweak TS & add missing deps that may cause build failures in some envs #793 [Tim Perry]
## v6.13.2 - 2018-02-20
* Ensure login does not wait for the browser process to close #794 [Tim Perry]
## v6.13.1 - 2018-02-07
* Move to the correct coffeescript (no hyphen) dependency #786 [Tim Perry]
* Add typings for 'ent' #786 [Tim Perry]
## v6.13.0 - 2018-02-06
* Add support for Balena in local ssh #777 [Tim Perry]
## v6.12.9 - 2018-02-05
* Switch back to upstream global-tunnel-ng #781 [Alexis Svinartchouk]
## v6.12.8 - 2018-02-03
* Fix uuid params being parsed a numbers #774 [Thodoris Greasidis]
## v6.12.7 - 2018-01-30
* Add 'or mounted resinOS image' #767 [MoranF]
## v6.12.6 - 2018-01-29
* Don't use the deprecated 'os configure' format in internal calls #759 [Tim Perry]
## v6.12.5 - 2018-01-11
* Fix breakage in deploy command from recent TS conversion #754 [Tim Perry]
## v6.12.4 - 2018-01-10
* Start using Prettier #753 [Tim Perry]
## v6.12.3 - 2018-01-09
* Lint TypeScript and CoffeeScript with resin-lint #743 [Tim Perry]
* Move documentation generation to TypeScript #743 [Tim Perry]
* Convert most of utils to TypeScript #743 [Tim Perry]
## v6.12.2 - 2018-01-09
* Convert windows paths to posix when passing to tar #748 [Andrew Shirley]
## v6.12.1 - 2018-01-02
* Fix deprecation warning for os configure, when passing a bare UUID #744 [Tim Perry]
## v6.12.0 - 2017-12-19
* Add ssh option for direct host OS access #737 [Andreas Fitzek]
## v6.11.0 - 2017-12-18
* Fix docs generation when building on windows #729 [Tim Perry]
* Autodeploy built standalone binaries for all platforms to github #729 [Tim Perry]
* Package the CLI into a standalone runnable binary #729 [Tim Perry]
* Move from open to opn #729 [Tim Perry]
## v6.10.3 - 2017-12-15
* Ensure logout works even with invalid credentials, or if not logged in #730 [Tim Perry]
## v6.10.2 - 2017-11-27
* Inline the entire resin-cli-auth module #721 [Tim Perry]
## v6.10.1 - 2017-11-27
* Set up TypeScript compilation, and make a small start on converting the CLI #720 [Tim Perry]
* Don't commit raw JS build output #720 [Tim Perry]
## v6.10.0 - 2017-11-17
* Allow `os configure` to configure for an app, not just a specific device #718 [Tim Perry]
* Print help even for expected errors #718 [Tim Perry]
## v6.9.0 - 2017-11-16
* Allow non-interactice config generate for simple network settings #714 [Tim Perry]
* Fix issue where network settings were not used by `config generate` #714 [Tim Perry]
## v6.8.3 - 2017-11-16
* Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates #717 [Tim Perry]
## v6.8.2 - 2017-11-14
* Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3 #713 [Tim Perry]
## v6.8.1 - 2017-11-09
* Avoid AmbiguousApplication errors in device register when an id is used #711 [Tim Perry]
## v6.8.0 - 2017-10-27
* Allow preloading jetson-tx2 images, improve flasher images detection and remove the --dont-detect-flasher-type-images option. #706 [Alexis Svinartchouk]
## v6.7.4 - 2017-10-25
* Add preload to the CLI docs #702 [Tim Perry]
## v6.7.3 - 2017-10-25
* Allow specifying `--commit=latest` for `resin preload` #701 [Alexis Svinartchouk]
## v6.7.2 - 2017-10-24
* Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER, by updating it #699 [Tim Perry]
## v6.7.1 - 2017-10-24
* Respect the -dont-check-device-type option, fix error message #697 [Alexis Svinartchouk]
## v6.7.0 - 2017-10-18
* Added a device api key parameter to the `os configure` command. #487 [Pagan Gazzard]
* Added a `--device-api-key` option to the `config generate` command. #487 [Pagan Gazzard]
* Added a `--device-api-key` option to the `device register` command. #487 [Pagan Gazzard]
## v6.6.13 - 2017-10-18
* Fix issue where `os download` would always download prod images #689 [Tim Perry]
## v6.6.12 - 2017-10-16
* Update resin-preload to 4.0.2 to support preloading Edison images #687 [Alexis Svinartchouk]
## v6.6.11 - 2017-10-13
* Document how to `resin deploy` to an app as a collaborator #685 [Tim Perry]
## v6.6.10 - 2017-10-09
* Ensure hostname truly is optional when configuring device images #676 [Tim Perry]
## v6.6.9 - 2017-10-06
* Fix resin preload --splash-image argument handling #678 [Alexis Svinartchouk]
## v6.6.8 - 2017-10-06
* Ensure analytics failures (e.g. from broken tokens) at startup don't break commands #675 [Tim Perry]
## v6.6.7 - 2017-09-22
* Update to resin-sync, which fixes local push on windows #666 [Tim Perry]
* Add windows instructions to fix node-gyp installs #666 [Tim Perry]
## v6.6.6 - 2017-09-11
* Create ISSUE_TEMPLATE.md #655 [Kostas Lekkas]
## v6.6.5 - 2017-08-31
* Fix lodash bugs in device move & quickstart #643 [Tim Perry]
## v6.6.4 - 2017-08-31
* Catch uncommitted build output automatically in Travis #644 [Tim Perry]
## v6.6.3 - 2017-08-31
* Update README to link to the full CLI command documentation #641 [Tim Perry]
## v6.6.2 - 2017-08-31
* Use DOCKER_HOST from env if possible, and no connection options are available #642 [Tim Perry]
## v6.6.1 - 2017-08-28
* Update resin-preload to 3.1.4 #650 [Alexis Svinartchouk]
## v6.6.0 - 2017-08-28
* Add a --dont-check-device-type option for `resin preload` #647 [Alexis Svinartchouk]
## v6.5.3 - 2017-08-24
* Remove resin-preload build filtering workaround. #645 [Alexis Svinartchouk]
## v6.5.2 - 2017-08-22
### Changed
- Progress bar when preload builds its docker image or fetches the build size;
- Preload will do nothing if you try to preload a build that is already preloaded in the image.
## v6.5.1 - 2017-08-21
### Fixed
- Fix lodash upgrade bug that broke `resin help`
## v6.5.0 - 2017-08-18
### Changed
- Set up Travis npm autodeploy
- Upgrade to Lodash v4, drastically reducing install size (due to dedupes)
- Updated npm package description
- Added support for looking up shared apps via [owner]/[appname] strings
### Added
- Use forked global-tunnel-ng that doesn't proxy connections to socket files
- Preload support
## v6.4.0 - 2017-08-11
### Changed
- Support overlay2 in resin local push
- Support Docker versions greater than 1.X in resin sync
- Provide a helpful warning when Docker is not installed (or is unaccessible)
- Added a link to the Node download page in the warning for users with old Node versions
- Remove inconsistent (and now unneccesary) 'Tagging image as' message from local build output
## v6.3.1 - 2017-08-08
- Updated resin-cli-auth to point to the correct resin dashboard ued for authentication (for example, staging)
## v6.3.0 - 2017-08-03
### Fixed
- Fixed compatibility of `resin local push` with docker >= 1.12
## v6.2.0 - 2017-07-27
### Changed
- Support the new resinOS versions where the sample connection file is called `resin-sample.ignore`
## v6.1.1 - 2017-07-18
### Changed
- Hide the intro quickstart message for now (until it gets renovated)
## v6.1.0 - 2017-06-30
### Fixed
- Fix issue where emulated builds broke Docker `ARG` commands
- Fix issue when using resin deploy with non-standard stdin (e.g. git bash on windows)
### Added
- Bump resin-sync@8.0.1
- Permit resin sync to collaborators
- Fix "'cwd' must be a string" error in Node 8
- Do not explicitly disable ControlMaster option for device SSH connections
## v6.0.0 - 2017-06-26
### Added
- Added support for `--squash` parameter for `resin build`
### Fixed
- **Breaking** Remove Buffer polyfill, require Node v6+, and print warnings in older versions
## v5.11.1 - 2017-06-22
### Added
- Include Node version in Sentry logging
### Fixed
- Ensure to send .pem file contents rather than filename to docker daemon
- Add a polyfill to fix `local configure` in older (<6) Node versions
## v5.11.0 - 2017-06-19
### Added
- `package-lock.json` for `npm5` users
- Added ability to run an emulated build silently with `resin build`
- Gzip images when uploading in `resin deploy`
- Show a clear message immediately as the deploy starts, if we're deploying an image.
- Forced update to the newest resin-sdk (^6.4.1)
- Allow OS version selection when doing `resin device init`
- Actually tolerate the `--yes` param to `resin device init`
- Allows passing `--drive` to `resin device init`
- List detected drives with `resin util available-drives`
- Add the `resin os build-config` method to pass the interactive config step once
and reuse the built file for subsequent `resin os configure` calls (added the new `--config` param to it),
and for `resin device init` (same `--config` param)
- Improve the supported device types listing
### Fixed
- Ensure emulated builds use the correct relative path to qemu when called from any location
- Make emulated builds reliable in the presence for WORKDIR comands
## v5.10.2 - 2017-05-31
### Fixed
- Fixed command line arguments for `resin build`
## v5.10.1 - 2017-05-22
### Fixed
- Fixed breaking bug in `resin local ssh`
## v5.10.0 - 2017-05-22
### Added
- Reduce granularity of update checking to one day
- Include extra usage metadata in error logging to help debugging
- Add uploading of build logs when present with resin deploy
- Highlight cache usage in a local build
- Show a progress bar for upload progress
- Add ability to specify build-time variables for local builds
- Added the proxy support for the `resin ssh` command
### Fixed
- Fixed the not enough unicorns bug in resin build
- Removed the install-time warning for the `valid-email` package
## v5.9.1 - 2017-05-01
### Fixed
- Technical release because v5.9.0 was published earlier (erroneously)
## v5.9.0 - 2017-05-01
### Added
- HTTP(S) proxy support
## v5.8.1 - 2017-04-27
### Fixed
- The `ssh` command was broken
## v5.8.0 - 2017-04-26
### Added
- Add cloud builder output to local build
- Add nocache and tag options to resin deploy
- Add ability to build and deploy an image to resin's infrastructure
### Fixed
- Capture and report errors happening during the program initialization, like parsing invalid YAML config
## v5.7.2 - 2017-04-18
### Fixed
- Fixed warning on NPM install due to dependency conflicts
- Improved node v4 support (some operations are fixed, some will run faster)
## v5.7.1 - 2017-04-03
### Fixed
- Add basic support for the new ResinOS version format
## v5.7.0 - 2017-03-28
### Fixed
- The OS init issues:
* failing to get the superuser resin auth (tried to look into `/root/.resin`)
* failing to write to the drive
## v5.6.1 - 2017-03-23
### Added
- Add Sentry error tracking
### Fixed
- The unneeded warning about the default OS version download from the `device init` command.
- Changed the help references from gitter to forums.
## v5.6.0 - 2017-03-23
### Added
- The `--version` option to the `os download` command. Check `resin help os download` for details.
## v5.5.0 - 2017-03-10
### Added
- Require superuser for scan commands, also introduce docker timeout
- Bump resin-sync@7.0.0: use experimental rds which requires superuser permissions
## v5.4.0 - 2017-03-09
### Added
- Implement 'resin local stop'
- Implement 'resin local'
- Implement 'resin local push'
- Implement 'resin local ssh'
- Implement 'resin local scan'
- Implement 'resin local promote'
- Implement 'resin local logs'
- Implement 'resin local flash'
- Implement 'resin local configure'
### Changed
- Remove app create from primary commands
## v5.3.0 - 2017-03-03
### Added
- `resin sync` AUFS device support
### Changed
- Moved to the new version of `resin-sdk` (via `resin-sdk-preconfigured`)
## v5.2.4 - 2017-01-18
### Changed
- Fix documented requirements for resin ssh and resin sync
## v5.2.3 - 2017-01-04
### Changed
- Add missing `js-yaml` dependency.
## v5.2.2 - 2016-11-01
### Changed
- Fix `shutdown` command not being available.
## v5.2.1 - 2016-10-28
### Changed
- Fix `Boolean options can't have parameters` error in every command.
## v5.2.0 - 2016-10-27
### Added
- Add `shutdown` command.
- Add `--force` option to `device reboot` command.
## v5.1.0 - 2016-09-25
### Added
- Add `devices supported` command.
## v5.0.0 - 2016-09-15
### Added
- Automatically parse '.gitignore' for file inclusions/exclusions from resin sync by default. Skip parsing with `--skip-gitignore`.
- Automatically save options to `<sourceDirectory>/.resin-sync.yml` after every run.
- Support user-specified destination directories with `--destination/-d` option.
- Implement `--after` option to perform actions local (e.g. cleanup) after resin sync has finished.
- Implement interactive dialog for destination directory, with `/usr/src/app` being the default choice.
### Changed
- Require `resin sync` `--source/-s` option if a `.resin-sync.yml` file is not found in the current directory.
- Require `uuid` as an argument in `resin sync/ssh` (`appName` has been removed).
- Always display interactive device selection dialog when uuid is not passed as an argument.
- Disable ControlMaster ssh option (as reported in support).
## v4.5.0 - 2016-09-14
### Added
- Attempt to retrieve device type from the image's first partition.
## v4.4.0 - 2016-08-11
### Changed
- Display OS and Supervisor version in `devices` and `device` commands.
## v4.3.0 - 2016-08-11
### Added
- Implement `device public-url enable` command.
- Implement `device public-url disable` command.
- Implement `device public-url status` command.
- Implement `device public-url` command.
- Add global `--help` option.
## v4.2.1 - 2016-07-26
### Changed
- Fix log messages being `undefined`.
## v4.2.0 - 2016-06-22
### Added
- Add `verbose` option to `resin sync`.
## v4.1.0 - 2016-06-22
### Added
- Add `verbose` option to `resin ssh`.
## v4.0.3 - 2016-05-17
### Changed
- Fix `resin ssh` errors when running in `cmd.exe`.
## v4.0.2 - 2016-04-27
### Changed
- Upgrade `resin-sync` to v2.0.2.
## v4.0.1 - 2016-04-26
### Changed
- Fix unhandled exceptions in the `resin ssh` command.
## v4.0.0 - 2016-04-26
### Added
- Implement `resin ssh` command
### Changed
- Remove `resin sync` `exec` option.
- Upgrade `resin-sync` to v2.0.1.
- Upgrade `resin-sdk` to v5.3.0.
## v3.0.2 - 2016-04-08
### Changed
- Fix `os configure` command not working with shorter uuids.
## v3.0.1 - 2016-03-29
### Changed
- Log Mixpanel events based on the matching command signature.
## v3.0.0 - 2016-03-28
### Added
- Implement `config inject` command.
- Document the case where `EACCES` is thrown during login because of an expired token.
- Integrate `resin-plugin-sync` as a build-in command.
### Changed
- Allow `config generate` to generate a `config.json` for an application.
- Force update alert to always be shown.
- Only throw "Invalid 2FA code" if we're sure that's the cause during `login`.
## v2.7.0 - 2016-03-07
### Added
- Implement `config generate` command.
- Implement `device reboot` command.
## [2.6.2] - 2016-02-19
## v2.6.2 - 2016-02-19
### Removed
- Remove debugging statement in `quickstart`.
## [2.6.1] - 2016-02-12
## v2.6.1 - 2016-02-12
### Added
@ -31,7 +910,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Improve `quickstart` messages.
- Fix `device` example.
## [2.6.0] - 2016-01-21
## v2.6.0 - 2016-01-21
### Added
@ -55,7 +934,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Upgrade Resin Image Manager to v3.2.6.
- Make `devices` output shorter uuids.
## [2.5.0] - 2015-12-11
## v2.5.0 - 2015-12-11
### Added
@ -67,7 +946,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Lazy load command actions dependencies for performance reasons.
## [2.4.0] - 2015-12-01
## v2.4.0 - 2015-12-01
### Added
@ -78,7 +957,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Simplify download output messages.
## [2.3.0] - 2015-11-20
## v2.3.0 - 2015-11-20
### Added
@ -90,13 +969,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Show uuids in `devices` command.
- Clarify resin url in `login` and `whoami`.
## [2.2.0] - 2015-11-12
## v2.2.0 - 2015-11-12
### Added
- Implement `device move` command.
## [2.1.0] - 2015-11-11
## v2.1.0 - 2015-11-11
### Added
@ -107,13 +986,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Clarify the need of computer password during `sudo` in `os initialize`.
## [2.0.1] - 2015-10-26
## v2.0.1 - 2015-10-26
### Changed
- Fix critical error when elevating permissions.
## [2.0.0] - 2015-10-26
## v2.0.0 - 2015-10-26
### Added
@ -139,7 +1018,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Remove project directory creation logic in `device init`.
- Remove `app associate` command.
## [1.1.0] - 2015-10-13
## v1.1.0 - 2015-10-13
### Added
@ -172,16 +1051,3 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Removed
- Remove outdated information from README.
[2.7.0]: https://github.com/resin-io/resin-cli/compare/v2.6.2...v2.7.0
[2.6.2]: https://github.com/resin-io/resin-cli/compare/v2.6.1...v2.6.2
[2.6.1]: https://github.com/resin-io/resin-cli/compare/v2.6.0...v2.6.1
[2.6.0]: https://github.com/resin-io/resin-cli/compare/v2.5.0...v2.6.0
[2.5.0]: https://github.com/resin-io/resin-cli/compare/v2.4.0...v2.5.0
[2.4.0]: https://github.com/resin-io/resin-cli/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/resin-io/resin-cli/compare/v2.2.0...v2.3.0
[2.2.0]: https://github.com/resin-io/resin-cli/compare/v2.1.0...v2.2.0
[2.1.0]: https://github.com/resin-io/resin-cli/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/resin-io/resin-cli/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/resin-io/resin-cli/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/resin-io/resin-cli/compare/v1.0.0...v1.1.0

View File

@ -1,7 +1,7 @@
Resin CLI
=========
> The official Resin CLI tool.
> The official resin.io CLI tool.
[![npm version](https://badge.fury.io/js/resin-cli.svg)](http://badge.fury.io/js/resin-cli)
[![dependencies](https://david-dm.org/resin-io/resin-cli.svg)](https://david-dm.org/resin-io/resin-cli)
@ -10,31 +10,85 @@ Resin CLI
Requisites
----------
- [NodeJS](https://nodejs.org) (at least v0.10)
If you want to install the CLI directly through npm, you'll need the below. If this looks difficult,
we do now have an experimental standalone binary release available, see ['Standalone install'](#standalone-install) below.
- [NodeJS](https://nodejs.org) (>= v6)
- [Git](https://git-scm.com)
- The following executables should be correctly installed in your shell environment:
- `ssh`: Any recent version of the OpenSSH ssh client (required by `resin sync` and `resin ssh`)
- if you need `ssh` to work behind the proxy you also need [`proxytunnel`](http://proxytunnel.sourceforge.net/) installed (available as `proxytunnel` package for Ubuntu, for example)
- `rsync`: >= 2.6.9 (required by `resin sync`)
##### Windows Support
Before installing resin-cli, you'll need a working node-gyp environment. If you don't already have one you'll see native module build errors during installation. To fix this, run `npm install -g --production windows-build-tools` in an administrator console (available as 'Command Prompt (Admin)' when pressing windows+x in Windows 7+).
`resin sync` and `resin ssh` have not been thoroughly tested on the standard Windows cmd.exe shell. We recommend using bash (or a similar) shell, like Bash for Windows 10 or [Git for Windows](https://git-for-windows.github.io/).
If you still want to use `cmd.exe` you will have to use a package manager like MinGW or chocolatey. For MinGW the steps are:
1. Install [MinGW](http://www.mingw.org).
2. Install the `msys-rsync` and `msys-openssh` packages.
3. Add MinGW to the `%PATH%` if this hasn't been done by the installer already. The location where the binaries are places is usually `C:\MinGW\msys\1.0\bin`, but it can vary if you selected a different location in the installer.
4. Copy your SSH keys to `%homedrive%%homepath\.ssh`.
5. If you need `ssh` to work behind the proxy you also need to install [proxytunnel](http://proxytunnel.sourceforge.net/)
Getting Started
---------------
### Installing
### NPM install
If you've got all the requirements above, you should be able to install the CLI directly from npm. If not,
or if you have any trouble with this, please try the new standalone install steps just below.
This might require elevated privileges in some environments.
```sh
$ npm install --global --production resin-cli
$ npm install resin-cli -g --production --unsafe-perm
```
### List available commands
`--unsafe-perm` is only required on systems where the global install directory is not user-writable.
This allows npm install steps to download and save prebuilt native binaries. You may be able to omit it,
especially if you're using a user-managed node install such as [nvm](https://github.com/creationix/nvm).
In some environments, this process will need to build native modules. This may require a more complex build
environment, and notably requires Python 2.7. If you hit any problems with this, we recommend you try the
alternative standalone install below instead.
### Standalone install
If you don't have node or a working pre-gyp environment, you can still install the CLI as a standalone
binary. **This is experimental and may not work perfectly yet in all environments**, but it seems to work
well in initial cross-platform testing, so it may be useful, and we'd love your feedback if you hit any issues.
To install the CLI as a standalone binary:
* Download the latest zip for your OS from https://github.com/resin-io/resin-cli/releases.
* Extract the contents, putting the `resin-cli` folder somewhere appropriate for your system (e.g. `C:/resin-cli`, `/usr/local/lib/resin-cli`, etc).
* Add the `resin-cli` folder to your `PATH` ([Windows instructions](https://www.computerhope.com/issues/ch000549.htm), [Linux instructions](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix), [OSX instructions](https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently))
* Running `resin` in a fresh command line should print the resin CLI help.
To update in future, simply download a new release and replace the extracted folder.
Have any problems, or see any unexpected behaviour? [Please file an issue!](https://github.com/resin-io/resin-cli/issues/new)
### Login
```sh
$ resin help
$ resin login
```
### Run the quickstart wizard
_(Typically useful, but not strictly required for all commands)_
```sh
$ resin quickstart
```
### Run commands
Take a look at the full command documentation at [https://docs.resin.io/tools/cli/](https://docs.resin.io/tools/cli/#table-of-contents
), or by running `resin help`.
### Bash completions
Optionally you can enable tab completions for the bash shell, enabling the shell to provide additional context and automatically complete arguments to`resin`. To enable bash completions, copy the `resin-completion.bash` file to the default bash completions directory (usually `/etc/bash_completion.d/`) or append it to the end of `~/.bash_completion`.
FAQ
---
@ -55,16 +109,16 @@ Alternatively, you can edit your configuration file and set `resinUrl: resinstag
The Resin CLI persists your session token, as well as cached images in `$HOME/.resin` or `%UserProfile%\_resin`.
Pointing the Resin CLI to persist data in another location is necessary in certain environments, like a server, where there is no home directory, or a device running Resin OS, which erases all data after a restart.
Pointing the Resin CLI to persist data in another location is necessary in certain environments, like a server, where there is no home directory, or a device running resinOS, which erases all data after a restart.
You can accomplish this by setting `RESINRC_DATA_DIRECTORY=/opt/resin` or adding `dataDirectory: /opt/resin` to your configuration file, replacing `/opt/resin` with your desired directory.
Support
-------
If you're having any problem, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the Resin.io team will be happy to help.
If you're having any problems, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the resin.io team will be happy to help.
You can also get in touch with us at our public [Gitter chat channel](https://gitter.im/resin-io/chat).
You can also get in touch with us in the resin.io [forums](https://forums.resin.io/).
License
-------

View File

@ -47,3 +47,13 @@ Or in Windows:
```sh
> del /s /q %UserProfile%\_resin\cache
```
### I get `EACCES: permission denied` when logging in
The Resin CLI stores the session token in `$HOME/.resin` or `C:\Users\<user>\_resin` in UNIX based operating systems and Windows respectively. This error usually indicates that the user doesn't have permissions over that directory, which can happen if you ran the Resin CLI as `root`, and thus the directory got owned by him.
Try resetting the ownership by running:
```sh
$ sudo chown -R <user> $HOME/.resin
```

34
appveyor.yml Normal file
View File

@ -0,0 +1,34 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
init:
- git config --global core.autocrlf input
cache:
- C:\Users\appveyor\.node-gyp
- '%AppData%\npm-cache'
matrix:
fast_finish: true
# what combinations to test
environment:
matrix:
- nodejs_version: 6
install:
- ps: Install-Product node $env:nodejs_version x64
- npm install -g npm@4
- set PATH=%APPDATA%\npm;%PATH%
- npm install
build: off
test_script:
- node --version
- npm --version
- cmd: npm test
deploy_script:
- IF "%APPVEYOR_REPO_TAG%" == "true" (npm run release)
- IF NOT "%APPVEYOR_REPO_TAG%" == "true" (echo 'Not tagged, skipping deploy')

36
automation/build-bin.ts Executable file
View File

@ -0,0 +1,36 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import * as filehound from 'filehound';
import { exec as execPkg } from 'pkg';
const ROOT = path.join(__dirname, '..');
console.log('Building package...\n');
execPkg(['--target', 'host', '--output', 'build-bin/resin', 'package.json'])
.then(() =>
fs.copy(
path.join(ROOT, 'node_modules', 'opn', 'xdg-open'),
path.join(ROOT, 'build-bin', 'xdg-open'),
),
)
.then(() => {
return filehound
.create()
.paths(path.join(ROOT, 'node_modules'))
.ext(['node', 'dll'])
.find();
})
.then(nativeExtensions => {
console.log(`\nCopying to build-bin:\n${nativeExtensions.join('\n')}`);
return nativeExtensions.map(extPath => {
return fs.copy(
extPath,
extPath.replace(
path.join(ROOT, 'node_modules'),
path.join(ROOT, 'build-bin'),
),
);
});
});

14
automation/capitanodoc/doc-types.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import { CommandDefinition } from 'capitano';
export interface Document {
title: string;
introduction: string;
categories: Category[];
}
export interface Category {
title: string;
commands: CommandDefinition[];
}
export { CommandDefinition as Command };

View File

@ -0,0 +1,34 @@
import capitanodoc = require('../../capitanodoc');
import * as _ from 'lodash';
import * as path from 'path';
import * as markdown from './markdown';
import { Document, Category } from './doc-types';
const result = <Document>{};
result.title = capitanodoc.title;
result.introduction = capitanodoc.introduction;
result.categories = [];
for (let commandCategory of capitanodoc.categories) {
const category = <Category>{};
category.title = commandCategory.title;
category.commands = [];
for (let file of commandCategory.files) {
// tslint:disable-next-line:no-var-requires
const actions: any = require(path.join(process.cwd(), file));
if (actions.signature) {
category.commands.push(_.omit(actions, 'action'));
} else {
for (let actionName of Object.keys(actions)) {
const actionCommand = actions[actionName];
category.commands.push(_.omit(actionCommand, 'action'));
}
}
}
result.categories.push(category);
}
console.log(markdown.render(result));

View File

@ -0,0 +1,76 @@
import * as _ from 'lodash';
import * as ent from 'ent';
import * as utils from './utils';
import { Document, Category, Command } from './doc-types';
export function renderCommand(command: Command) {
let result = `## ${ent.encode(command.signature)}\n\n${command.help}\n`;
if (!_.isEmpty(command.options)) {
result += '\n### Options';
for (let option of command.options!) {
result += `\n\n#### ${utils.parseSignature(option)}\n\n${
option.description
}`;
}
result += '\n';
}
return result;
}
export function renderCategory(category: Category) {
let result = `# ${category.title}\n`;
for (let command of category.commands) {
result += `\n${renderCommand(command)}`;
}
return result;
}
function getAnchor(command: Command) {
return (
'#' +
command.signature
.replace(/\s/g, '-')
.replace(/</g, '-')
.replace(/>/g, '-')
.replace(/\[/g, '-')
.replace(/\]/g, '-')
.replace(/-+/g, '-')
.replace(/\.\.\./g, '')
.replace(/\|/g, '')
.toLowerCase()
);
}
export function renderToc(categories: Category[]) {
let result = `# Table of contents\n`;
for (let category of categories) {
result += `\n- ${category.title}\n\n`;
for (let command of category.commands) {
result += `\t- [${ent.encode(command.signature)}](${getAnchor(
command,
)})\n`;
}
}
return result;
}
export function render(doc: Document) {
let result = `# ${doc.title}\n\n${doc.introduction}\n\n${renderToc(
doc.categories,
)}`;
for (let category of doc.categories) {
result += `\n${renderCategory(category)}`;
}
return result;
}

View File

@ -0,0 +1,33 @@
import { OptionDefinition } from 'capitano';
import * as _ from 'lodash';
import * as ent from 'ent';
export function getOptionPrefix(signature: string) {
if (signature.length > 1) {
return '--';
} else {
return '-';
}
}
export function getOptionSignature(signature: string) {
return `${getOptionPrefix(signature)}${signature}`;
}
export function parseSignature(option: OptionDefinition) {
let result = getOptionSignature(option.signature);
if (_.isArray(option.alias)) {
for (let alias of option.alias) {
result += `, ${getOptionSignature(alias)}`;
}
} else if (_.isString(option.alias)) {
result += `, ${getOptionSignature(option.alias)}`;
}
if (option.parameter) {
result += ` <${option.parameter}>`;
}
return ent.encode(result);
}

38
automation/custom-types.d.ts vendored Normal file
View File

@ -0,0 +1,38 @@
declare module 'pkg' {
export function exec(args: string[]): Promise<void>;
}
declare module 'filehound' {
export function create(): FileHound;
export interface FileHound {
paths(paths: string[]): FileHound;
paths(...paths: string[]): FileHound;
ext(extensions: string[]): FileHound;
ext(...extensions: string[]): FileHound;
find(): Promise<string[]>;
}
}
declare module 'publish-release' {
interface PublishOptions {
token: string;
owner: string;
repo: string;
tag: string;
name: string;
reuseRelease?: boolean;
assets: string[];
}
interface Release {
html_url: string;
}
let publishRelease: (
args: PublishOptions,
callback: (e: Error, release: Release) => void,
) => void;
export = publishRelease;
}

67
automation/deploy-bin.ts Normal file
View File

@ -0,0 +1,67 @@
import * as Promise from 'bluebird';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs-extra';
import * as mkdirp from 'mkdirp';
import * as publishRelease from 'publish-release';
import * as archiver from 'archiver';
import * as packageJSON from '../package.json';
const publishReleaseAsync = Promise.promisify(publishRelease);
const mkdirpAsync = Promise.promisify<string | null, string>(mkdirp);
const { GITHUB_TOKEN } = process.env;
const ROOT = path.join(__dirname, '..');
const version = 'v' + packageJSON.version;
const outputFile = path.join(
ROOT,
'build-zip',
`resin-cli-${version}-${os.platform()}-${os.arch()}.zip`,
);
mkdirpAsync(path.dirname(outputFile))
.then(
() =>
new Promise((resolve, reject) => {
console.log('Zipping build...');
let archive = archiver('zip', {
zlib: { level: 7 },
});
archive.directory(path.join(ROOT, 'build-bin'), 'resin-cli');
let outputStream = fs.createWriteStream(outputFile);
outputStream.on('close', resolve);
outputStream.on('error', reject);
archive.on('error', reject);
archive.on('warning', console.warn);
archive.pipe(outputStream);
archive.finalize();
}),
)
.then(() => {
console.log('Build zipped');
console.log('Publishing build...');
return publishReleaseAsync({
token: <string>GITHUB_TOKEN,
owner: 'resin-io',
repo: 'resin-cli',
tag: version,
name: `Resin-CLI ${version}`,
reuseRelease: true,
assets: [outputFile],
});
})
.then(release => {
console.log(`Release ${version} successful: ${release.html_url}`);
})
.catch(err => {
console.error('Release failed');
console.error(err);
process.exit(1);
});

16
automation/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": true,
"sourceMap": true
},
"include": [
"./**/*.ts",
"../typings/*.d.ts"
]
}

View File

@ -1,2 +1,7 @@
#!/usr/bin/env node
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
require('../build/app');

View File

@ -1,115 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var commandOptions;
commandOptions = require('./command-options');
exports.create = {
signature: 'app create <name>',
description: 'create an application',
help: 'Use this command to create a new resin.io application.\n\nYou can specify the application type with the `--type` option.\nOtherwise, an interactive dropdown will be shown for you to select from.\n\nYou can see a list of supported device types with\n\n $ resin devices supported\n\nExamples:\n\n $ resin app create MyApp\n $ resin app create MyApp --type raspberry-pi',
options: [
{
signature: 'type',
parameter: 'type',
description: 'application type',
alias: 't'
}
],
permission: 'user',
primary: true,
action: function(params, options, done) {
var patterns, resin;
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return resin.models.application.has(params.name).then(function(hasApplication) {
if (hasApplication) {
throw new Error('You already have an application with that name!');
}
}).then(function() {
return options.type || patterns.selectDeviceType();
}).then(function(deviceType) {
return resin.models.application.create(params.name, deviceType);
}).then(function(application) {
return console.info("Application created: " + application.app_name + " (" + application.device_type + ", id " + application.id + ")");
}).nodeify(done);
}
};
exports.list = {
signature: 'apps',
description: 'list all applications',
help: 'Use this command to list all your applications.\n\nNotice this command only shows the most important bits of information for each app.\nIf you want detailed information, use resin app <name> instead.\n\nExamples:\n\n $ resin apps',
permission: 'user',
primary: true,
action: function(params, options, done) {
var resin, visuals;
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return resin.models.application.getAll().then(function(applications) {
return console.log(visuals.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
}).nodeify(done);
}
};
exports.info = {
signature: 'app <name>',
description: 'list a single application',
help: 'Use this command to show detailed information for a single application.\n\nExamples:\n\n $ resin app MyApp',
permission: 'user',
primary: true,
action: function(params, options, done) {
var resin, visuals;
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return resin.models.application.get(params.name).then(function(application) {
return console.log(visuals.table.vertical(application, ["$" + application.app_name + "$", 'id', 'device_type', 'git_repository', 'commit']));
}).nodeify(done);
}
};
exports.restart = {
signature: 'app restart <name>',
description: 'restart an application',
help: 'Use this command to restart all devices that belongs to a certain application.\n\nExamples:\n\n $ resin app restart MyApp',
permission: 'user',
action: function(params, options, done) {
var resin;
resin = require('resin-sdk');
return resin.models.application.restart(params.name).nodeify(done);
}
};
exports.remove = {
signature: 'app rm <name>',
description: 'remove an application',
help: 'Use this command to remove a resin.io application.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin app rm MyApp\n $ resin app rm MyApp --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
var patterns, resin;
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then(function() {
return resin.models.application.remove(params.name);
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,163 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
exports.login = {
signature: 'login',
description: 'login to resin.io',
help: 'Use this command to login to your resin.io account.\n\nThis command will prompt you to login using the following login types:\n\n- Web authorization: open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\n- Credentials: using email/password and 2FA.\n\n- Token: using the authentication token from the preferences page.\n\nExamples:\n\n $ resin login\n $ resin login --web\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret',
options: [
{
signature: 'token',
description: 'auth token',
parameter: 'token',
alias: 't'
}, {
signature: 'web',
description: 'web-based login',
boolean: true,
alias: 'w'
}, {
signature: 'credentials',
description: 'credential-based login',
boolean: true,
alias: 'c'
}, {
signature: 'email',
parameter: 'email',
description: 'email',
alias: ['e', 'u']
}, {
signature: 'password',
parameter: 'password',
description: 'password',
alias: 'p'
}
],
primary: true,
action: function(params, options, done) {
var Promise, _, auth, capitano, form, login, messages, patterns, resin;
_ = require('lodash');
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
resin = require('resin-sdk');
auth = require('resin-cli-auth');
form = require('resin-cli-form');
patterns = require('../utils/patterns');
messages = require('../utils/messages');
login = function(options) {
if (options.token != null) {
return Promise["try"](function() {
if (_.isString(options.token)) {
return options.token;
}
return form.ask({
message: 'Token (from the preferences page)',
name: 'token',
type: 'input'
});
}).then(resin.auth.loginWithToken);
} else if (options.credentials) {
return patterns.authenticate(options);
} else if (options.web) {
console.info('Connecting to the web dashboard');
return auth.login();
}
return patterns.askLoginType().then(function(loginType) {
if (loginType === 'register') {
return capitano.runAsync('signup');
}
options[loginType] = true;
return login(options);
});
};
return resin.settings.get('resinUrl').then(function(resinUrl) {
console.log(messages.resinAsciiArt);
console.log("\nLogging in to " + resinUrl);
return login(options);
}).then(resin.auth.whoami).tap(function(username) {
console.info("Successfully logged in as: " + username);
return console.info("\nNow what?\n\n" + messages.gettingStarted + "\n\nFind out about more super powers by running:\n\n $ resin help\n\n" + messages.reachingOut);
}).nodeify(done);
}
};
exports.logout = {
signature: 'logout',
description: 'logout from resin.io',
help: 'Use this command to logout from your resin.io account.o\n\nExamples:\n\n $ resin logout',
permission: 'user',
action: function(params, options, done) {
var resin;
resin = require('resin-sdk');
return resin.auth.logout().nodeify(done);
}
};
exports.signup = {
signature: 'signup',
description: 'signup to resin.io',
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin whoami\n johndoe',
action: function(params, options, done) {
var form, resin, validation;
resin = require('resin-sdk');
form = require('resin-cli-form');
validation = require('../utils/validation');
return resin.settings.get('resinUrl').then(function(resinUrl) {
console.log("\nRegistering to " + resinUrl);
return form.run([
{
message: 'Email:',
name: 'email',
type: 'input',
validate: validation.validateEmail
}, {
message: 'Username:',
name: 'username',
type: 'input'
}, {
message: 'Password:',
name: 'password',
type: 'password',
validate: validation.validatePassword
}
]);
}).then(resin.auth.register).then(resin.auth.loginWithToken).nodeify(done);
}
};
exports.whoami = {
signature: 'whoami',
description: 'get current username and email address',
help: 'Use this command to find out the current logged in username and email address.\n\nExamples:\n\n $ resin whoami',
permission: 'user',
action: function(params, options, done) {
var Promise, resin, visuals;
Promise = require('bluebird');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return Promise.props({
username: resin.auth.whoami(),
email: resin.auth.getEmail(),
url: resin.settings.get('resinUrl')
}).then(function(results) {
return console.log(visuals.table.vertical(results, ['$account information$', 'username', 'email', 'url']));
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,76 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var _;
_ = require('lodash');
exports.yes = {
signature: 'yes',
description: 'confirm non interactively',
boolean: true,
alias: 'y'
};
exports.optionalApplication = {
signature: 'application',
parameter: 'application',
description: 'application name',
alias: ['a', 'app']
};
exports.application = _.defaults({
required: 'You have to specify an application'
}, exports.optionalApplication);
exports.optionalDevice = {
signature: 'device',
parameter: 'device',
description: 'device uuid',
alias: 'd'
};
exports.booleanDevice = {
signature: 'device',
description: 'device',
boolean: true,
alias: 'd'
};
exports.network = {
signature: 'network',
parameter: 'network',
description: 'network type',
alias: 'n'
};
exports.wifiSsid = {
signature: 'ssid',
parameter: 'ssid',
description: 'wifi ssid, if network is wifi',
alias: 's'
};
exports.wifiKey = {
signature: 'key',
parameter: 'key',
description: 'wifi key, if network is wifi',
alias: 'k'
};
}).call(this);

View File

@ -1,185 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
exports.read = {
signature: 'config read',
description: 'read a device configuration',
help: 'Use this command to read the config.json file from a provisioned device\n\nExamples:\n\n $ resin config read --type raspberry-pi\n $ resin config read --type raspberry-pi --drive /dev/disk2',
options: [
{
signature: 'type',
description: 'device type',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type'
}, {
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd'
}
],
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, config, prettyjson, umount, visuals;
Promise = require('bluebird');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
umount = Promise.promisifyAll(require('umount'));
prettyjson = require('prettyjson');
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
return config.read(drive, options.type);
}).tap(function(configJSON) {
return console.info(prettyjson.render(configJSON));
}).nodeify(done);
}
};
exports.write = {
signature: 'config write <key> <value>',
description: 'write a device configuration',
help: 'Use this command to write the config.json file of a provisioned device\n\nExamples:\n\n $ resin config write --type raspberry-pi username johndoe\n $ resin config write --type raspberry-pi --drive /dev/disk2 username johndoe\n $ resin config write --type raspberry-pi files.network/settings "..."',
options: [
{
signature: 'type',
description: 'device type',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type'
}, {
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd'
}
],
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, _, config, umount, visuals;
Promise = require('bluebird');
_ = require('lodash');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
umount = Promise.promisifyAll(require('umount'));
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
return config.read(drive, options.type).then(function(configJSON) {
console.info("Setting " + params.key + " to " + params.value);
_.set(configJSON, params.key, params.value);
return configJSON;
}).tap(function() {
return umount.umountAsync(drive);
}).then(function(configJSON) {
return config.write(drive, options.type, configJSON);
});
}).tap(function() {
return console.info('Done');
}).nodeify(done);
}
};
exports.reconfigure = {
signature: 'config reconfigure',
description: 'reconfigure a provisioned device',
help: 'Use this command to reconfigure a provisioned device\n\nExamples:\n\n $ resin config reconfigure --type raspberry-pi\n $ resin config reconfigure --type raspberry-pi --advanced\n $ resin config reconfigure --type raspberry-pi --drive /dev/disk2',
options: [
{
signature: 'type',
description: 'device type',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type'
}, {
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd'
}, {
signature: 'advanced',
description: 'show advanced commands',
boolean: true,
alias: 'v'
}
],
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, capitano, config, umount, visuals;
Promise = require('bluebird');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
capitano = Promise.promisifyAll(require('capitano'));
umount = Promise.promisifyAll(require('umount'));
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
return config.read(drive, options.type).get('uuid').tap(function() {
return umount.umountAsync(drive);
}).then(function(uuid) {
var configureCommand;
configureCommand = "os configure " + drive + " " + uuid;
if (options.advanced) {
configureCommand += ' --advanced';
}
return capitano.runAsync(configureCommand);
});
}).then(function() {
return console.info('Done');
}).nodeify(done);
}
};
exports.generate = {
signature: 'config generate <uuid>',
description: 'generate a config.json file',
help: 'Use this command to generate a config.json for a device\n\nExamples:\n\n $ resin config generate 7cf02a6\n $ resin config generate 7cf02a6 --output config.json',
options: [
{
signature: 'output',
description: 'output',
parameter: 'output',
alias: 'o'
}
],
permission: 'user',
action: function(params, options, done) {
var Promise, _, deviceConfig, form, fs, prettyjson, resin;
Promise = require('bluebird');
fs = Promise.promisifyAll(require('fs'));
resin = require('resin-sdk');
_ = require('lodash');
form = require('resin-cli-form');
deviceConfig = require('resin-device-config');
prettyjson = require('prettyjson');
return resin.models.device.get(params.uuid).then(function(device) {
return resin.models.device.getManifestBySlug(device.device_type).get('options').then(form.run).then(_.partial(deviceConfig.get, device.uuid));
}).then(function(config) {
if (options.output != null) {
return fs.writeFileAsync(options.output, JSON.stringify(config));
}
return console.log(prettyjson.render(config));
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,246 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var commandOptions;
commandOptions = require('./command-options');
exports.list = {
signature: 'devices',
description: 'list all devices',
help: 'Use this command to list all devices that belong to you.\n\nYou can filter the devices by application by using the `--application` option.\n\nExamples:\n\n $ resin devices\n $ resin devices --application MyApp\n $ resin devices --app MyApp\n $ resin devices -a MyApp',
options: [commandOptions.optionalApplication],
permission: 'user',
primary: true,
action: function(params, options, done) {
var Promise, _, resin, visuals;
Promise = require('bluebird');
_ = require('lodash');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return Promise["try"](function() {
if (options.application != null) {
return resin.models.device.getAllByApplication(options.application);
}
return resin.models.device.getAll();
}).tap(function(devices) {
devices = _.map(devices, function(device) {
device.uuid = device.uuid.slice(0, 7);
return device;
});
return console.log(visuals.table.horizontal(devices, ['id', 'uuid', 'name', 'device_type', 'application_name', 'status', 'is_online']));
}).nodeify(done);
}
};
exports.info = {
signature: 'device <uuid>',
description: 'list a single device',
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device 7cf02a6',
permission: 'user',
primary: true,
action: function(params, options, done) {
var resin, visuals;
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return resin.models.device.get(params.uuid).then(function(device) {
return resin.models.device.getStatus(device).then(function(status) {
device.status = status;
return console.log(visuals.table.vertical(device, ["$" + device.name + "$", 'id', 'device_type', 'status', 'is_online', 'ip_address', 'application_name', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
});
}).nodeify(done);
}
};
exports.register = {
signature: 'device register <application>',
description: 'register a device',
help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp',
permission: 'user',
options: [
{
signature: 'uuid',
description: 'custom uuid',
parameter: 'uuid',
alias: 'u'
}
],
action: function(params, options, done) {
var Promise, resin;
Promise = require('bluebird');
resin = require('resin-sdk');
return resin.models.application.get(params.application).then(function(application) {
return Promise["try"](function() {
return options.uuid || resin.models.device.generateUUID();
}).then(function(uuid) {
console.info("Registering to " + application.app_name + ": " + uuid);
return resin.models.device.register(application.app_name, uuid);
});
}).get('uuid').nodeify(done);
}
};
exports.remove = {
signature: 'device rm <uuid>',
description: 'remove a device',
help: 'Use this command to remove a device from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device rm 7cf02a6\n $ resin device rm 7cf02a6 --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
var patterns, resin;
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then(function() {
return resin.models.device.remove(params.uuid);
}).nodeify(done);
}
};
exports.identify = {
signature: 'device identify <uuid>',
description: 'identify a device with a UUID',
help: 'Use this command to identify a device.\n\nIn the Raspberry Pi, the ACT led is blinked several times.\n\nExamples:\n\n $ resin device identify 23c73a1',
permission: 'user',
action: function(params, options, done) {
var resin;
resin = require('resin-sdk');
return resin.models.device.identify(params.uuid).nodeify(done);
}
};
exports.reboot = {
signature: 'device reboot <uuid>',
description: 'restart a device',
help: 'Use this command to remotely reboot a device\n\nExamples:\n\n $ resin device reboot 23c73a1',
permission: 'user',
action: function(params, options, done) {
var resin;
resin = require('resin-sdk');
return resin.models.device.reboot(params.uuid).nodeify(done);
}
};
exports.rename = {
signature: 'device rename <uuid> [newName]',
description: 'rename a resin device',
help: 'Use this command to rename a device.\n\nIf you omit the name, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device rename 7cf02a6\n $ resin device rename 7cf02a6 MyPi',
permission: 'user',
action: function(params, options, done) {
var Promise, _, form, resin;
Promise = require('bluebird');
_ = require('lodash');
resin = require('resin-sdk');
form = require('resin-cli-form');
return Promise["try"](function() {
if (!_.isEmpty(params.newName)) {
return params.newName;
}
return form.ask({
message: 'How do you want to name this device?',
type: 'input'
});
}).then(_.partial(resin.models.device.rename, params.uuid)).nodeify(done);
}
};
exports.move = {
signature: 'device move <uuid>',
description: 'move a device to another application',
help: 'Use this command to move a device to another application you own.\n\nIf you omit the application, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device move 7cf02a6\n $ resin device move 7cf02a6 --application MyNewApp',
permission: 'user',
options: [commandOptions.optionalApplication],
action: function(params, options, done) {
var _, patterns, resin;
resin = require('resin-sdk');
_ = require('lodash');
patterns = require('../utils/patterns');
return resin.models.device.get(params.uuid).then(function(device) {
return options.application || patterns.selectApplication(function(application) {
return _.all([application.device_type === device.device_type, device.application_name !== application.app_name]);
});
}).tap(function(application) {
return resin.models.device.move(params.uuid, application);
}).then(function(application) {
return console.info(params.uuid + " was moved to " + application);
}).nodeify(done);
}
};
exports.init = {
signature: 'device init',
description: 'initialise a device with resin os',
help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNotice this command may ask for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp',
options: [
commandOptions.optionalApplication, commandOptions.yes, {
signature: 'advanced',
description: 'enable advanced configuration',
boolean: true,
alias: 'v'
}
],
permission: 'user',
action: function(params, options, done) {
var Promise, capitano, helpers, patterns, resin, rimraf, tmp;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
rimraf = Promise.promisify(require('rimraf'));
tmp = Promise.promisifyAll(require('tmp'));
tmp.setGracefulCleanup();
resin = require('resin-sdk');
helpers = require('../utils/helpers');
patterns = require('../utils/patterns');
return Promise["try"](function() {
if (options.application != null) {
return options.application;
}
return patterns.selectApplication();
}).then(resin.models.application.get).then(function(application) {
var download;
download = function() {
return tmp.tmpNameAsync().then(function(temporalPath) {
return capitano.runAsync("os download " + application.device_type + " --output " + temporalPath);
}).disposer(function(temporalPath) {
return rimraf(temporalPath);
});
};
return Promise.using(download(), function(temporalPath) {
return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
var configure;
configure = "os configure " + temporalPath + " " + device.uuid;
if (options.advanced) {
configure += ' --advanced';
}
return capitano.runAsync(configure).then(function() {
var message;
message = 'Initializing a device requires administrative permissions\ngiven that we need to access raw devices directly.\n';
return helpers.sudo(['os', 'initialize', temporalPath, '--type', application.device_type], message);
})["catch"](function(error) {
return resin.models.device.remove(device.uuid)["finally"](function() {
throw error;
});
});
});
}).then(function(device) {
console.log('Done');
return device.uuid;
});
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,134 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var commandOptions;
commandOptions = require('./command-options');
exports.list = {
signature: 'envs',
description: 'list all environment variables',
help: 'Use this command to list all environment variables for\na particular application or device.\n\nThis command lists all custom environment variables.\nIf you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application MyApp\n $ resin envs --application MyApp --verbose\n $ resin envs --device 7cf02a6',
options: [
commandOptions.optionalApplication, commandOptions.optionalDevice, {
signature: 'verbose',
description: 'show private environment variables',
boolean: true,
alias: 'v'
}
],
permission: 'user',
action: function(params, options, done) {
var Promise, _, resin, visuals;
Promise = require('bluebird');
_ = require('lodash');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return Promise["try"](function() {
if (options.application != null) {
return resin.models.environmentVariables.getAllByApplication(options.application);
} else if (options.device != null) {
return resin.models.environmentVariables.device.getAll(options.device);
} else {
throw new Error('You must specify an application or device');
}
}).tap(function(environmentVariables) {
var isSystemVariable;
if (_.isEmpty(environmentVariables)) {
throw new Error('No environment variables found');
}
if (!options.verbose) {
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
environmentVariables = _.reject(environmentVariables, isSystemVariable);
}
return console.log(visuals.table.horizontal(environmentVariables, ['id', 'name', 'value']));
}).nodeify(done);
}
};
exports.remove = {
signature: 'env rm <id>',
description: 'remove an environment variable',
help: 'Use this command to remove an environment variable from an application.\n\nDon\'t remove resin specific variables, as things might not work as expected.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nIf you want to eliminate a device environment variable, pass the `--device` boolean option.\n\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes\n $ resin env rm 215 --device',
options: [commandOptions.yes, commandOptions.booleanDevice],
permission: 'user',
action: function(params, options, done) {
var patterns, resin;
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
if (options.device) {
return resin.models.environmentVariables.device.remove(params.id);
} else {
return resin.models.environmentVariables.remove(params.id);
}
}).nodeify(done);
}
};
exports.add = {
signature: 'env add <key> [value]',
description: 'add an environment variable',
help: 'Use this command to add an enviroment variable to an application.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\n\nUse the `--device` option if you want to assign the environment variable\nto a specific device.\n\nIf the value is grabbed from the environment, a warning message will be printed.\nUse `--quiet` to remove it.\n\nExamples:\n\n $ resin env add EDITOR vim --application MyApp\n $ resin env add TERM --application MyApp\n $ resin env add EDITOR vim --device 7cf02a6',
options: [commandOptions.optionalApplication, commandOptions.optionalDevice],
permission: 'user',
action: function(params, options, done) {
var Promise, resin;
Promise = require('bluebird');
resin = require('resin-sdk');
return Promise["try"](function() {
if (params.value == null) {
params.value = process.env[params.key];
if (params.value == null) {
throw new Error("Environment value not found for key: " + params.key);
} else {
console.info("Warning: using " + params.key + "=" + params.value + " from host environment");
}
}
if (options.application != null) {
return resin.models.environmentVariables.create(options.application, params.key, params.value);
} else if (options.device != null) {
return resin.models.environmentVariables.device.create(options.device, params.key, params.value);
} else {
throw new Error('You must specify an application or device');
}
}).nodeify(done);
}
};
exports.rename = {
signature: 'env rename <id> <value>',
description: 'rename an environment variable',
help: 'Use this command to rename an enviroment variable from an application.\n\nPass the `--device` boolean option if you want to rename a device environment variable.\n\nExamples:\n\n $ resin env rename 376 emacs\n $ resin env rename 376 emacs --device',
permission: 'user',
options: [commandOptions.booleanDevice],
action: function(params, options, done) {
var Promise, resin;
Promise = require('bluebird');
resin = require('resin-sdk');
return Promise["try"](function() {
if (options.device) {
return resin.models.environmentVariables.device.update(params.id, params.value);
} else {
return resin.models.environmentVariables.update(params.id, params.value);
}
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,136 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var _, capitano, columnify, command, general, indent, messages, parse, print;
_ = require('lodash');
_.str = require('underscore.string');
capitano = require('capitano');
columnify = require('columnify');
messages = require('../utils/messages');
parse = function(object) {
return _.object(_.map(object, function(item) {
var signature;
if (item.alias != null) {
signature = item.toString();
} else {
signature = item.signature.toString();
}
return [signature, item.description];
}));
};
indent = function(text) {
text = _.map(_.str.lines(text), function(line) {
return ' ' + line;
});
return text.join('\n');
};
print = function(data) {
return console.log(indent(columnify(data, {
showHeaders: false,
minWidth: 35
})));
};
general = function(params, options, done) {
var commands, groupedCommands;
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
console.log(messages.gettingStarted + "\n");
console.log(messages.reachingOut);
console.log('\nPrimary commands:\n');
commands = _.reject(capitano.state.commands, function(command) {
return command.isWildcard();
});
groupedCommands = _.groupBy(commands, function(command) {
if (command.plugin) {
return 'plugins';
} else if (command.primary) {
return 'primary';
}
return 'secondary';
});
print(parse(groupedCommands.primary));
if (options.verbose) {
if (!_.isEmpty(groupedCommands.plugins)) {
console.log('\nInstalled plugins:\n');
print(parse(groupedCommands.plugins));
}
console.log('\nAdditional commands:\n');
print(parse(groupedCommands.secondary));
} else {
console.log('\nRun `resin help --verbose` to list additional commands');
}
if (!_.isEmpty(capitano.state.globalOptions)) {
console.log('\nGlobal Options:\n');
print(parse(capitano.state.globalOptions));
}
return done();
};
command = function(params, options, done) {
return capitano.state.getMatchCommand(params.command, function(error, command) {
if (error != null) {
return done(error);
}
if ((command == null) || command.isWildcard()) {
return done(new Error("Command not found: " + params.command));
}
console.log("Usage: " + command.signature);
if (command.help != null) {
console.log("\n" + command.help);
} else if (command.description != null) {
console.log("\n" + (_.str.humanize(command.description)));
}
if (!_.isEmpty(command.options)) {
console.log('\nOptions:\n');
print(parse(command.options));
}
return done();
});
};
exports.help = {
signature: 'help [command...]',
description: 'show help',
help: 'Get detailed help for an specific command.\n\nExamples:\n\n $ resin help apps\n $ resin help os download',
primary: true,
options: [
{
signature: 'verbose',
description: 'show additional commands',
boolean: true,
alias: 'v'
}
],
action: function(params, options, done) {
if (params.command != null) {
return command(params, options, done);
} else {
return general(params, options, done);
}
}
};
}).call(this);

View File

@ -1,35 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
module.exports = {
wizard: require('./wizard'),
app: require('./app'),
info: require('./info'),
auth: require('./auth'),
device: require('./device'),
env: require('./environment-variables'),
keys: require('./keys'),
logs: require('./logs'),
notes: require('./notes'),
help: require('./help'),
os: require('./os'),
settings: require('./settings'),
config: require('./config')
};
}).call(this);

View File

@ -1,97 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var commandOptions;
commandOptions = require('./command-options');
exports.list = {
signature: 'keys',
description: 'list all ssh keys',
help: 'Use this command to list all your SSH keys.\n\nExamples:\n\n $ resin keys',
permission: 'user',
action: function(params, options, done) {
var resin, visuals;
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return resin.models.key.getAll().then(function(keys) {
return console.log(visuals.table.horizontal(keys, ['id', 'title']));
}).nodeify(done);
}
};
exports.info = {
signature: 'key <id>',
description: 'list a single ssh key',
help: 'Use this command to show information about a single SSH key.\n\nExamples:\n\n $ resin key 17',
permission: 'user',
action: function(params, options, done) {
var resin, visuals;
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
return resin.models.key.get(params.id).then(function(key) {
console.log(visuals.table.vertical(key, ['id', 'title']));
return console.log('\n' + key.public_key);
}).nodeify(done);
}
};
exports.remove = {
signature: 'key rm <id>',
description: 'remove a ssh key',
help: 'Use this command to remove a SSH key from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin key rm 17\n $ resin key rm 17 --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
var patterns, resin;
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then(function() {
return resin.models.key.remove(params.id);
}).nodeify(done);
}
};
exports.add = {
signature: 'key add <name> [path]',
description: 'add a SSH key to resin.io',
help: 'Use this command to associate a new SSH key with your account.\n\nIf `path` is omitted, the command will attempt\nto read the SSH key from stdin.\n\nExamples:\n\n $ resin key add Main ~/.ssh/id_rsa.pub\n $ cat ~/.ssh/id_rsa.pub | resin key add Main',
permission: 'user',
action: function(params, options, done) {
var Promise, _, capitano, fs, resin;
_ = require('lodash');
Promise = require('bluebird');
fs = Promise.promisifyAll(require('fs'));
capitano = require('capitano');
resin = require('resin-sdk');
return Promise["try"](function() {
if (params.path != null) {
return fs.readFileAsync(params.path, {
encoding: 'utf8'
});
}
return Promise.fromNode(function(callback) {
return capitano.utils.getStdin(function(data) {
return callback(null, data);
});
});
}).then(_.partial(resin.models.key.create, params.name)).nodeify(done);
}
};
}).call(this);

View File

@ -1,58 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
module.exports = {
signature: 'logs <uuid>',
description: 'show device logs',
help: 'Use this command to show logs for a specific device.\n\nBy default, the command prints all log messages and exit.\n\nTo continuously stream output, and see new logs in real time, use the `--tail` option.\n\nNote that for now you need to provide the whole UUID for this command to work correctly.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a1\n $ resin logs 23c73a1',
options: [
{
signature: 'tail',
description: 'continuously stream output',
boolean: true,
alias: 't'
}
],
permission: 'user',
primary: true,
action: function(params, options, done) {
var _, moment, printLine, promise, resin;
_ = require('lodash');
resin = require('resin-sdk');
moment = require('moment');
printLine = function(line) {
var timestamp;
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)');
return console.log(timestamp + " " + line.message);
};
promise = resin.logs.history(params.uuid).each(printLine);
if (!options.tail) {
return promise["catch"](done)["finally"](function() {
return process.exit(0);
});
}
return promise.then(function() {
return resin.logs.subscribe(params.uuid).then(function(logs) {
logs.on('line', printLine);
return logs.on('error', done);
});
})["catch"](done);
}
};
}).call(this);

View File

@ -1,47 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
exports.set = {
signature: 'note <|note>',
description: 'set a device note',
help: 'Use this command to set or update a device note.\n\nIf note command isn\'t passed, the tool attempts to read from `stdin`.\n\nTo view the notes, use $ resin device <uuid>.\n\nExamples:\n\n $ resin note "My useful note" --device 7cf02a6\n $ cat note.txt | resin note --device 7cf02a6',
options: [
{
signature: 'device',
parameter: 'device',
description: 'device uuid',
alias: ['d', 'dev'],
required: 'You have to specify a device'
}
],
permission: 'user',
action: function(params, options, done) {
var Promise, _, resin;
Promise = require('bluebird');
_ = require('lodash');
resin = require('resin-sdk');
return Promise["try"](function() {
if (_.isEmpty(params.note)) {
throw new Error('Missing note content');
}
return resin.models.device.note(options.device, params.note);
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,191 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var commandOptions, stepHandler;
commandOptions = require('./command-options');
exports.download = {
signature: 'os download <type>',
description: 'download an unconfigured os image',
help: 'Use this command to download an unconfigured os image for a certain device type.\n\nExamples:\n\n $ resin os download parallella -o ../foo/bar/parallella.img',
permission: 'user',
options: [
{
signature: 'output',
description: 'output path',
parameter: 'output',
alias: 'o',
required: 'You have to specify an output location'
}
],
action: function(params, options, done) {
var fs, manager, rindle, unzip, visuals;
unzip = require('unzip2');
fs = require('fs');
rindle = require('rindle');
manager = require('resin-image-manager');
visuals = require('resin-cli-visuals');
console.info("Getting device operating system for " + params.type);
return manager.get(params.type).then(function(stream) {
var bar, output, spinner;
bar = new visuals.Progress('Downloading Device OS');
spinner = new visuals.Spinner('Downloading Device OS (size unknown)');
stream.on('progress', function(state) {
if (state != null) {
return bar.update(state);
} else {
return spinner.start();
}
});
stream.on('end', function() {
return spinner.stop();
});
if (stream.mime === 'application/zip') {
output = unzip.Extract({
path: options.output
});
} else {
output = fs.createWriteStream(options.output);
}
return rindle.wait(stream.pipe(output))["return"](options.output);
}).tap(function(output) {
return console.info('The image was downloaded successfully');
}).nodeify(done);
}
};
stepHandler = function(step) {
var _, bar, helpers, rindle, visuals;
_ = require('lodash');
rindle = require('rindle');
visuals = require('resin-cli-visuals');
helpers = require('../utils/helpers');
step.on('stdout', _.bind(process.stdout.write, process.stdout));
step.on('stderr', _.bind(process.stderr.write, process.stderr));
step.on('state', function(state) {
if (state.operation.command === 'burn') {
return;
}
return console.log(helpers.stateToString(state));
});
bar = new visuals.Progress('Writing Device OS');
step.on('burn', _.bind(bar.update, bar));
return rindle.wait(step);
};
exports.configure = {
signature: 'os configure <image> <uuid>',
description: 'configure an os image',
help: 'Use this command to configure a previously download operating system image with a device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6',
permission: 'user',
options: [
{
signature: 'advanced',
description: 'show advanced commands',
boolean: true,
alias: 'v'
}
],
action: function(params, options, done) {
var _, form, helpers, init, resin;
_ = require('lodash');
resin = require('resin-sdk');
form = require('resin-cli-form');
init = require('resin-device-init');
helpers = require('../utils/helpers');
console.info('Configuring operating system image');
return resin.models.device.get(params.uuid).get('device_type').then(resin.models.device.getManifestBySlug).get('options').then(function(questions) {
var advancedGroup, override;
if (!options.advanced) {
advancedGroup = _.findWhere(questions, {
name: 'advanced',
isGroup: true
});
if (advancedGroup != null) {
override = helpers.getGroupDefaults(advancedGroup);
}
}
return form.run(questions, {
override: override
});
}).then(function(answers) {
return init.configure(params.image, params.uuid, answers).then(stepHandler);
}).nodeify(done);
}
};
exports.initialize = {
signature: 'os initialize <image>',
description: 'initialize an os image',
help: 'Use this command to initialize a previously configured operating system image.\n\nExamples:\n\n $ resin os initialize ../path/rpi.img --type \'raspberry-pi\'',
permission: 'user',
options: [
commandOptions.yes, {
signature: 'type',
description: 'device type',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type'
}, {
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd'
}
],
root: true,
action: function(params, options, done) {
var Promise, form, init, patterns, resin, umount;
Promise = require('bluebird');
umount = Promise.promisifyAll(require('umount'));
resin = require('resin-sdk');
form = require('resin-cli-form');
init = require('resin-device-init');
patterns = require('../utils/patterns');
console.info('Initializing device');
return resin.models.device.getManifestBySlug(options.type).then(function(manifest) {
var ref;
return (ref = manifest.initialization) != null ? ref.options : void 0;
}).then(function(questions) {
return form.run(questions, {
override: {
drive: options.drive
}
});
}).tap(function(answers) {
var message;
if (answers.drive == null) {
return;
}
message = "This will erase " + answers.drive + ". Are you sure?";
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.umountAsync);
}).tap(function(answers) {
return init.initialize(params.image, options.type, answers).then(stepHandler);
}).then(function(answers) {
if (answers.drive == null) {
return;
}
return umount.umountAsync(answers.drive).tap(function() {
return console.info("You can safely remove " + answers.drive + " now");
});
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,31 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
exports.list = {
signature: 'settings',
description: 'print current settings',
help: 'Use this command to display detected settings\n\nExamples:\n\n $ resin settings',
action: function(params, options, done) {
var prettyjson, resin;
resin = require('resin-sdk');
prettyjson = require('prettyjson');
return resin.settings.getAll().then(prettyjson.render).then(console.log).nodeify(done);
}
};
}).call(this);

View File

@ -1,63 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
exports.wizard = {
signature: 'quickstart [name]',
description: 'getting started with resin.io',
help: 'Use this command to run a friendly wizard to get started with resin.io.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ resin quickstart\n $ resin quickstart MyApp',
primary: true,
action: function(params, options, done) {
var Promise, capitano, patterns, resin;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
if (isLoggedIn) {
return;
}
console.info('Looks like you\'re not logged in yet!');
console.info('Lets go through a quick wizard to get you started.\n');
return capitano.runAsync('login');
}).then(function() {
if (params.name != null) {
return;
}
return patterns.selectOrCreateApplication().tap(function(applicationName) {
return resin.models.application.has(applicationName).then(function(hasApplication) {
if (hasApplication) {
return applicationName;
}
return capitano.runAsync("app create " + applicationName);
});
}).then(function(applicationName) {
return params.name = applicationName;
});
}).then(function() {
return capitano.runAsync("device init --application " + params.name);
}).tap(patterns.awaitDevice).then(function(uuid) {
return capitano.runAsync("device " + uuid);
}).then(function() {
return resin.models.application.get(params.name);
}).then(function(application) {
return console.log("Your device is ready to start pushing some code!\n\nCheck our official documentation for more information:\n\n http://docs.resin.io/#/pages/introduction/introduction.md\n\nClone an example or go to an existing application directory and run:\n\n $ git remote add resin " + application.git_repository + "\n $ git push resin master");
}).nodeify(done);
}
};
}).call(this);

View File

@ -1,144 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var Promise, _, actions, capitano, errors, events, plugins, resin, update;
_ = require('lodash');
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
resin = require('resin-sdk');
actions = require('./actions');
errors = require('./errors');
events = require('./events');
plugins = require('./utils/plugins');
update = require('./utils/update');
capitano.permission('user', function(done) {
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
if (!isLoggedIn) {
throw new Error('You have to log in to continue\n\nRun the following command to go through the login wizard:\n\n $ resin login');
}
}).nodeify(done);
});
capitano.command({
signature: '*',
action: function() {
return capitano.execute({
command: 'help'
});
}
});
capitano.command(actions.info.version);
capitano.command(actions.help.help);
capitano.command(actions.wizard.wizard);
capitano.command(actions.auth.login);
capitano.command(actions.auth.logout);
capitano.command(actions.auth.signup);
capitano.command(actions.auth.whoami);
capitano.command(actions.app.create);
capitano.command(actions.app.list);
capitano.command(actions.app.remove);
capitano.command(actions.app.restart);
capitano.command(actions.app.info);
capitano.command(actions.device.list);
capitano.command(actions.device.rename);
capitano.command(actions.device.init);
capitano.command(actions.device.remove);
capitano.command(actions.device.identify);
capitano.command(actions.device.reboot);
capitano.command(actions.device.register);
capitano.command(actions.device.move);
capitano.command(actions.device.info);
capitano.command(actions.notes.set);
capitano.command(actions.keys.list);
capitano.command(actions.keys.add);
capitano.command(actions.keys.info);
capitano.command(actions.keys.remove);
capitano.command(actions.env.list);
capitano.command(actions.env.add);
capitano.command(actions.env.rename);
capitano.command(actions.env.remove);
capitano.command(actions.os.download);
capitano.command(actions.os.configure);
capitano.command(actions.os.initialize);
capitano.command(actions.config.read);
capitano.command(actions.config.write);
capitano.command(actions.config.reconfigure);
capitano.command(actions.config.generate);
capitano.command(actions.settings.list);
capitano.command(actions.logs);
update.notify();
plugins.register(/^resin-plugin-(.+)$/).then(function() {
var cli;
cli = capitano.parse(process.argv);
return events.trackCommand(cli).then(function() {
return capitano.executeAsync(cli);
});
})["catch"](errors.handle);
}).call(this);

View File

@ -1,37 +0,0 @@
(function() {
var Mixpanel, Promise, _, packageJSON, resin;
_ = require('lodash');
Mixpanel = require('mixpanel');
Promise = require('bluebird');
resin = require('resin-sdk');
packageJSON = require('../package.json');
exports.getLoggerInstance = _.memoize(function() {
return resin.models.config.getMixpanelToken().then(Mixpanel.init);
});
exports.trackCommand = function(capitanoCommand) {
return Promise.props({
resinUrl: resin.settings.get('resinUrl'),
username: resin.auth.whoami(),
mixpanel: exports.getLoggerInstance()
}).then(function(data) {
return data.mixpanel.track("[CLI] " + capitanoCommand.command, {
distinct_id: data.username,
argv: process.argv.join(' '),
version: packageJSON.version,
node: process.version,
arch: process.arch,
resinUrl: data.resinUrl,
platform: process.platform,
command: capitanoCommand
});
});
};
}).call(this);

View File

@ -1,66 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var Promise, _, capitano, chalk, os, president;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
_ = require('lodash');
_.str = require('underscore.string');
president = Promise.promisifyAll(require('president'));
os = require('os');
chalk = require('chalk');
exports.getGroupDefaults = function(group) {
return _.chain(group).get('options').map(function(question) {
return [question.name, question["default"]];
}).object().value();
};
exports.stateToString = function(state) {
var percentage, result;
percentage = _.str.lpad(state.percentage, 3, '0') + '%';
result = (chalk.blue(percentage)) + " " + (chalk.cyan(state.operation.command));
switch (state.operation.command) {
case 'copy':
return result + " " + state.operation.from.path + " -> " + state.operation.to.path;
case 'replace':
return result + " " + state.operation.file.path + ", " + state.operation.copy + " -> " + state.operation.replace;
case 'run-script':
return result + " " + state.operation.script;
default:
throw new Error("Unsupported operation: " + state.operation.type);
}
};
exports.sudo = function(command, message) {
command = _.union(_.take(process.argv, 2), command);
console.log(message);
if (os.platform() !== 'win32') {
console.log('Type your computer password to continue');
}
return president.executeAsync(command);
};
}).call(this);

View File

@ -1,10 +0,0 @@
(function() {
exports.gettingStarted = 'Run the following command to get a device started with Resin.io\n\n $ resin quickstart';
exports.reachingOut = 'If you need help, or just want to say hi, don\'t hesitate in reaching out at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
exports.getHelp = 'If you need help, don\'t hesitate in contacting us at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
exports.resinAsciiArt = '______ _ _\n| ___ \\ (_) (_)\n| |_/ /___ ___ _ _ __ _ ___\n| // _ \\/ __| | \'_ \\ | |/ _ \\\n| |\\ \\ __/\\__ \\ | | | |_| | (_) |\n\\_| \\_\\___||___/_|_| |_(_)_|\\___/';
}).call(this);

View File

@ -1,196 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var Promise, _, chalk, form, messages, resin, validation, visuals;
_ = require('lodash');
Promise = require('bluebird');
form = require('resin-cli-form');
visuals = require('resin-cli-visuals');
resin = require('resin-sdk');
chalk = require('chalk');
validation = require('./validation');
messages = require('./messages');
exports.authenticate = function(options) {
return form.run([
{
message: 'Email:',
name: 'email',
type: 'input',
validate: validation.validateEmail
}, {
message: 'Password:',
name: 'password',
type: 'password'
}
], {
override: options
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
if (isTwoFactorAuthPassed) {
return;
}
return form.ask({
message: 'Two factor auth challenge:',
name: 'code',
type: 'input'
}).then(resin.auth.twoFactor.challenge)["catch"](function() {
return resin.auth.logout().then(function() {
throw new Error('Invalid two factor authentication code');
});
});
});
};
exports.askLoginType = function() {
return form.ask({
message: 'How would you like to login?',
name: 'loginType',
type: 'list',
choices: [
{
name: 'Web authorization (recommended)',
value: 'web'
}, {
name: 'Credentials',
value: 'credentials'
}, {
name: 'Authentication token',
value: 'token'
}, {
name: 'I don\'t have a Resin account!',
value: 'register'
}
]
});
};
exports.selectDeviceType = function() {
return resin.models.device.getSupportedDeviceTypes().then(function(deviceTypes) {
return form.ask({
message: 'Device Type',
type: 'list',
choices: deviceTypes
});
});
};
exports.confirm = function(yesOption, message) {
return Promise["try"](function() {
if (yesOption) {
return true;
}
return form.ask({
message: message,
type: 'confirm',
"default": false
});
}).then(function(confirmed) {
if (!confirmed) {
throw new Error('Aborted');
}
});
};
exports.selectApplication = function(filter) {
return resin.models.application.hasAny().then(function(hasAnyApplications) {
if (!hasAnyApplications) {
throw new Error('You don\'t have any applications');
}
return resin.models.application.getAll();
}).filter(filter || _.constant(true)).then(function(applications) {
return form.ask({
message: 'Select an application',
type: 'list',
choices: _.map(applications, function(application) {
return {
name: application.app_name + " (" + application.device_type + ")",
value: application.app_name
};
})
});
});
};
exports.selectOrCreateApplication = function() {
return resin.models.application.hasAny().then(function(hasAnyApplications) {
if (!hasAnyApplications) {
return;
}
return resin.models.application.getAll().then(function(applications) {
applications = _.map(applications, function(application) {
return {
name: application.app_name + " (" + application.device_type + ")",
value: application.app_name
};
});
applications.unshift({
name: 'Create a new application',
value: null
});
return form.ask({
message: 'Select an application',
type: 'list',
choices: applications
});
});
}).then(function(application) {
if (application != null) {
return application;
}
return form.ask({
message: 'Choose a Name for your new application',
type: 'input',
validate: validation.validateApplicationName
});
});
};
exports.awaitDevice = function(uuid) {
return resin.models.device.getName(uuid).then(function(deviceName) {
var poll, spinner;
spinner = new visuals.Spinner("Waiting for " + deviceName + " to come online");
poll = function() {
return resin.models.device.isOnline(uuid).then(function(isOnline) {
if (isOnline) {
spinner.stop();
console.info("The device **" + deviceName + "** is online!");
} else {
spinner.start();
return Promise.delay(3000).then(poll);
}
});
};
console.info("Waiting for " + deviceName + " to connect to resin...");
return poll()["return"](uuid);
});
};
exports.printErrorMessage = function(message) {
console.error(chalk.red(message));
return console.error(chalk.red("\n" + messages.getHelp + "\n"));
};
}).call(this);

View File

@ -1,43 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var _, capitano, nplugm, patterns;
nplugm = require('nplugm');
_ = require('lodash');
capitano = require('capitano');
patterns = require('./patterns');
exports.register = function(regex) {
return nplugm.list(regex).map(function(plugin) {
var command;
command = require(plugin);
command.plugin = true;
if (!_.isArray(command)) {
return capitano.command(command);
}
return _.each(command, capitano.command);
})["catch"](function(error) {
return patterns.printErrorMessage(error.message);
});
};
}).call(this);

View File

@ -1,49 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var isRoot, notifier, packageJSON, updateNotifier;
updateNotifier = require('update-notifier');
isRoot = require('is-root');
packageJSON = require('../../package.json');
if (!isRoot()) {
notifier = updateNotifier({
pkg: packageJSON
});
}
exports.hasAvailableUpdate = function() {
return notifier != null;
};
exports.notify = function() {
if (!exports.hasAvailableUpdate()) {
return;
}
notifier.notify({
defer: false
});
if (notifier.update != null) {
return console.log('Notice that you might need administrator privileges depending on your setup\n');
}
};
}).call(this);

View File

@ -1,44 +0,0 @@
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function() {
var validEmail;
validEmail = require('valid-email');
exports.validateEmail = function(input) {
if (!validEmail(input)) {
return 'Email is not valid';
}
return true;
};
exports.validatePassword = function(input) {
if (input.length < 8) {
return 'Password should be 8 characters long';
}
return true;
};
exports.validateApplicationName = function(input) {
if (input.length < 4) {
return 'The application name should be at least 4 characters';
}
return true;
};
}).call(this);

View File

@ -1,84 +0,0 @@
{
"title": "Resin CLI Documentation",
"introduction": "This tool allows you to interact with the resin.io api from the comfort of your command line.\n\nTo get started download the CLI from npm.\n\n\t$ npm install resin-cli -g\n\nThen authenticate yourself:\n\n\t$ resin login\n\nNow you have access to all the commands referenced below.",
"categories": [
{
"title": "Application",
"files": [
"lib/actions/app.coffee"
]
},
{
"title": "Authentication",
"files": [
"lib/actions/auth.coffee"
]
},
{
"title": "Device",
"files": [
"lib/actions/device.coffee"
]
},
{
"title": "Environment Variables",
"files": [
"lib/actions/environment-variables.coffee"
]
},
{
"title": "Help",
"files": [
"lib/actions/help.coffee"
]
},
{
"title": "Information",
"files": [
"lib/actions/info.coffee"
]
},
{
"title": "Keys",
"files": [
"lib/actions/keys.coffee"
]
},
{
"title": "Logs",
"files": [
"lib/actions/logs.coffee"
]
},
{
"title": "Notes",
"files": [
"lib/actions/notes.coffee"
]
},
{
"title": "OS",
"files": [
"lib/actions/os.coffee"
]
},
{
"title": "Config",
"files": [
"lib/actions/config.coffee"
]
},
{
"title": "Settings",
"files": [
"lib/actions/settings.coffee"
]
},
{
"title": "Wizard",
"files": [
"lib/actions/wizard.coffee"
]
}
]
}

154
capitanodoc.ts Normal file
View File

@ -0,0 +1,154 @@
export = {
title: 'Resin CLI Documentation',
introduction: `\
This tool allows you to interact with the resin.io api from the comfort of your command line.
Please make sure your system meets the requirements as specified in the [README](https://github.com/resin-io/resin-cli).
## Install the CLI
### Npm install
The best supported way to install the CLI is from npm:
$ npm install resin-cli -g --production --unsafe-perm
\`--unsafe-perm\` is only required on systems where the global install directory is not user-writable.
This allows npm install steps to download and save prebuilt native binaries. You may be able to omit it,
especially if you're using a user-managed node install such as [nvm](https://github.com/creationix/nvm).
### Standalone install
Alternatively, if you don't have a node or pre-gyp environment, you can still install the CLI as a standalone
binary. **This is in experimental and may not work perfectly yet in all environments**, but works well in
initial cross-platform testing, so it may be useful, and we'd love your feedback if you hit any issues.
To install the CLI as a standalone binary:
* Download the latest zip for your OS from https://github.com/resin-io/resin-cli/releases.
* Extract the contents, putting the \`resin-cli\` folder somewhere appropriate for your system (e.g. \`C:/resin-cli\`, \`/usr/local/lib/resin-cli\`, etc).
* Add the \`resin-cli\` folder to your \`PATH\`. (
[Windows instructions](https://www.computerhope.com/issues/ch000549.htm),
[Linux instructions](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix),
[OSX instructions](https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently))
* Running \`resin\` in a fresh command line should print the resin CLI help.
To update in future, simply download a new release and replace the extracted folder.
Have any problems, or see any unexpected behaviour? Please file an issue!
## Getting started
Once you have the CLI installed, you'll need to log in, so it can access everything in your resin.io account.
To authenticate yourself, run:
$ resin login
You now have access to all the commands referenced below.
## Proxy support
The CLI does support HTTP(S) proxies.
You can configure the proxy using several methods (in order of their precedence):
* set the \`RESINRC_PROXY\` environment variable in the URL format (with protocol, host, port, and optionally the basic auth),
* use the [resin config file](https://www.npmjs.com/package/resin-settings-client#documentation) (project-specific or user-level)
and set the \`proxy\` setting. This can be:
* a string in the URL format,
* or an object following [this format](https://www.npmjs.com/package/global-tunnel-ng#options), which allows more control,
* or set the conventional \`https_proxy\` / \`HTTPS_PROXY\` / \`http_proxy\` / \`HTTP_PROXY\`
environment variable (in the same standard URL format).\
`,
categories: [
{
title: 'Api keys',
files: [ 'build/actions/api-key.js' ],
},
{
title: 'Application',
files: [ 'build/actions/app.js' ]
},
{
title: 'Authentication',
files: [ 'build/actions/auth.js' ]
},
{
title: 'Device',
files: [ 'build/actions/device.js' ]
},
{
title: 'Environment Variables',
files: [ 'build/actions/environment-variables.js' ]
},
{
title: 'Help',
files: [ 'build/actions/help.js' ]
},
{
title: 'Information',
files: [ 'build/actions/info.js' ]
},
{
title: 'Keys',
files: [ 'build/actions/keys.js' ]
},
{
title: 'Logs',
files: [ 'build/actions/logs.js' ]
},
{
title: 'Sync',
files: [ 'build/actions/sync.js' ]
},
{
title: 'SSH',
files: [ 'build/actions/ssh.js' ]
},
{
title: 'Notes',
files: [ 'build/actions/notes.js' ]
},
{
title: 'OS',
files: [ 'build/actions/os.js' ]
},
{
title: 'Config',
files: [ 'build/actions/config.js' ]
},
{
title: 'Preload',
files: [ 'build/actions/preload.js' ]
},
{
title: 'Push',
files: [ 'build/actions/push.js' ]
},
{
title: 'Settings',
files: [ 'build/actions/settings.js' ]
},
{
title: 'Wizard',
files: [ 'build/actions/wizard.js' ]
},
{
title: 'Local',
files: [ 'build/actions/local/index.js' ]
},
{
title: 'Deploy',
files: [
'build/actions/build.js',
'build/actions/deploy.js'
]
},
{
title: 'Utilities',
files: [ 'build/actions/util.js' ]
},
]
};

112
doc/automated-init.md Normal file
View File

@ -0,0 +1,112 @@
# Provisioning Resin.io devices in automated (non-interactive) mode
This document describes how to run the `device init` command in non-interactive mode.
It requires collecting some preliminary information _once_.
The final command to provision the device looks like this:
```bash
resin device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
```
You can run this command as many times as you need, putting the new medium (SD card / USB stick) each time.
But before you can run it you need to collect the parameters and build the configuration file. Keep reading to figure out how to do it.
## Collect all the required parameters.
1. `DEVICE_TYPE`. Run
```bash
resin devices supported
```
and find the _slug_ for your target device type, like _raspberrypi3_.
1. `APP_ID`. Create an application (`resin app create APP_NAME --type DEVICE_TYPE`) or find an existing one (`resin apps`) and notice its ID.
1. `OS_VERSION`. Run
```bash
resin os versions DEVICE_TYPE
```
and pick the version that you need, like _v2.0.6+rev1.prod_.
_Note_ that even though we support _semver ranges_ it's recommended to use the exact version when doing the automated provisioning as it
guarantees full compatibility between the steps.
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
```bash
resin util available-drives
```
and get the drive name, like _/dev/sdb_ or _/dev/mmcblk0_.
The resin CLI will not display the system drives to protect you,
but still please check very carefully that you've picked the correct drive as it will be erased during the provisioning process.
Now we have all the parameters -- time to build the config file.
## Build the config file
Interactive device provisioning process often includes collecting some extra device configuration, like the networking mode and wifi credentials.
To skip this interactive step we need to buid this configuration once and save it to the JSON file for later reuse.
Let's say we will place it into the `CONFIG_FILE` path, like _./resin-os/raspberrypi3-config.json_.
We also need to put the OS image somewhere, let's call this path `OS_IMAGE_PATH`, it can be something like _./resin-os/raspberrypi3-v2.0.6+rev1.prod.img_.
1. First we need to download the OS image once. That's needed for building the config, and will speedup the subsequent operations as the downloaded OS image is placed into the local cache.
Run:
```bash
resin os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
```
1. Now we're ready to build the config:
```bash
resin os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
```
This will run you through the interactive configuration wizard and in the end save the generated config as `CONFIG_FILE`. You can then verify it's not empty:
```bash
cat CONFIG_FILE
```
## Done
Now you're ready to run the command in the beginning of this guide.
Please note again that all of these steps only need to be done once (unless you need to change something), and once all the parameters are collected the main init command can be run unchanged.
But there are still some nuances to cover, please read below.
## Nuances
### `sudo` password on *nix systems
In order to write the image to the raw device we need the root permissions, this is unavoidable.
To improve the security we only run the minimal subcommand with `sudo`.
This means that with the default setup you're interrupted closer to the end of the device init process to enter your sudo password for this subcommand to work.
There are several ways to eliminate it and make the process fully non-interactive.
#### Option 1: make passwordless sudo.
Obviously you shouldn't do that if the machine you're working on has access to any sensitive resources or information.
But if you're using a machine dedicated to resin provisioning this can be fine, and also the simplest thing to do.
#### Option 2: `NOPASSWD` directive
You can configure the `resin` CLI command to be sudo-runnable without the password. Check [this post](https://askubuntu.com/questions/159007/how-do-i-run-specific-sudo-commands-without-a-password) for an example.
### Extra initialization config
As of June 2017 all the supported devices should not require any other interactive configuration.
But by the design of our system it is _possible_ (though it doesn't look very likely it's going to happen any time soon) that some extra initialization options may be requested for the specific device types.
If that is the case please raise the issue in the resin CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
_ = require('lodash')
path = require('path')
capitanodoc = require('../../capitanodoc.json')
markdown = require('./markdown')
result = {}
result.title = capitanodoc.title
result.introduction = capitanodoc.introduction
result.categories = []
for commandCategory in capitanodoc.categories
category = {}
category.title = commandCategory.title
category.commands = []
for file in commandCategory.files
actions = require(path.join(process.cwd(), file))
if actions.signature?
category.commands.push(_.omit(actions, 'action'))
else
for actionName, actionCommand of actions
category.commands.push(_.omit(actionCommand, 'action'))
result.categories.push(category)
result.toc = _.cloneDeep(result.categories)
result.toc = _.map result.toc, (category) ->
category.commands = _.map category.commands, (command) ->
return {
signature: command.signature
anchor: '#' + command.signature
.replace(/\s/g,'-')
.replace(/</g, '60-')
.replace(/>/g, '-62-')
.replace(/\[/g, '')
.replace(/\]/g, '-')
.replace(/--/g, '-')
.replace(/\.\.\./g, '')
.replace(/\|/g, '')
.toLowerCase()
}
return category
console.log(markdown.display(result))

View File

@ -1,66 +0,0 @@
_ = require('lodash')
ent = require('ent')
utils = require('./utils')
exports.command = (command) ->
result = """
## #{ent.encode(command.signature)}
#{command.help}\n
"""
if not _.isEmpty(command.options)
result += '\n### Options'
for option in command.options
result += """
\n\n#### #{utils.parseSignature(option)}
#{option.description}
"""
result += '\n'
return result
exports.category = (category) ->
result = """
# #{category.title}\n
"""
for command in category.commands
result += '\n' + exports.command(command)
return result
exports.toc = (toc) ->
result = '''
# Table of contents\n
'''
for category in toc
result += """
\n- #{category.title}\n\n
"""
for command in category.commands
result += """
\t- [#{ent.encode(command.signature)}](#{command.anchor})\n
"""
return result
exports.display = (doc) ->
result = """
# #{doc.title}
#{doc.introduction}
#{exports.toc(doc.toc)}
"""
for category in doc.categories
result += '\n' + exports.category(category)
return result

View File

@ -1,26 +0,0 @@
_ = require('lodash')
ent = require('ent')
exports.getOptionPrefix = (signature) ->
if signature.length > 1
return '--'
else
return '-'
exports.getOptionSignature = (signature) ->
return "#{exports.getOptionPrefix(signature)}#{signature}"
exports.parseSignature = (option) ->
result = exports.getOptionSignature(option.signature)
if not _.isEmpty(option.alias)
if _.isString(option.alias)
result += ", #{exports.getOptionSignature(option.alias)}"
else
for alias in option.alias
result += ", #{exports.getOptionSignature(option.alias)}"
if option.parameter?
result += " <#{option.parameter}>"
return ent.encode(result)

View File

@ -1,34 +1,40 @@
path = require('path')
gulp = require('gulp')
coffee = require('gulp-coffee')
coffeelint = require('gulp-coffeelint')
inlinesource = require('gulp-inline-source')
mocha = require('gulp-mocha')
shell = require('gulp-shell')
packageJSON = require('./package.json')
OPTIONS =
config:
coffeelint: path.join(__dirname, 'coffeelint.json')
files:
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
app: 'lib/**/*.coffee'
tests: 'tests/**/*.spec.coffee'
pages: 'lib/auth/pages/*.ejs'
directories:
build: 'build/'
gulp.task 'coffee', [ 'lint' ], ->
gulp.task 'pages', ->
gulp.src(OPTIONS.files.pages)
.pipe(inlinesource())
.pipe(gulp.dest('build/auth/pages'))
gulp.task 'coffee', ->
gulp.src(OPTIONS.files.app)
.pipe(coffee())
.pipe(coffee(bare: true, header: true))
.pipe(gulp.dest(OPTIONS.directories.build))
gulp.task 'lint', ->
gulp.src(OPTIONS.files.coffee)
.pipe(coffeelint({
optFile: OPTIONS.config.coffeelint
gulp.task 'test', ->
gulp.src(OPTIONS.files.tests, read: false)
.pipe(mocha({
reporter: 'spec'
}))
.pipe(coffeelint.reporter())
gulp.task 'build', [
'coffee'
'coffee',
'pages'
]
gulp.task 'watch', [ 'lint', 'coffee' ], ->
gulp.watch([ OPTIONS.files.coffee ], [ 'coffee' ])
gulp.task 'watch', [ 'build' ], ->
gulp.watch([ OPTIONS.files.coffee ], [ 'build' ])

36
lib/actions/api-key.ts Normal file
View File

@ -0,0 +1,36 @@
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
export const generate: CommandDefinition<{
name: string;
}> = {
signature: 'api-key generate <name>',
description: 'Generate a new API key with the given name',
help: stripIndent`
This command generates a new API key for the current user, with the given
name. The key will be logged to the console.
This key can be used to log into the CLI using 'resin login --token <key>',
or to authenticate requests to the API with an 'Authorization: Bearer <key>' header.
Examples:
$ resin api-key generate "Jenkins Key"
`,
async action(params, _options, done) {
const resin = (await import('resin-sdk')).fromSharedOptions();
resin.models.apiKey
.create(params.name)
.then(key => {
console.log(stripIndent`
Registered api key '${params.name}':
${key}
This key will not be shown again, so please save it now.
`);
})
.finally(done);
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -22,7 +22,7 @@ exports.create =
help: '''
Use this command to create a new resin.io application.
You can specify the application type with the `--type` option.
You can specify the application device type with the `--type` option.
Otherwise, an interactive dropdown will be shown for you to select from.
You can see a list of supported device types with
@ -38,14 +38,13 @@ exports.create =
{
signature: 'type'
parameter: 'type'
description: 'application type'
description: 'application device type (Check available types with `resin devices supported`)'
alias: 't'
}
]
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
# Validate the the application name is available
@ -53,7 +52,7 @@ exports.create =
# https://github.com/resin-io/resin-cli/issues/30
resin.models.application.has(params.name).then (hasApplication) ->
if hasApplication
throw new Error('You already have an application with that name!')
patterns.exitWithExpectedError('You already have an application with that name!')
.then ->
return options.type or patterns.selectDeviceType()
@ -79,7 +78,7 @@ exports.list =
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
resin.models.application.getAll().then (applications) ->
@ -105,7 +104,7 @@ exports.info =
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
resin.models.application.get(params.name).then (application) ->
@ -130,7 +129,7 @@ exports.restart =
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
resin.models.application.restart(params.name).nodeify(done)
exports.remove =
@ -150,7 +149,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -27,7 +27,7 @@ exports.login =
- Credentials: using email/password and 2FA.
- Token: using the authentication token from the preferences page.
- Token: using a session token or API key (experimental) from the preferences page.
Examples:
@ -40,7 +40,7 @@ exports.login =
options: [
{
signature: 'token'
description: 'auth token'
description: 'session token or API key (experimental)'
parameter: 'token'
alias: 't'
}
@ -73,9 +73,8 @@ exports.login =
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
resin = require('resin-sdk')
auth = require('resin-cli-auth')
resin = require('resin-sdk').fromSharedOptions()
auth = require('../auth')
form = require('resin-cli-form')
patterns = require('../utils/patterns')
messages = require('../utils/messages')
@ -85,10 +84,15 @@ exports.login =
return Promise.try ->
return options.token if _.isString(options.token)
return form.ask
message: 'Token (from the preferences page)'
message: 'Session token or API key (experimental) from the preferences page'
name: 'token'
type: 'input'
.then(resin.auth.loginWithToken)
.tap ->
resin.auth.whoami()
.then (username) ->
if !username
patterns.exitWithExpectedError('Token authentication failed')
else if options.credentials
return patterns.authenticate(options)
else if options.web
@ -98,7 +102,8 @@ exports.login =
return patterns.askLoginType().then (loginType) ->
if loginType is 'register'
return capitano.runAsync('signup')
{ runCommand } = require('../utils/helpers')
return runCommand('signup')
options[loginType] = true
return login(options)
@ -112,11 +117,7 @@ exports.login =
console.info("Successfully logged in as: #{username}")
console.info """
Now what?
#{messages.gettingStarted}
Find out about more super powers by running:
Find out about the available commands by running:
$ resin help
@ -134,9 +135,8 @@ exports.logout =
$ resin logout
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
resin.auth.logout().nodeify(done)
exports.signup =
@ -150,15 +150,14 @@ exports.signup =
Examples:
$ resin signup
Email: me@mycompany.com
Username: johndoe
Email: johndoe@acme.com
Password: ***********
$ resin whoami
johndoe
'''
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
form = require('resin-cli-form')
validation = require('../utils/validation')
@ -170,10 +169,6 @@ exports.signup =
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Username:'
name: 'username'
type: 'input'
,
message: 'Password:'
name: 'password'
@ -198,7 +193,7 @@ exports.whoami =
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
Promise.props

145
lib/actions/build.coffee Normal file
View File

@ -0,0 +1,145 @@
# Imported here because it's needed for the setup
# of this action
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
compose = require('../utils/compose')
###
Opts must be an object with the following keys:
app: the app this build is for (optional)
arch: the architecture to build for
deviceType: the device type to build for
buildEmulated
buildOpts: arguments to forward to docker build command
###
buildProject = (docker, logger, composeOpts, opts) ->
compose.loadProject(
logger
composeOpts.projectPath
composeOpts.projectName
)
.then (project) ->
appType = opts.app?.application_type?[0]
if appType? and project.descriptors.length > 1 and not appType.supports_multicontainer
logger.logWarn(
'Target application does not support multiple containers.\n' +
'Continuing with build, but you will not be able to deploy.'
)
compose.buildProject(
docker
logger
project.path
project.name
project.composition
opts.arch
opts.deviceType
opts.buildEmulated
opts.buildOpts
composeOpts.inlineLogs
)
.then ->
logger.logSuccess('Build succeeded!')
.tapCatch (e) ->
logger.logError('Build failed')
module.exports =
signature: 'build [source]'
description: 'Build a single image or a multicontainer project locally'
primary: true
help: '''
Use this command to build an image or a complete multicontainer project
with the provided docker daemon.
You must provide either an application or a device-type/architecture
pair to use the resin Dockerfile pre-processor
(e.g. Dockerfile.template -> Dockerfile).
This command will look into the given source directory (or the current working
directory if one isn't specified) for a compose file. If one is found, this
command will build each service defined in the compose file. If a compose file
isn't found, the command will look for a Dockerfile, and if yet that isn't found,
it will try to generate one.
Examples:
$ resin build
$ resin build ./source/
$ resin build --deviceType raspberrypi3 --arch armhf --emulated
$ resin build --application MyApp ./source/
$ resin build --docker '/var/run/docker.sock'
$ resin build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem
'''
options: dockerUtils.appendOptions compose.appendOptions [
{
signature: 'arch'
parameter: 'arch'
description: 'The architecture to build for'
alias: 'A'
},
{
signature: 'deviceType'
parameter: 'deviceType'
description: 'The type of device this build is for'
alias: 'd'
},
{
signature: 'application'
parameter: 'application'
description: 'The target resin.io application this build is for'
alias: 'a'
},
]
action: (params, options, done) ->
# compositions with many services trigger misleading warnings
require('events').defaultMaxListeners = 1000
{ exitWithExpectedError } = require('../utils/patterns')
helpers = require('../utils/helpers')
Logger = require('../utils/logger')
logger = new Logger()
logger.logDebug('Parsing input...')
Promise.try ->
# `build` accepts `[source]` as a parameter, but compose expects it
# as an option. swap them here
options.source ?= params.source
delete params.source
{ application, arch, deviceType } = options
if (not (arch? and deviceType?) and not application?) or (application? and (arch? or deviceType?))
exitWithExpectedError('You must specify either an application or an arch/deviceType pair to build for')
if arch? and deviceType?
[ undefined, arch, deviceType ]
else
Promise.join(
helpers.getApplication(application)
helpers.getArchAndDeviceType(application)
(app, { arch, device_type }) ->
app.arch = arch
app.device_type = device_type
return app
)
.then (app) ->
[ app, app.arch, app.device_type ]
.then ([ app, arch, deviceType ]) ->
Promise.join(
dockerUtils.getDocker(options)
dockerUtils.generateBuildOpts(options)
compose.generateOpts(options)
(docker, buildOpts, composeOpts) ->
buildProject(docker, logger, composeOpts, {
app
arch
deviceType
buildEmulated: !!options.emulated
buildOpts
})
)
.asCallback(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -38,12 +38,34 @@ exports.optionalDevice =
description: 'device uuid'
alias: 'd'
exports.optionalDeviceApiKey =
signature: 'deviceApiKey'
description: 'custom device key - note that this is only supported on ResinOS 2.0.3+'
parameter: 'device-api-key'
alias: 'k'
exports.optionalOsVersion =
signature: 'version'
description: 'a resinOS version'
parameter: 'version'
exports.booleanDevice =
signature: 'device'
description: 'device'
boolean: true
alias: 'd'
exports.osVersion =
signature: 'version'
description: """
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
"""
parameter: 'version'
exports.network =
signature: 'network'
parameter: 'network'
@ -61,3 +83,29 @@ exports.wifiKey =
parameter: 'key'
description: 'wifi key, if network is wifi'
alias: 'k'
exports.forceUpdateLock =
signature: 'force'
description: 'force action if the update lock is set'
boolean: true
alias: 'f'
exports.drive =
signature: 'drive'
description: 'the drive to write the image to, like `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `resin util available-drives` for available options.'
parameter: 'drive'
alias: 'd'
exports.advancedConfig =
signature: 'advanced'
description: 'show advanced configuration options'
boolean: true
alias: 'v'
exports.hostOSAccess =
signature: 'host'
boolean: true
description: 'access host OS (for devices with Resin OS >= 2.7.5)'
alias: 's'

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
###
commandOptions = require('./command-options')
{ normalizeUuidProp } = require('../utils/normalization')
exports.read =
signature: 'config read'
description: 'read a device configuration'
help: '''
Use this command to read the config.json file from a provisioned device
Use this command to read the config.json file from the mounted filesystem (e.g. SD card) of a provisioned device"
Examples:
@ -28,7 +31,7 @@ exports.read =
options: [
{
signature: 'type'
description: 'device type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -46,12 +49,12 @@ exports.read =
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
prettyjson = require('prettyjson')
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
return config.read(drive, options.type)
.tap (configJSON) ->
@ -62,7 +65,7 @@ exports.write =
signature: 'config write <key> <value>'
description: 'write a device configuration'
help: '''
Use this command to write the config.json file of a provisioned device
Use this command to write the config.json file to the mounted filesystem (e.g. SD card) of a provisioned device
Examples:
@ -73,7 +76,7 @@ exports.write =
options: [
{
signature: 'type'
description: 'device type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -92,24 +95,70 @@ exports.write =
_ = require('lodash')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).then (configJSON) ->
console.info("Setting #{params.key} to #{params.value}")
_.set(configJSON, params.key, params.value)
return configJSON
.tap ->
return umount.umountAsync(drive)
return umountAsync(drive)
.then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
console.info('Done')
.nodeify(done)
exports.inject =
signature: 'config inject <file>'
description: 'inject a device configuration file'
help: '''
Use this command to inject a config.json file to the mounted filesystem
(e.g. SD card or mounted resinOS image) of a provisioned device"
Examples:
$ resin config inject my/config.json --type raspberry-pi
$ resin config inject my/config.json --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
readFileAsync = Promise.promisify(require('fs').readFile)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umountAsync)
.then (drive) ->
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
console.info('Done')
.nodeify(done)
exports.reconfigure =
signature: 'config reconfigure'
description: 'reconfigure a provisioned device'
@ -125,7 +174,7 @@ exports.reconfigure =
options: [
{
signature: 'type'
description: 'device type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -149,62 +198,131 @@ exports.reconfigure =
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
capitano = Promise.promisifyAll(require('capitano'))
umount = Promise.promisifyAll(require('umount'))
{ runCommand } = require('../utils/helpers')
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).get('uuid')
.tap ->
umount.umountAsync(drive)
umountAsync(drive)
.then (uuid) ->
configureCommand = "os configure #{drive} #{uuid}"
configureCommand = "os configure #{drive} --device #{uuid}"
if options.advanced
configureCommand += ' --advanced'
return capitano.runAsync(configureCommand)
return runCommand(configureCommand)
.then ->
console.info('Done')
.nodeify(done)
exports.generate =
signature: 'config generate <uuid>'
signature: 'config generate'
description: 'generate a config.json file'
help: '''
Use this command to generate a config.json for a device
Use this command to generate a config.json for a device or application.
Calling this command without --version is not recommended, and may fail in
future releases if the OS version cannot be inferred.
This is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
Examples:
$ resin config generate 7cf02a6
$ resin config generate 7cf02a6 --output config.json
$ resin config generate --device 7cf02a6 --version 2.12.7
$ resin config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key
$ resin config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
$ resin config generate --device 7cf02a6 --version 2.12.7 --output config.json
$ resin config generate --app MyApp --version 2.12.7
$ resin config generate --app MyApp --version 2.12.7 --output config.json
$ resin config generate --app MyApp --version 2.12.7 \
--network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
'''
options: [
commandOptions.optionalOsVersion
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
{
signature: 'generate-device-api-key'
description: 'generate a fresh device key for the device'
boolean: true
}
{
signature: 'output'
description: 'output'
parameter: 'output'
alias: 'o'
}
# Options for non-interactive configuration
{
signature: 'network'
description: 'the network type to use: ethernet or wifi'
parameter: 'network'
}
{
signature: 'wifiSsid'
description: 'the wifi ssid to use (used only if --network is set to wifi)'
parameter: 'wifiSsid'
}
{
signature: 'wifiKey'
description: 'the wifi key to use (used only if --network is set to wifi)'
parameter: 'wifiKey'
}
{
signature: 'appUpdatePollInterval'
description: 'how frequently (in minutes) to poll for application updates'
parameter: 'appUpdatePollInterval'
}
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs'))
resin = require('resin-sdk')
_ = require('lodash')
writeFileAsync = Promise.promisify(require('fs').writeFile)
resin = require('resin-sdk').fromSharedOptions()
form = require('resin-cli-form')
deviceConfig = require('resin-device-config')
prettyjson = require('prettyjson')
resin.models.device.get(params.uuid).then (device) ->
resin.models.device.getManifestBySlug(device.device_type)
.get('options')
.then(form.run)
.then(_.partial(deviceConfig.get, device.uuid))
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
{ exitWithExpectedError } = require('../utils/patterns')
if not options.device? and not options.application?
exitWithExpectedError '''
You have to pass either a device or an application.
See the help page for examples:
$ resin help config generate
'''
Promise.try ->
if options.device?
return resin.models.device.get(options.device)
return resin.models.application.get(options.application)
.then (resource) ->
resin.models.device.getManifestBySlug(resource.device_type)
.get('options')
.then (formOptions) ->
# Pass params as an override: if there is any param with exactly the same name as a
# required option, that value is used (and the corresponding question is not asked)
form.run(formOptions, override: options)
.then (answers) ->
answers.version = options.version
if resource.uuid?
generateDeviceConfig(resource, options.deviceApiKey || options['generate-device-api-key'], answers)
else
generateApplicationConfig(resource, answers)
.then (config) ->
deviceConfig.validate(config)
if options.output?
return fs.writeFileAsync(options.output, JSON.stringify(config))
return writeFileAsync(options.output, JSON.stringify(config))
console.log(prettyjson.render(config))
.nodeify(done)

220
lib/actions/deploy.coffee Normal file
View File

@ -0,0 +1,220 @@
# Imported here because it's needed for the setup
# of this action
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
compose = require('../utils/compose')
###
Opts must be an object with the following keys:
app: the application instance to deploy to
image: the image to deploy; optional
shouldPerformBuild
shouldUploadLogs
buildEmulated
buildOpts: arguments to forward to docker build command
###
deployProject = (docker, logger, composeOpts, opts) ->
_ = require('lodash')
doodles = require('resin-doodles')
sdk = require('resin-sdk').fromSharedOptions()
compose.loadProject(
logger
composeOpts.projectPath
composeOpts.projectName
opts.image
)
.then (project) ->
if project.descriptors.length > 1 and !opts.app.application_type?[0]?.supports_multicontainer
throw new Error('Target application does not support multiple containers. Aborting!')
# find which services use images that already exist locally
Promise.map project.descriptors, (d) ->
# unconditionally build (or pull) if explicitly requested
return d if opts.shouldPerformBuild
docker.getImage(d.image.tag ? d.image).inspect()
.return(d.serviceName)
.catchReturn()
.filter (d) -> !!d
.then (servicesToSkip) ->
# multibuild takes in a composition and always attempts to
# build or pull all services. we workaround that here by
# passing a modified composition.
compositionToBuild = _.cloneDeep(project.composition)
compositionToBuild.services = _.omit(compositionToBuild.services, servicesToSkip)
if _.size(compositionToBuild.services) is 0
logger.logInfo('Everything is up to date (use --build to force a rebuild)')
return {}
compose.buildProject(
docker
logger
project.path
project.name
compositionToBuild
opts.app.arch
opts.app.device_type
opts.buildEmulated
opts.buildOpts
composeOpts.inlineLogs
)
.then (builtImages) ->
_.keyBy(builtImages, 'serviceName')
.then (builtImages) ->
project.descriptors.map (d) ->
builtImages[d.serviceName] ? {
serviceName: d.serviceName,
name: d.image.tag ? d.image
logs: 'Build skipped; image for service already exists.'
props: {}
}
.then (images) ->
if opts.app.application_type?[0]?.is_legacy
chalk = require('chalk')
legacyDeploy = require('../utils/deploy-legacy')
msg = chalk.yellow('Target application requires legacy deploy method.')
logger.logWarn(msg)
return Promise.join(
docker
logger
sdk.auth.getToken()
sdk.auth.whoami()
sdk.settings.get('resinUrl')
{
appName: opts.app.app_name
imageName: images[0].name
buildLogs: images[0].logs
shouldUploadLogs: opts.shouldUploadLogs
}
legacyDeploy
)
.then (releaseId) ->
sdk.models.release.get(releaseId, $select: [ 'commit' ])
Promise.join(
sdk.auth.getUserId()
sdk.auth.getToken()
sdk.settings.get('apiUrl')
(userId, auth, apiEndpoint) ->
compose.deployProject(
docker
logger
project.composition
images
opts.app.id
userId
"Bearer #{auth}"
apiEndpoint
!opts.shouldUploadLogs
)
)
.then (release) ->
logger.logSuccess('Deploy succeeded!')
logger.logSuccess("Release: #{release.commit}")
console.log()
console.log(doodles.getDoodle()) # Show charlie
console.log()
.tapCatch (e) ->
logger.logError('Deploy failed')
module.exports =
signature: 'deploy <appName> [image]'
description: 'Deploy a single image or a multicontainer project to a resin.io application'
help: '''
Use this command to deploy an image or a complete multicontainer project
to an application, optionally building it first.
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
Unless an image is specified, this command will look into the current directory
(or the one specified by --source) for a compose file. If one is found, this
command will deploy each service defined in the compose file, building it first
if an image for it doesn't exist. If a compose file isn't found, the command
will look for a Dockerfile, and if yet that isn't found, it will try to
generate one.
To deploy to an app on which you're a collaborator, use
`resin deploy <appOwnerUsername>/<appName>`.
Note: If building with this command, all options supported by `resin build`
are also supported with this command.
Examples:
$ resin deploy myApp
$ resin deploy myApp --build --source myBuildDir/
$ resin deploy myApp myApp/myImage
'''
permission: 'user'
primary: true
options: dockerUtils.appendOptions compose.appendOptions [
{
signature: 'source'
parameter: 'source'
description: 'Specify an alternate source directory; default is the working directory'
alias: 's'
},
{
signature: 'build'
boolean: true
description: 'Force a rebuild before deploy'
alias: 'b'
},
{
signature: 'nologupload'
description: "Don't upload build logs to the dashboard with image (if building)"
boolean: true
}
]
action: (params, options, done) ->
# compositions with many services trigger misleading warnings
require('events').defaultMaxListeners = 1000
helpers = require('../utils/helpers')
Logger = require('../utils/logger')
logger = new Logger()
logger.logDebug('Parsing input...')
Promise.try ->
{ appName, image } = params
# look into "resin build" options if appName isn't given
appName = options.application if not appName?
delete options.application
if not appName?
throw new Error('Please specify the name of the application to deploy')
if image? and options.build
throw new Error('Build option is not applicable when specifying an image')
Promise.join(
helpers.getApplication(appName)
helpers.getArchAndDeviceType(appName)
(app, { arch, device_type }) ->
app.arch = arch
app.device_type = device_type
return app
)
.then (app) ->
[ app, image, !!options.build, !options.nologupload ]
.then ([ app, image, shouldPerformBuild, shouldUploadLogs ]) ->
Promise.join(
dockerUtils.getDocker(options)
dockerUtils.generateBuildOpts(options)
compose.generateOpts(options)
(docker, buildOpts, composeOpts) ->
deployProject(docker, logger, composeOpts, {
app
image
shouldPerformBuild
shouldUploadLogs
buildEmulated: !!options.emulated
buildOpts
})
)
.asCallback(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,6 +15,12 @@ limitations under the License.
###
commandOptions = require('./command-options')
_ = require('lodash')
{ normalizeUuidProp } = require('../utils/normalization')
expandForAppName = {
$expand: belongs_to__application: $select: 'app_name'
}
exports.list =
signature: 'devices'
@ -36,28 +42,32 @@ exports.list =
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
Promise.try ->
if options.application?
return resin.models.device.getAllByApplication(options.application)
return resin.models.device.getAll()
return resin.models.device.getAllByApplication(options.application, expandForAppName)
return resin.models.device.getAll(expandForAppName)
.tap (devices) ->
devices = _.map devices, (device) ->
device.dashboard_url = resin.models.device.getDashboardUrl(device.uuid)
device.application_name = device.belongs_to__application[0].app_name
device.uuid = device.uuid.slice(0, 7)
return device
console.log visuals.table.horizontal devices, [
'id'
'uuid'
'name'
'device_name'
'device_type'
'application_name'
'status'
'is_online'
'supervisor_version'
'os_version'
'dashboard_url'
]
.nodeify(done)
@ -74,16 +84,20 @@ exports.info =
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk')
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.device.get(params.uuid).then (device) ->
resin.models.device.get(params.uuid, expandForAppName)
.then (device) ->
resin.models.device.getStatus(device).then (status) ->
device.status = status
device.dashboard_url = resin.models.device.getDashboardUrl(device.uuid)
device.application_name = device.belongs_to__application[0].app_name
device.commit = device.is_on__commit
console.log visuals.table.vertical device, [
"$#{device.name}$"
"$#{device.device_name}$"
'id'
'device_type'
'status'
@ -96,9 +110,32 @@ exports.info =
'supervisor_version'
'is_web_accessible'
'note'
'os_version'
'dashboard_url'
]
.nodeify(done)
exports.supported =
signature: 'devices supported'
description: 'list all supported devices'
help: '''
Use this command to get the list of all supported devices
Examples:
$ resin devices supported
'''
action: (params, options, done) ->
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.config.getDeviceTypes().then (deviceTypes) ->
console.log visuals.table.horizontal deviceTypes, [
'slug'
'name'
]
.nodeify(done)
exports.register =
signature: 'device register <application>'
description: 'register a device'
@ -108,25 +145,28 @@ exports.register =
Examples:
$ resin device register MyApp
$ resin device register MyApp --uuid <uuid>
'''
permission: 'user'
options: [
signature: 'uuid'
description: 'custom uuid'
parameter: 'uuid'
alias: 'u'
{
signature: 'uuid'
description: 'custom uuid'
parameter: 'uuid'
alias: 'u'
}
]
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
resin.models.application.get(params.application).then (application) ->
Promise.try ->
return options.uuid or resin.models.device.generateUUID()
.then (uuid) ->
Promise.join(
resin.models.application.get(params.application)
options.uuid ? resin.models.device.generateUniqueKey()
(application, uuid) ->
console.info("Registering to #{application.app_name}: #{uuid}")
return resin.models.device.register(application.app_name, uuid)
return resin.models.device.register(application.id, uuid)
)
.get('uuid')
.nodeify(done)
@ -147,7 +187,8 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
@ -168,7 +209,8 @@ exports.identify =
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.identify(params.uuid).nodeify(done)
exports.reboot =
@ -181,10 +223,97 @@ exports.reboot =
$ resin device reboot 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin.models.device.reboot(params.uuid).nodeify(done)
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.reboot(params.uuid, options).nodeify(done)
exports.shutdown =
signature: 'device shutdown <uuid>'
description: 'shutdown a device'
help: '''
Use this command to remotely shutdown a device
Examples:
$ resin device shutdown 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.shutdown(params.uuid, options).nodeify(done)
exports.enableDeviceUrl =
signature: 'device public-url enable <uuid>'
description: 'enable public URL for a device'
help: '''
Use this command to enable public URL for a device
Examples:
$ resin device public-url enable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.enableDeviceUrl(params.uuid).nodeify(done)
exports.disableDeviceUrl =
signature: 'device public-url disable <uuid>'
description: 'disable public URL for a device'
help: '''
Use this command to disable public URL for a device
Examples:
$ resin device public-url disable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.disableDeviceUrl(params.uuid).nodeify(done)
exports.getDeviceUrl =
signature: 'device public-url <uuid>'
description: 'gets the public URL of a device'
help: '''
Use this command to get the public URL of a device
Examples:
$ resin device public-url 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.getDeviceUrl(params.uuid).then (url) ->
console.log(url)
.nodeify(done)
exports.hasDeviceUrl =
signature: 'device public-url status <uuid>'
description: 'Returns true if public URL is enabled for a device'
help: '''
Use this command to determine if public URL is enabled for a device
Examples:
$ resin device public-url status 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
console.log(hasDeviceUrl)
.nodeify(done)
exports.rename =
signature: 'device rename <uuid> [newName]'
@ -201,9 +330,9 @@ exports.rename =
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
form = require('resin-cli-form')
Promise.try ->
@ -232,15 +361,15 @@ exports.move =
permission: 'user'
options: [ commandOptions.optionalApplication ]
action: (params, options, done) ->
resin = require('resin-sdk')
_ = require('lodash')
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
resin.models.device.get(params.uuid).then (device) ->
resin.models.device.get(params.uuid, expandForAppName).then (device) ->
return options.application or patterns.selectApplication (application) ->
return _.all [
return _.every [
application.device_type is device.device_type
device.application_name isnt application.app_name
device.belongs_to__application[0].app_name isnt application.app_name
]
.tap (application) ->
return resin.models.device.move(params.uuid, application)
@ -250,7 +379,7 @@ exports.move =
exports.init =
signature: 'device init'
description: 'initialise a device with resin os'
description: 'initialise a device with resinOS'
help: '''
Use this command to download the OS image of a certain application and write it to an SD Card.
@ -265,24 +394,26 @@ exports.init =
options: [
commandOptions.optionalApplication
commandOptions.yes
commandOptions.advancedConfig
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
commandOptions.drive
{
signature: 'advanced'
description: 'enable advanced configuration'
boolean: true
alias: 'v'
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
parameter: 'config'
}
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
rimraf = Promise.promisify(require('rimraf'))
tmp = Promise.promisifyAll(require('tmp'))
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
tmp.setGracefulCleanup()
resin = require('resin-sdk')
helpers = require('../utils/helpers')
resin = require('resin-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
{ runCommand } = require('../utils/helpers')
Promise.try ->
return options.application if options.application?
@ -291,26 +422,29 @@ exports.init =
.then (application) ->
download = ->
tmp.tmpNameAsync().then (temporalPath) ->
capitano.runAsync("os download #{application.device_type} --output #{temporalPath}")
.disposer (temporalPath) ->
return rimraf(temporalPath)
tmpNameAsync().then (tempPath) ->
osVersion = options['os-version'] or 'default'
runCommand("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
.disposer (tempPath) ->
return rimraf(tempPath)
Promise.using download(), (temporalPath) ->
capitano.runAsync("device register #{application.app_name}")
Promise.using download(), (tempPath) ->
runCommand("device register #{application.app_name}")
.then(resin.models.device.get)
.tap (device) ->
configure = "os configure #{temporalPath} #{device.uuid}"
configure += ' --advanced' if options.advanced
capitano.runAsync(configure).then ->
message = '''
Initializing a device requires administrative permissions
given that we need to access raw devices directly.
'''
helpers.sudo([ 'os', 'initialize', temporalPath, '--type', application.device_type ], message)
configureCommand = "os configure '#{tempPath}' --device #{device.uuid}"
if options.config
configureCommand += " --config '#{options.config}'"
else if options.advanced
configureCommand += ' --advanced'
runCommand(configureCommand)
.then ->
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
if options.yes
osInitCommand += ' --yes'
if options.drive
osInitCommand += " --drive #{options.drive}"
runCommand(osInitCommand)
# Make sure the device resource is removed if there is an
# error when configuring or initializing a device image
.catch (error) ->

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,6 +15,7 @@ limitations under the License.
###
commandOptions = require('./command-options')
{ normalizeUuidProp } = require('../utils/normalization')
exports.list =
signature: 'envs'
@ -27,6 +28,10 @@ exports.list =
If you want to see all environment variables, including private
ones used by resin, use the verbose option.
At the moment the CLI doesn't fully support multi-container applications,
so the following commands will only show service variables,
without showing which service they belong to.
Example:
$ resin envs --application MyApp
@ -46,22 +51,25 @@ exports.list =
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
{ exitWithExpectedError } = require('../utils/patterns')
Promise.try ->
if options.application?
return resin.models.environmentVariables.getAllByApplication(options.application)
else if options.device?
return resin.models.environmentVariables.device.getAll(options.device)
else
throw new Error('You must specify an application or device')
exitWithExpectedError('You must specify an application or device')
.tap (environmentVariables) ->
if _.isEmpty(environmentVariables)
throw new Error('No environment variables found')
exitWithExpectedError('No environment variables found')
if not options.verbose
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
@ -98,7 +106,7 @@ exports.remove =
]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
@ -114,6 +122,10 @@ exports.add =
help: '''
Use this command to add an enviroment variable to an application.
At the moment the CLI doesn't fully support multi-container applications,
so the following commands will only set service variables for the first
service in your application.
If value is omitted, the tool will attempt to use the variable's value
as defined in your host machine.
@ -135,8 +147,11 @@ exports.add =
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
{ exitWithExpectedError } = require('../utils/patterns')
Promise.try ->
if not params.value?
@ -152,7 +167,7 @@ exports.add =
else if options.device?
resin.models.environmentVariables.device.create(options.device, params.key, params.value)
else
throw new Error('You must specify an application or device')
exitWithExpectedError('You must specify an application or device')
.nodeify(done)
exports.rename =
@ -172,7 +187,7 @@ exports.rename =
options: [ commandOptions.booleanDevice ]
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk')
resin = require('resin-sdk-preconfigured')
Promise.try ->
if options.device

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,13 +15,13 @@ limitations under the License.
###
_ = require('lodash')
_.str = require('underscore.string')
capitano = require('capitano')
columnify = require('columnify')
messages = require('../utils/messages')
{ exitWithExpectedError } = require('../utils/patterns')
parse = (object) ->
return _.object _.map object, (item) ->
return _.fromPairs _.map object, (item) ->
# Hacky way to determine if an object is
# a function or a command
@ -36,7 +36,7 @@ parse = (object) ->
]
indent = (text) ->
text = _.map _.str.lines(text), (line) ->
text = _.map text.split('\n'), (line) ->
return ' ' + line
return text.join('\n')
@ -47,29 +47,22 @@ print = (data) ->
general = (params, options, done) ->
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
console.log("#{messages.gettingStarted}\n")
console.log(messages.reachingOut)
console.log('\nPrimary commands:\n')
# We do not want the wildcard command
# to be printed in the help screen.
commands = _.reject capitano.state.commands, (command) ->
return command.isWildcard()
return command.hidden or command.isWildcard()
groupedCommands = _.groupBy commands, (command) ->
if command.plugin
return 'plugins'
else if command.primary
if command.primary
return 'primary'
return 'secondary'
print(parse(groupedCommands.primary))
if options.verbose
if not _.isEmpty(groupedCommands.plugins)
console.log('\nInstalled plugins:\n')
print(parse(groupedCommands.plugins))
console.log('\nAdditional commands:\n')
print(parse(groupedCommands.secondary))
else
@ -86,14 +79,14 @@ command = (params, options, done) ->
return done(error) if error?
if not command? or command.isWildcard()
return done(new Error("Command not found: #{params.command}"))
exitWithExpectedError("Command not found: #{params.command}")
console.log("Usage: #{command.signature}")
if command.help?
console.log("\n#{command.help}")
else if command.description?
console.log("\n#{_.str.humanize(command.description)}")
console.log("\n#{_.capitalize(command.description)}")
if not _.isEmpty(command.options)
console.log('\nOptions:\n')

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,15 +16,27 @@ limitations under the License.
module.exports =
wizard: require('./wizard')
apiKey: require('./api-key')
app: require('./app')
info: require('./info')
auth: require('./auth')
info: require('./info')
device: require('./device')
env: require('./environment-variables')
keys: require('./keys')
logs: require('./logs')
local: require('./local')
notes: require('./notes')
help: require('./help')
os: require('./os')
settings: require('./settings')
config: require('./config')
sync: require('./sync')
ssh: require('./ssh')
internal: require('./internal')
build: require('./build')
deploy: require('./deploy')
util: require('./util')
preload: require('./preload')
push: require('./push')
join: require('./join')
leave: require('./leave')

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -12,23 +12,19 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
*/
exports.list =
signature: 'settings'
description: 'print current settings'
help: '''
Use this command to display detected settings
import { CommandDefinition } from 'capitano';
Examples:
$ resin settings
'''
action: (params, options, done) ->
resin = require('resin-sdk')
prettyjson = require('prettyjson')
resin.settings.getAll()
.then(prettyjson.render)
.then(console.log)
.nodeify(done)
export const version: CommandDefinition = {
signature: 'version',
description: 'output the version number',
help: `\
Display the Resin CLI version.\
`,
async action(_params, _options, done) {
const packageJSON = await import('../../package.json');
console.log(packageJSON.version);
return done();
},
};

View File

@ -0,0 +1,87 @@
###
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
# These are internal commands we want to be runnable from the outside
# One use-case for this is spawning the minimal operation with root priviledges
exports.osInit =
signature: 'internal osinit <image> <type> <config>'
description: 'do actual init of the device with the preconfigured os image'
help: '''
Don't use this command directly! Use `resin os initialize <image>` instead.
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
init = require('resin-device-init')
helpers = require('../utils/helpers')
return Promise.try ->
config = JSON.parse(params.config)
init.initialize(params.image, params.type, config)
.then(helpers.osProgressHandler)
.nodeify(done)
exports.scanDevices =
signature: 'internal scandevices'
description: 'scan for local resin-enabled devices and show a picker to choose one'
help: '''
Don't use this command directly!
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
{ forms } = require('resin-sync')
return Promise.try ->
forms.selectLocalResinOsDevice()
.then (hostnameOrIp) ->
console.error("==> Selected device: #{hostnameOrIp}")
.nodeify(done)
exports.sudo =
signature: 'internal sudo <command>'
description: 'execute arbitrary commands in a privileged subprocess'
help: '''
Don't use this command directly!
<command> must be passed as a single argument. That means, you need to make sure
you enclose <command> in quotes (eg. resin internal sudo 'ls -alF') if for
whatever reason you invoke the command directly or, typically, pass <command>
as a single argument to spawn (eg. `spawn('resin', [ 'internal', 'sudo', 'ls -alF' ])`).
Furthermore, this command will naively split <command> on whitespace and directly
forward the parts as arguments to `sudo`, so be careful.
'''
hidden: true
action: (params, options, done) ->
os = require('os')
Promise = require('bluebird')
return Promise.try ->
if os.platform() is 'win32'
windosu = require('windosu')
windosu.exec(params.command, {})
else
{ spawn } = require('child_process')
{ wait } = require('rindle')
cmd = params.command.split(' ')
ps = spawn('sudo', cmd, stdio: 'inherit', env: process.env)
wait(ps)
.nodeify(done)

62
lib/actions/join.ts Normal file
View File

@ -0,0 +1,62 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
interface Args {
deviceIp?: string;
}
interface Options {
application?: string;
}
export const join: CommandDefinition<Args, Options> = {
signature: 'join [deviceIp]',
description:
'Promote a local device running unmanaged resinOS to join a resin.io application',
help: stripIndent`
Examples:
$ resin join
$ resin join resin.local
$ resin join resin.local --application MyApp
$ resin join 192.168.1.25
$ resin join 192.168.1.25 --application MyApp
`,
options: [
{
signature: 'application',
parameter: 'application',
alias: 'a',
description: 'The name of the application the device should join',
},
],
primary: true,
async action(params, options, done) {
const resin = await import('resin-sdk');
const Logger = await import('../utils/logger');
const promote = await import('../utils/promote');
const sdk = resin.fromSharedOptions();
const logger = new Logger();
return Bluebird.try(() => {
return promote.join(logger, sdk, params.deviceIp, options.application);
}).nodeify(done);
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ exports.list =
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.key.getAll().then (keys) ->
@ -50,7 +50,7 @@ exports.info =
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.key.get(params.id).then (key) ->
@ -82,7 +82,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
@ -107,13 +107,14 @@ exports.add =
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs'))
readFileAsync = Promise.promisify(require('fs').readFile)
capitano = require('capitano')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
Promise.try ->
return fs.readFileAsync(params.path, encoding: 'utf8') if params.path?
return readFileAsync(params.path, encoding: 'utf8') if params.path?
# TODO: should this be promisified for consistency?
Promise.fromNode (callback) ->
capitano.utils.getStdin (data) ->
return callback(null, data)

49
lib/actions/leave.ts Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
interface Args {
deviceIp?: string;
}
export const leave: CommandDefinition<Args, {}> = {
signature: 'leave [deviceIp]',
description: 'Detach a local device from its resin.io application',
help: stripIndent`
Examples:
$ resin leave
$ resin leave resin.local
$ resin leave 192.168.1.25
`,
options: [],
permission: 'user',
primary: true,
async action(params, _options, done) {
const resin = await import('resin-sdk');
const Logger = await import('../utils/logger');
const promote = await import('../utils/promote');
const sdk = resin.fromSharedOptions();
const logger = new Logger();
return Bluebird.try(() => {
return promote.leave(logger, sdk, params.deviceIp);
}).nodeify(done);
},
};

View File

@ -0,0 +1,62 @@
Promise = require('bluebird')
_ = require('lodash')
form = require('resin-cli-form')
chalk = require('chalk')
dockerUtils = require('../../utils/docker')
{ exitWithExpectedError } = require('../../utils/patterns')
exports.dockerPort = dockerPort = 2375
exports.dockerTimeout = dockerTimeout = 2000
exports.filterOutSupervisorContainer = filterOutSupervisorContainer = (container) ->
for name in container.Names
return false if name.includes('resin_supervisor')
return true
exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor = false) ->
docker = dockerUtils.createClient(host: deviceIp, port: dockerPort, timeout: dockerTimeout)
# List all containers, including those not running
docker.listContainersAsync(all: true)
.filter (container) ->
return true if not filterSupervisor
filterOutSupervisorContainer(container)
.then (containers) ->
if _.isEmpty(containers)
exitWithExpectedError("No containers found in #{deviceIp}")
return form.ask
message: 'Select a container'
type: 'list'
choices: _.map containers, (container) ->
containerName = container.Names?[0] or 'Untitled'
shortContainerId = ('' + container.Id).substr(0, 11)
return {
name: "#{containerName} (#{shortContainerId})"
value: container.Id
}
exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follow = false }) ->
docker = dockerUtils.createClient(host: deviceIp, port: dockerPort)
container = docker.getContainer(name)
container.inspectAsync()
.then (containerInfo) ->
return containerInfo?.State?.Running
.then (isRunning) ->
container.attachAsync
logs: not follow or not isRunning
stream: follow and isRunning
stdout: true
stderr: true
.then (containerStream) ->
containerStream.pipe(outStream)
.catch (err) ->
err = '' + err.statusCode
if err is '404'
return console.log(chalk.red.bold("Container '#{name}' not found."))
throw err
exports.getSubShellCommand = require('../../utils/helpers').getSubShellCommand

View File

@ -0,0 +1,230 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
BOOT_PARTITION = 1
CONNECTIONS_FOLDER = '/system-connections'
getConfigurationSchema = (connnectionFileName = 'resin-wifi') ->
mapper: [
{
template:
persistentLogging: '{{persistentLogging}}'
domain: [
[ 'config_json', 'persistentLogging' ]
]
}
{
template:
hostname: '{{hostname}}'
domain: [
[ 'config_json', 'hostname' ]
]
}
{
template:
wifi:
ssid: '{{networkSsid}}'
'wifi-security':
psk: '{{networkKey}}'
domain: [
[ 'system_connections', connnectionFileName, 'wifi' ]
[ 'system_connections', connnectionFileName, 'wifi-security' ]
]
}
]
files:
system_connections:
fileset: true
type: 'ini'
location:
path: CONNECTIONS_FOLDER.slice(1)
# Reconfix still uses the older resin-image-fs, so still needs an
# object-based partition definition.
partition: BOOT_PARTITION
config_json:
type: 'json'
location:
path: 'config.json'
partition: BOOT_PARTITION
inquirerOptions = (data) -> [
{
message: 'Network SSID'
type: 'input'
name: 'networkSsid'
default: data.networkSsid
}
{
message: 'Network Key'
type: 'input'
name: 'networkKey'
default: data.networkKey
}
{
message: 'Do you want to set advanced settings?'
type: 'confirm'
name: 'advancedSettings'
default: false
}
{
message: 'Device Hostname'
type: 'input'
name: 'hostname'
default: data.hostname,
when: (answers) ->
answers.advancedSettings
}
{
message: 'Do you want to enable persistent logging?'
type: 'confirm'
name: 'persistentLogging'
default: data.persistentLogging
when: (answers) ->
answers.advancedSettings
}
]
getConfiguration = (data) ->
_ = require('lodash')
inquirer = require('inquirer')
# `persistentLogging` can be `undefined`, so we want
# to make sure that case defaults to `false`
data = _.assign data,
persistentLogging: data.persistentLogging or false
inquirer.prompt(inquirerOptions(data))
.then (answers) ->
return _.merge(data, answers)
# Taken from https://goo.gl/kr1kCt
CONNECTION_FILE = '''
[connection]
id=resin-wifi
type=wifi
[wifi]
hidden=true
mode=infrastructure
ssid=My_Wifi_Ssid
[wifi-security]
auth-alg=open
key-mgmt=wpa-psk
psk=super_secret_wifi_password
[ipv4]
method=auto
[ipv6]
addr-gen-mode=stable-privacy
method=auto
'''
###
* if the `resin-wifi` file exists (previously configured image or downloaded from the UI) it's used and reconfigured
* if the `resin-sample.ignore` exists it's copied to `resin-wifi`
* if the `resin-sample` exists it's reconfigured (legacy mode, will be removed eventually)
* otherwise, the new file is created
###
prepareConnectionFile = (target) ->
_ = require('lodash')
imagefs = require('resin-image-fs')
imagefs.listDirectory
image: target
partition: BOOT_PARTITION
path: CONNECTIONS_FOLDER
.then (files) ->
# The required file already exists
if _.includes(files, 'resin-wifi')
return null
# Fresh image, new mode, accoding to https://github.com/resin-os/meta-resin/pull/770/files
if _.includes(files, 'resin-sample.ignore')
return imagefs.copy
image: target
partition: BOOT_PARTITION
path: "#{CONNECTIONS_FOLDER}/resin-sample.ignore"
,
image: target
partition: BOOT_PARTITION
path: "#{CONNECTIONS_FOLDER}/resin-wifi"
.thenReturn(null)
# Legacy mode, to be removed later
# We return the file name override from this branch
# When it is removed the following cleanup should be done:
# * delete all the null returns from this method
# * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
# * drop the final `then` from this method
# * adapt the code in the main listener to not receive the config from this method, and use that constant instead
if _.includes(files, 'resin-sample')
return 'resin-sample'
# In case there's no file at all (shouldn't happen normally, but the file might have been removed)
return imagefs.writeFile
image: target
partition: BOOT_PARTITION
path: "#{CONNECTIONS_FOLDER}/resin-wifi"
, CONNECTION_FILE
.thenReturn(null)
.then (connectionFileName) ->
return getConfigurationSchema(connectionFileName)
removeHostname = (schema) ->
_ = require('lodash')
schema.mapper = _.reject schema.mapper, (mapper) ->
_.isEqual(Object.keys(mapper.template), ['hostname'])
module.exports =
signature: 'local configure <target>'
description: '(Re)configure a resinOS drive or image'
help: '''
Use this command to configure or reconfigure a resinOS drive or image.
Examples:
$ resin local configure /dev/sdc
$ resin local configure path/to/image.img
'''
root: true
action: (params, options, done) ->
Promise = require('bluebird')
umount = require('umount')
umountAsync = Promise.promisify(umount.umount)
isMountedAsync = Promise.promisify(umount.isMounted)
reconfix = require('reconfix')
denymount = Promise.promisify(require('denymount'))
prepareConnectionFile(params.target)
.tap ->
isMountedAsync(params.target).then (isMounted) ->
return if not isMounted
umountAsync(params.target)
.then (configurationSchema) ->
denymount params.target, (cb) ->
reconfix.readConfiguration(configurationSchema, params.target)
.then(getConfiguration)
.then (answers) ->
if not answers.hostname
removeHostname(configurationSchema)
reconfix.writeConfiguration(configurationSchema, answers, params.target)
.asCallback(cb)
.then ->
console.log('Done!')
.asCallback(done)

View File

@ -0,0 +1,120 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
module.exports =
signature: 'local flash <image>'
description: 'Flash an image to a drive'
help: '''
Use this command to flash a resinOS image to a drive.
Examples:
$ resin local flash path/to/resinos.img
$ resin local flash path/to/resinos.img --drive /dev/disk2
$ resin local flash path/to/resinos.img --drive /dev/disk2 --yes
'''
options: [
signature: 'yes'
boolean: true
description: 'confirm non-interactively'
alias: 'y'
,
signature: 'drive'
parameter: 'drive'
description: 'drive'
alias: 'd'
]
root: true
action: (params, options, done) ->
_ = require('lodash')
os = require('os')
Promise = require('bluebird')
umountAsync = Promise.promisify(require('umount').umount)
fs = Promise.promisifyAll(require('fs'))
driveListAsync = Promise.promisify(require('drivelist').list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
imageWrite = require('etcher-image-write')
form.run [
{
message: 'Select drive'
type: 'drive'
name: 'drive'
},
{
message: 'This will erase the selected drive. Are you sure?'
type: 'confirm'
name: 'yes'
default: false
}
],
override:
drive: options.drive
# If `options.yes` is `false`, pass `undefined`,
# otherwise the question will not be asked because
# `false` is a defined value.
yes: options.yes || undefined
# TODO: dedupe with the resin-device-operations
.then (answers) ->
if answers.yes isnt true
console.log(chalk.red.bold('Aborted image flash'))
process.exit(0)
driveListAsync().then (drives) ->
selectedDrive = _.find(drives, device: answers.drive)
if not selectedDrive?
throw new Error("Drive not found: #{answers.drive}")
return selectedDrive
.then (selectedDrive) ->
progressBars =
write: new visuals.Progress('Flashing')
check: new visuals.Progress('Validating')
umountAsync(selectedDrive.device).then ->
Promise.props
imageSize: fs.statAsync(params.image).get('size'),
imageStream: Promise.resolve(fs.createReadStream(params.image))
driveFileDescriptor: fs.openAsync(selectedDrive.raw, 'rs+')
.then (results) ->
imageWrite.write
fd: results.driveFileDescriptor
device: selectedDrive.raw
size: selectedDrive.size
,
stream: results.imageStream,
size: results.imageSize
,
check: true
.then (writer) ->
new Promise (resolve, reject) ->
writer.on 'progress', (state) ->
progressBars[state.type].update(state)
writer.on('error', reject)
writer.on('done', resolve)
.then ->
if (os.platform() is 'win32') and selectedDrive.mountpoint?
ejectAsync = Promise.promisify(require('removedrive').eject)
return ejectAsync(selectedDrive.mountpoint)
return umountAsync(selectedDrive.device)
.asCallback(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,13 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
###
exports.version =
signature: 'version'
description: 'output the version number'
help: '''
Display the Resin CLI version.
'''
action: (params, options, done) ->
packageJSON = require('../../package.json')
console.log(packageJSON.version)
return done()
exports.configure = require('./configure')
exports.flash = require('./flash')
exports.logs = require('./logs')
exports.scan = require('./scan')
exports.ssh = require('./ssh')
exports.push = require('./push')
exports.stop = require('./stop')

View File

@ -0,0 +1,66 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
#
module.exports =
signature: 'local logs [deviceIp]'
description: 'Get or attach to logs of a running container on a resinOS device'
help: '''
Examples:
$ resin local logs
$ resin local logs -f
$ resin local logs 192.168.1.10
$ resin local logs 192.168.1.10 -f
$ resin local logs 192.168.1.10 -f --app-name myapp
'''
options: [
signature: 'follow'
boolean: true
description: 'follow log'
alias: 'f'
,
signature: 'app-name'
parameter: 'name'
description: 'name of container to get logs from'
alias: 'a'
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
{ forms } = require('resin-sync')
{ selectContainerFromDevice, pipeContainerStream } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (@deviceIp) =>
if not options['app-name']?
return selectContainerFromDevice(@deviceIp)
return options['app-name']
.then (appName) =>
pipeContainerStream
deviceIp: @deviceIp
name: appName
outStream: process.stdout
follow: options['follow']

View File

@ -0,0 +1,78 @@
###
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
# Loads '.resin-sync.yml' configuration from 'source' directory.
# Returns the configuration object on success
#
_ = require('lodash')
resinPush = require('resin-sync').capitano('resin-toolbox')
# TODO: This is a temporary workaround to reuse the existing `rdt push`
# capitano frontend in `resin local push`.
resinPushHelp = '''
Warning: 'resin local push' requires an openssh-compatible client and 'rsync' to
be correctly installed in your shell environment. For more information (including
Windows support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to push your local changes to a container on a LAN-accessible resinOS device on the fly.
If `Dockerfile` or any file in the 'build-triggers' list is changed,
a new container will be built and run on your device.
If not, changes will simply be synced with `rsync` into the application container.
After every 'resin local push' the updated settings will be saved in
'<source>/.resin-sync.yml' and will be used in later invocations. You can
also change any option by editing '.resin-sync.yml' directly.
Here is an example '.resin-sync.yml' :
$ cat $PWD/.resin-sync.yml
local_resinos:
app-name: local-app
build-triggers:
- Dockerfile: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
- package.json: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
environment:
- MY_VARIABLE=123
Command line options have precedence over the ones saved in '.resin-sync.yml'.
If '.gitignore' is found in the source directory then all explicitly listed files will be
excluded when using rsync to update the container. You can choose to change this default behavior with the
'--skip-gitignore' option.
Examples:
$ resin local push
$ resin local push --app-name test-server --build-triggers package.json,requirements.txt
$ resin local push --force-build
$ resin local push --force-build --skip-logs
$ resin local push --ignore lib/
$ resin local push --verbose false
$ resin local push 192.168.2.10 --source . --destination /usr/src/app
$ resin local push 192.168.2.10 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
'''
module.exports = _.assign resinPush,
signature: 'local push [deviceIp]'
help: resinPushHelp
primary: true
root: true

View File

@ -0,0 +1,100 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
dockerInfoProperties = [
'Containers'
'ContainersRunning'
'ContainersPaused'
'ContainersStopped'
'Images'
'Driver'
'SystemTime'
'KernelVersion'
'OperatingSystem'
'Architecture'
]
dockerVersionProperties = [
'Version'
'ApiVersion'
]
module.exports =
signature: 'local scan'
description: 'Scan for resinOS devices in your local network'
help: '''
Examples:
$ resin local scan
$ resin local scan --timeout 120
$ resin local scan --verbose
'''
options: [
signature: 'verbose'
boolean: true
description: 'Display full info'
alias: 'v'
,
signature: 'timeout'
parameter: 'timeout'
description: 'Scan timeout in seconds'
alias: 't'
]
primary: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
prettyjson = require('prettyjson')
{ discover } = require('resin-sync')
{ SpinnerPromise } = require('resin-cli-visuals')
{ dockerPort, dockerTimeout } = require('./common')
dockerUtils = require('../../utils/docker')
{ exitWithExpectedError } = require('../../utils/patterns')
if options.timeout?
options.timeout *= 1000
Promise.try ->
new SpinnerPromise
promise: discover.discoverLocalResinOsDevices(options.timeout)
startMessage: 'Scanning for local resinOS devices..'
stopMessage: 'Reporting scan results'
.filter ({ address }) ->
Promise.try ->
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
docker.pingAsync()
.return(true)
.catchReturn(false)
.tap (devices) ->
if _.isEmpty(devices)
exitWithExpectedError('Could not find any resinOS devices in the local network')
.map ({ host, address }) ->
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
Promise.props
dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info')
dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version')
.then ({ dockerInfo, dockerVersion }) ->
if not options.verbose
dockerInfo = _.pick(dockerInfo, dockerInfoProperties) if _.isObject(dockerInfo)
dockerVersion = _.pick(dockerVersion, dockerVersionProperties) if _.isObject(dockerVersion)
return { host, address, dockerInfo, dockerVersion }
.then (devicesInfo) ->
console.log(prettyjson.render(devicesInfo, noColor: true))
.nodeify(done)

View File

@ -0,0 +1,114 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
{ hostOSAccess } = require('../command-options')
_ = require('lodash')
localHostOSAccessOption = _.cloneDeep(hostOSAccess)
localHostOSAccessOption.description = 'get a shell into the host OS'
module.exports =
signature: 'local ssh [deviceIp]'
description: 'Get a shell into a resinOS device'
help: '''
Warning: 'resin local ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to get a shell into the running application container of
your device.
The '--host' option will get you a shell into the Host OS of the resinOS device.
No option will return a list of containers to enter or you can explicitly select
one by passing its name to the --container option
Examples:
$ resin local ssh
$ resin local ssh --host
$ resin local ssh --container chaotic_water
$ resin local ssh --container chaotic_water --port 22222
$ resin local ssh --verbose
'''
options: [
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
,
localHostOSAccessOption,
signature: 'container'
parameter: 'container'
default: null
description: 'name of container to access'
alias: 'c'
,
signature: 'port'
parameter: 'port'
description: 'ssh port number (default: 22222)'
alias: 'p'
]
root: true
action: (params, options, done) ->
child_process = require('child_process')
Promise = require 'bluebird'
_ = require('lodash')
{ forms } = require('resin-sync')
{ selectContainerFromDevice, getSubShellCommand } = require('./common')
{ exitWithExpectedError } = require('../../utils/patterns')
if (options.host is true and options.container?)
exitWithExpectedError('Please pass either --host or --container option')
if not options.port?
options.port = 22222
verbose = if options.verbose then '-vvv' else ''
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (deviceIp) ->
_.assign(options, { deviceIp })
return if options.host
if not options.container?
return selectContainerFromDevice(deviceIp)
return options.container
.then (container) ->
command = "ssh \
#{verbose} \
-t \
-p #{options.port} \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
root@#{options.deviceIp}"
if not options.host
shellCmd = '''/bin/sh -c $"'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi'"'''
dockerCmd = "'$(if [ -f /usr/bin/balena ]; then echo \"balena\"; else echo \"docker\"; fi)'"
command += " #{dockerCmd} exec -ti #{container} #{shellCmd}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)

View File

@ -0,0 +1,79 @@
###
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
#
module.exports =
signature: 'local stop [deviceIp]'
description: 'Stop a running container on a resinOS device'
help: '''
Examples:
$ resin local stop
$ resin local stop --app-name myapp
$ resin local stop --all
$ resin local stop 192.168.1.10
$ resin local stop 192.168.1.10 --app-name myapp
'''
options: [
signature: 'all'
boolean: true
description: 'stop all containers'
,
signature: 'app-name'
parameter: 'name'
description: 'name of container to stop'
alias: 'a'
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
chalk = require('chalk')
{ forms, config, ResinLocalDockerUtils } = require('resin-sync')
{ selectContainerFromDevice, filterOutSupervisorContainer } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (@deviceIp) =>
@docker = new ResinLocalDockerUtils(@deviceIp)
if options.all
# Only list running containers
return @docker.docker.listContainersAsync(all: false)
.filter(filterOutSupervisorContainer)
.then (containers) =>
Promise.map containers, ({ Names, Id }) =>
console.log(chalk.yellow.bold("* Stopping container #{Names[0]}"))
@docker.stopContainer(Id)
ymlConfig = config.load()
@appName = options['app-name'] ? ymlConfig['local_resinos']?['app-name']
@docker.checkForRunningContainer(@appName)
.then (isRunning) =>
if not isRunning
return selectContainerFromDevice(@deviceIp, true)
console.log(chalk.yellow.bold("* Stopping container #{@appName}"))
return @appName
.then (runningContainerName) =>
@docker.stopContainer(runningContainerName)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
###
{ normalizeUuidProp } = require('../utils/normalization')
module.exports =
signature: 'logs <uuid>'
description: 'show device logs'
@ -24,10 +26,6 @@ module.exports =
To continuously stream output, and see new logs in real time, use the `--tail` option.
Note that for now you need to provide the whole UUID for this command to work correctly.
This is due to some technical limitations that we plan to address soon.
Examples:
$ resin logs 23c73a1
@ -44,26 +42,20 @@ module.exports =
permission: 'user'
primary: true
action: (params, options, done) ->
_ = require('lodash')
resin = require('resin-sdk')
normalizeUuidProp(params)
resin = require('resin-sdk').fromSharedOptions()
moment = require('moment')
printLine = (line) ->
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)')
console.log("#{timestamp} #{line.message}")
promise = resin.logs.history(params.uuid).each(printLine)
if not options.tail
# PubNub keeps the process alive after a history query.
# Until this is fixed, we force the process to exit.
# This of course prevents this command to be used programatically
return promise.catch(done).finally ->
process.exit(0)
promise.then ->
resin.logs.subscribe(params.uuid).then (logs) ->
if options.tail
resin.logs.subscribe(params.uuid, { count: 100 }).then (logs) ->
logs.on('line', printLine)
logs.on('error', done)
.catch(done)
.catch(done)
else
resin.logs.history(params.uuid)
.each(printLine)
.catch(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
###
{ normalizeUuidProp } = require('../utils/normalization')
exports.set =
signature: 'note <|note>'
description: 'set a device note'
@ -38,13 +40,16 @@ exports.set =
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
{ exitWithExpectedError } = require('../utils/patterns')
Promise.try ->
if _.isEmpty(params.note)
throw new Error('Missing note content')
exitWithExpectedError('Missing note content')
resin.models.device.note(options.device, params.note)
.nodeify(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,26 +15,91 @@ limitations under the License.
###
commandOptions = require('./command-options')
_ = require('lodash')
{ normalizeUuidProp } = require('../utils/normalization')
formatVersion = (v, isRecommended) ->
result = "v#{v}"
if isRecommended
result += ' (recommended)'
return result
resolveVersion = (deviceType, version) ->
if version isnt 'menu'
if version[0] == 'v'
version = version.slice(1)
return Promise.resolve(version)
form = require('resin-cli-form')
resin = require('resin-sdk').fromSharedOptions()
resin.models.os.getSupportedVersions(deviceType)
.then ({ versions, recommended }) ->
choices = versions.map (v) ->
value: v
name: formatVersion(v, v is recommended)
return form.ask
message: 'Select the OS version:'
type: 'list'
choices: choices
default: recommended
exports.versions =
signature: 'os versions <type>'
description: 'show the available resinOS versions for the given device type'
help: '''
Use this command to show the available resinOS versions for a certain device type.
Check available types with `resin devices supported`
Example:
$ resin os versions raspberrypi3
'''
action: (params, options, done) ->
resin = require('resin-sdk').fromSharedOptions()
resin.models.os.getSupportedVersions(params.type)
.then ({ versions, recommended }) ->
versions.forEach (v) ->
console.log(formatVersion(v, v is recommended))
exports.download =
signature: 'os download <type>'
description: 'download an unconfigured os image'
help: '''
Use this command to download an unconfigured os image for a certain device type.
Check available types with `resin devices supported`
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded if available, or the newest version otherwise (if all existing
versions for the given device type are pre-release).
You can pass `--version menu` to pick the OS version from the interactive menu
of all available versions.
Examples:
$ resin os download parallella -o ../foo/bar/parallella.img
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
'''
permission: 'user'
options: [
signature: 'output'
description: 'output path'
parameter: 'output'
alias: 'o'
required: 'You have to specify an output location'
{
signature: 'output'
description: 'output path'
parameter: 'output'
alias: 'o'
required: 'You have to specify the output location'
}
commandOptions.osVersion
]
action: (params, options, done) ->
Promise = require('bluebird')
unzip = require('unzip2')
fs = require('fs')
rindle = require('rindle')
@ -43,9 +108,22 @@ exports.download =
console.info("Getting device operating system for #{params.type}")
manager.get(params.type).then (stream) ->
bar = new visuals.Progress('Downloading Device OS')
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
displayVersion = ''
Promise.try ->
if not options.version
console.warn('OS version is not specified, using the default version:
the newest stable (non-pre-release) version if available,
or the newest version otherwise (if all existing
versions for the given device type are pre-release).')
return 'default'
return resolveVersion(params.type, options.version)
.then (version) ->
if version isnt 'default'
displayVersion = " #{version}"
return manager.get(params.type, version)
.then (stream) ->
bar = new visuals.Progress("Downloading Device OS#{displayVersion}")
spinner = new visuals.Spinner("Downloading Device OS#{displayVersion} (size unknown)")
stream.on 'progress', (state) ->
if state?
@ -69,107 +147,195 @@ exports.download =
console.info('The image was downloaded successfully')
.nodeify(done)
stepHandler = (step) ->
_ = require('lodash')
rindle = require('rindle')
visuals = require('resin-cli-visuals')
buildConfig = (image, deviceType, advanced = false) ->
form = require('resin-cli-form')
helpers = require('../utils/helpers')
step.on('stdout', _.bind(process.stdout.write, process.stdout))
step.on('stderr', _.bind(process.stderr.write, process.stderr))
helpers.getManifest(image, deviceType)
.get('options')
.then (questions) ->
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
step.on 'state', (state) ->
return if state.operation.command is 'burn'
console.log(helpers.stateToString(state))
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
bar = new visuals.Progress('Writing Device OS')
return form.run(questions, { override })
step.on('burn', _.bind(bar.update, bar))
return rindle.wait(step)
exports.configure =
signature: 'os configure <image> <uuid>'
description: 'configure an os image'
exports.buildConfig =
signature: 'os build-config <image> <device-type>'
description: 'build the OS config and save it to the JSON file'
help: '''
Use this command to configure a previously download operating system image with a device.
Use this command to prebuild the OS config once and skip the interactive part of `resin os configure`.
Examples:
Example:
$ resin os configure ../path/rpi.img 7cf02a6
$ resin os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
$ resin os configure ../path/rpi3.img 7cf02a6 --config "$(cat rpi3-config.json)"
'''
permission: 'user'
options: [
signature: 'advanced'
description: 'show advanced commands'
boolean: true
alias: 'v'
commandOptions.advancedConfig
{
signature: 'output'
description: 'the path to the output JSON file'
alias: 'o'
required: 'the output path is required'
parameter: 'output'
}
]
action: (params, options, done) ->
_ = require('lodash')
resin = require('resin-sdk')
form = require('resin-cli-form')
fs = require('fs')
Promise = require('bluebird')
writeFileAsync = Promise.promisify(fs.writeFile)
buildConfig(params.image, params['device-type'], options.advanced)
.then (answers) ->
writeFileAsync(options.output, JSON.stringify(answers, null, 4))
.nodeify(done)
exports.configure =
signature: 'os configure <image> [uuid] [deviceApiKey]'
description: 'configure an os image'
help: '''
Use this command to configure a previously downloaded operating system image for
the specific device or for an application generally.
Calling this command without --version is not recommended, and may fail in
future releases if the OS version cannot be inferred.
Note that device api keys are only supported on ResinOS 2.0.3+.
This command still supports the *deprecated* format where the UUID and optionally device key
are passed directly on the command line, but the recommended way is to pass either an --app or
--device argument. The deprecated format will be remove in a future release.
Examples:
$ resin os configure ../path/rpi.img --device 7cf02a6 --version 2.12.7
$ resin os configure ../path/rpi.img --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
$ resin os configure ../path/rpi.img --app MyApp --version 2.12.7
'''
permission: 'user'
options: [
commandOptions.advancedConfig
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
commandOptions.optionalOsVersion
{
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
parameter: 'config'
}
]
action: (params, options, done) ->
normalizeUuidProp(params)
normalizeUuidProp(options, 'device')
fs = require('fs')
Promise = require('bluebird')
readFileAsync = Promise.promisify(fs.readFile)
resin = require('resin-sdk').fromSharedOptions()
init = require('resin-device-init')
helpers = require('../utils/helpers')
patterns = require('../utils/patterns')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
if _.filter([
options.device
options.application
params.uuid
]).length != 1
patterns.exitWithExpectedError '''
To configure an image, you must provide exactly one of:
* A device, with --device <uuid>
* An application, with --app <appname>
* [Deprecated] A device, passing its uuid directly on the command line
See the help page for examples:
$ resin help os configure
'''
if params.uuid
console.warn(
'Directly passing a UUID to `resin os configure` is deprecated, and will stop working in future.\n' +
'Pass your device UUID with --device <uuid> instead.' +
if params.deviceApiKey
' Device api keys can be passed with --deviceApiKey.\n'
else '\n'
)
uuid = options.device || params.uuid
deviceApiKey = options.deviceApiKey || params.deviceApiKey
console.info('Configuring operating system image')
resin.models.device.get(params.uuid)
.get('device_type')
.then(resin.models.device.getManifestBySlug)
.get('options')
.then (questions) ->
if not options.advanced
advancedGroup = _.findWhere questions,
name: 'advanced'
isGroup: true
configurationResourceType = if uuid then 'device' else 'application'
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
return form.run(questions, { override })
resin.models[configurationResourceType].get(uuid || options.application)
.then (appOrDevice) ->
Promise.try ->
if options.config
return readFileAsync(options.config, 'utf8')
.then(JSON.parse)
return buildConfig(params.image, appOrDevice.device_type, options.advanced)
.then (answers) ->
init.configure(params.image, params.uuid, answers).then(stepHandler)
answers.version = options.version
(if configurationResourceType == 'device'
generateDeviceConfig(appOrDevice, deviceApiKey, answers)
else
generateApplicationConfig(appOrDevice, answers)
).then (config) ->
init.configure(params.image, appOrDevice.device_type, config, answers)
.then(helpers.osProgressHandler)
.nodeify(done)
INIT_WARNING_MESSAGE = '''
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
'''
exports.initialize =
signature: 'os initialize <image>'
description: 'initialize an os image'
help: '''
Use this command to initialize a previously configured operating system image.
help: """
Use this command to initialize a device with previously configured operating system image.
#{INIT_WARNING_MESSAGE}
Examples:
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
'''
"""
permission: 'user'
options: [
commandOptions.yes
{
signature: 'type'
description: 'device type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
commandOptions.drive
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
umount = Promise.promisifyAll(require('umount'))
resin = require('resin-sdk')
umountAsync = Promise.promisify(require('umount').umount)
form = require('resin-cli-form')
init = require('resin-device-init')
patterns = require('../utils/patterns')
helpers = require('../utils/helpers')
console.info('Initializing device')
resin.models.device.getManifestBySlug(options.type)
console.info("""
Initializing device
#{INIT_WARNING_MESSAGE}
""")
helpers.getManifest(params.image, options.type)
.then (manifest) ->
return manifest.initialization?.options
.then (questions) ->
@ -178,14 +344,39 @@ exports.initialize =
drive: options.drive
.tap (answers) ->
return if not answers.drive?
message = "This will erase #{answers.drive}. Are you sure?"
patterns.confirm(options.yes, message)
patterns.confirm(
options.yes
"This will erase #{answers.drive}. Are you sure?"
"Going to erase #{answers.drive}."
)
.return(answers.drive)
.then(umount.umountAsync)
.then(umountAsync)
.tap (answers) ->
return init.initialize(params.image, options.type, answers).then(stepHandler)
return helpers.sudo([
'internal'
'osinit'
params.image
options.type
JSON.stringify(answers)
])
.then (answers) ->
return if not answers.drive?
umount.umountAsync(answers.drive).tap ->
# TODO: resin local makes use of ejectAsync, see below
# DO we need this / should we do that here?
# getDrive = (drive) ->
# driveListAsync().then (drives) ->
# selectedDrive = _.find(drives, device: drive)
# if not selectedDrive?
# throw new Error("Drive not found: #{drive}")
# return selectedDrive
# if (os.platform() is 'win32') and selectedDrive.mountpoint?
# ejectAsync = Promise.promisify(require('removedrive').eject)
# return ejectAsync(selectedDrive.mountpoint)
umountAsync(answers.drive).tap ->
console.info("You can safely remove #{answers.drive} now")
.nodeify(done)

299
lib/actions/preload.coffee Normal file
View File

@ -0,0 +1,299 @@
###
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
dockerUtils = require('../utils/docker')
LATEST = 'latest'
allDeviceTypes = undefined
getDeviceTypes = ->
Bluebird = require('bluebird')
if allDeviceTypes != undefined
return Bluebird.resolve(allDeviceTypes)
resin = require('resin-sdk').fromSharedOptions()
resin.models.config.getDeviceTypes()
.tap (dt) ->
allDeviceTypes = dt
getDeviceTypesWithSameArch = (deviceTypeSlug) ->
_ = require('lodash')
getDeviceTypes()
.then (deviceTypes) ->
deviceType = _.find(deviceTypes, slug: deviceTypeSlug)
_(deviceTypes).filter(arch: deviceType.arch).map('slug').value()
getApplicationsWithSuccessfulBuilds = (deviceType) ->
preload = require('resin-preload')
resin = require('resin-sdk').fromSharedOptions()
getDeviceTypesWithSameArch(deviceType)
.then (deviceTypes) ->
resin.pine.get
resource: 'my_application'
options:
$filter:
device_type:
$in: deviceTypes
owns__release:
$any:
$alias: 'r'
$expr:
r:
status: 'success'
$expand: preload.applicationExpandOptions
$select: [ 'id', 'app_name', 'device_type', 'commit', 'should_track_latest_release' ]
$orderby: 'app_name asc'
selectApplication = (deviceType) ->
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
{ exitWithExpectedError } = require('../utils/patterns')
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and releases.')
applicationInfoSpinner.start()
getApplicationsWithSuccessfulBuilds(deviceType)
.then (applications) ->
applicationInfoSpinner.stop()
if applications.length == 0
exitWithExpectedError("You have no apps with successful releases for a '#{deviceType}' device type.")
form.ask
message: 'Select an application'
type: 'list'
choices: applications.map (app) ->
name: app.app_name
value: app
selectApplicationCommit = (releases) ->
form = require('resin-cli-form')
{ exitWithExpectedError } = require('../utils/patterns')
if releases.length == 0
exitWithExpectedError('This application has no successful releases.')
DEFAULT_CHOICE = { 'name': LATEST, 'value': LATEST }
choices = [ DEFAULT_CHOICE ].concat releases.map (release) ->
name: "#{release.end_timestamp} - #{release.commit}"
value: release.commit
return form.ask
message: 'Select a release'
type: 'list'
default: LATEST
choices: choices
offerToDisableAutomaticUpdates = (application, commit) ->
Promise = require('bluebird')
resin = require('resin-sdk').fromSharedOptions()
form = require('resin-cli-form')
if commit == LATEST or not application.should_track_latest_release
return Promise.resolve()
message = '''
This application is set to automatically update all devices to the latest available version.
This might be unexpected behaviour: with this enabled, the preloaded device will still
download and install the latest release once it is online.
Do you want to disable automatic updates for this application?
Warning: To re-enable this requires direct api calls,
see https://docs.resin.io/reference/api/resources/device/#set-device-to-release
'''
form.ask
message: message,
type: 'confirm'
.then (update) ->
if not update
return
resin.pine.patch
resource: 'application'
id: application.id
body:
should_track_latest_release: false
module.exports =
signature: 'preload <image>'
description: '(beta) preload an app on a disk image (or Edison zip archive)'
help: '''
Warning: "resin preload" requires Docker to be correctly installed in
your shell environment. For more information (including Windows support)
please check the README here: https://github.com/resin-io/resin-cli .
Use this command to preload an application to a local disk image (or
Edison zip archive) with a built release from Resin.io.
Examples:
$ resin preload resin.img --app 1234 --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image some-image.png
$ resin preload resin.img
'''
permission: 'user'
primary: true
options: dockerUtils.appendConnectionOptions [
{
signature: 'app'
parameter: 'appId'
description: 'id of the application to preload'
alias: 'a'
}
{
signature: 'commit'
parameter: 'hash'
description: '''
the commit hash for a specific application release to preload, use "latest" to specify the latest release
(ignored if no appId is given)
'''
alias: 'c'
}
{
signature: 'splash-image'
parameter: 'splashImage.png'
description: 'path to a png image to replace the splash screen'
alias: 's'
}
{
signature: 'dont-check-arch'
boolean: true
description: 'Disables check for matching architecture in image and application'
}
{
signature: 'pin-device-to-release'
boolean: true
description: 'Pin the preloaded device to the preloaded release on provision'
alias: 'p'
}
]
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk').fromSharedOptions()
preload = require('resin-preload')
visuals = require('resin-cli-visuals')
nodeCleanup = require('node-cleanup')
{ exitWithExpectedError } = require('../utils/patterns')
progressBars = {}
progressHandler = (event) ->
progressBar = progressBars[event.name]
if not progressBar
progressBar = progressBars[event.name] = new visuals.Progress(event.name)
progressBar.update(percentage: event.percentage)
spinners = {}
spinnerHandler = (event) ->
spinner = spinners[event.name]
if not spinner
spinner = spinners[event.name] = new visuals.Spinner(event.name)
if event.action == 'start'
spinner.start()
else
console.log()
spinner.stop()
options.image = params.image
options.appId = options.app
delete options.app
options.splashImage = options['splash-image']
delete options['splash-image']
options.dontCheckArch = options['dont-check-arch'] || false
delete options['dont-check-arch']
if options.dontCheckArch and not options.appId
exitWithExpectedError('You need to specify an app id if you disable the architecture check.')
options.pinDevice = options['pin-device-to-release'] || false
delete options['pin-device-to-release']
# Get a configured dockerode instance
dockerUtils.getDocker(options)
.then (docker) ->
preloader = new preload.Preloader(
resin
docker
options.appId
options.commit
options.image
options.splashImage
options.proxy
options.dontCheckArch
options.pinDevice
)
gotSignal = false
nodeCleanup (exitCode, signal) ->
if signal
gotSignal = true
nodeCleanup.uninstall() # don't call cleanup handler again
preloader.cleanup()
.then ->
# calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal)
return false
if process.env.DEBUG
preloader.stderr.pipe(process.stderr)
preloader.on('progress', progressHandler)
preloader.on('spinner', spinnerHandler)
return new Promise (resolve, reject) ->
preloader.on('error', reject)
preloader.prepare()
.then ->
# If no appId was provided, show a list of matching apps
Promise.try ->
if not preloader.appId
selectApplication(preloader.config.deviceType)
.then (application) ->
preloader.setApplication(application)
.then ->
# Use the commit given as --commit or show an interactive commit selection menu
Promise.try ->
if options.commit
if options.commit == LATEST and preloader.application.commit
# handle `--commit latest`
return LATEST
release = _.find preloader.application.owns__release, (release) ->
release.commit.startsWith(options.commit)
if not release
exitWithExpectedError('There is no release matching this commit')
return release.commit
selectApplicationCommit(preloader.application.owns__release)
.then (commit) ->
if commit == LATEST
preloader.commit = preloader.application.commit
else
preloader.commit = commit
# Propose to disable automatic app updates if the commit is not the latest
offerToDisableAutomaticUpdates(preloader.application, commit)
.then ->
# All options are ready: preload the image.
preloader.preload()
.catch(resin.errors.ResinError, exitWithExpectedError)
.then(resolve)
.catch(reject)
.then(done)
.finally ->
if not gotSignal
preloader.cleanup()

229
lib/actions/push.ts Normal file
View File

@ -0,0 +1,229 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import { ResinSDK } from 'resin-sdk';
import { BuildError } from '../utils/device/errors';
// An regex to detect an IP address, from https://www.regular-expressions.info/ip.html
const IP_REGEX = new RegExp(
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
);
enum BuildTarget {
Cloud,
Device,
}
function getBuildTarget(appOrDevice: string): BuildTarget | null {
// First try the application regex from the api
if (/^[a-zA-Z0-9_-]+$/.test(appOrDevice)) {
return BuildTarget.Cloud;
}
if (IP_REGEX.test(appOrDevice)) {
return BuildTarget.Device;
}
return null;
}
async function getAppOwner(sdk: ResinSDK, appName: string) {
const {
exitWithExpectedError,
selectFromList,
} = await import('../utils/patterns');
const _ = await import('lodash');
const applications = await sdk.models.application.getAll({
$expand: {
user: {
$select: ['username'],
},
},
$filter: {
app_name: appName,
},
$select: ['id'],
});
if (applications == null || applications.length === 0) {
exitWithExpectedError(
stripIndent`
No applications found with name: ${appName}.
This could mean that the application does not exist, or you do
not have the permissions required to access it.`,
);
}
if (applications.length === 1) {
return _.get(applications, '[0].user[0].username');
}
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
const entries = _.map(applications, app => {
const username = _.get(app, 'user[0].username');
return {
name: `${username}/${appName}`,
extra: username,
};
});
const selected = await selectFromList(
`${
entries.length
} applications found with that name, please select the application you would like to push to`,
entries,
);
return selected.extra;
}
export const push: CommandDefinition<
{
applicationOrDevice: string;
},
{
source: string;
emulated: boolean;
nocache: boolean;
}
> = {
signature: 'push <applicationOrDevice>',
description:
'Start a remote build on the resin.io cloud build servers or a local mode device',
help: stripIndent`
This command can be used to start a build on the remote
resin.io cloud builders, or a local mode resin device.
When building on the resin cloud the given source directory will be sent to the
resin.io builder, and the build will proceed. This can be used as a drop-in
replacement for git push to deploy.
When building on a local mode device, the given source directory will be built on
device, and the resulting containers will be run on the device. Logs will be
streamed back from the device as part of the same invocation.
Examples:
$ resin push myApp
$ resin push myApp --source <source directory>
$ resin push myApp -s <source directory>
$ resin push 10.0.0.1
$ resin push 10.0.0.1 --source <source directory>
$ resin push 10.0.0.1 -s <source directory>
`,
permission: 'user',
options: [
{
signature: 'source',
alias: 's',
description:
'The source that should be sent to the resin builder to be built (defaults to the current directory)',
parameter: 'source',
},
{
signature: 'emulated',
alias: 'e',
description: 'Force an emulated build to occur on the remote builder',
boolean: true,
},
{
signature: 'nocache',
alias: 'c',
description: "Don't use cache when building this project",
boolean: true,
},
],
async action(params, options, done) {
const sdk = (await import('resin-sdk')).fromSharedOptions();
const Bluebird = await import('bluebird');
const remote = await import('../utils/remote-build');
const deviceDeploy = await import('../utils/device/deploy');
const { exitWithExpectedError } = await import('../utils/patterns');
const appOrDevice: string | null = params.applicationOrDevice;
if (appOrDevice == null) {
exitWithExpectedError('You must specify an application or a device');
}
const source = options.source || '.';
if (process.env.DEBUG) {
console.log(`[debug] Using ${source} as build source`);
}
const buildTarget = getBuildTarget(appOrDevice);
switch (buildTarget) {
case BuildTarget.Cloud:
const app = appOrDevice;
Bluebird.join(
sdk.auth.getToken(),
sdk.settings.get('resinUrl'),
getAppOwner(sdk, app),
(token, baseUrl, owner) => {
const opts = {
emulated: options.emulated,
nocache: options.nocache,
};
const args = {
app,
owner,
source,
auth: token,
baseUrl,
sdk,
opts,
};
return remote.startRemoteBuild(args);
},
).nodeify(done);
break;
case BuildTarget.Device:
const device = appOrDevice;
// TODO: Support passing a different port
Bluebird.resolve(
deviceDeploy.deployToDevice({
source,
deviceHost: device,
}),
)
.catch(BuildError, e => {
exitWithExpectedError(e.toString());
})
.nodeify(done);
break;
default:
exitWithExpectedError(
stripIndent`
Build target not recognised. Please provide either an application name or device address.
The only supported device addresses currently are IP addresses.
If you believe your build target should have been detected, and this is an error, please
create an issue.`,
);
break;
}
},
};

39
lib/actions/settings.ts Normal file
View File

@ -0,0 +1,39 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CommandDefinition } from 'capitano';
export const list: CommandDefinition = {
signature: 'settings',
description: 'print current settings',
help: `\
Use this command to display detected settings
Examples:
$ resin settings\
`,
async action(_params, _options, done) {
const resin = (await import('resin-sdk')).fromSharedOptions();
const prettyjson = await import('prettyjson');
return resin.settings
.getAll()
.then(prettyjson.render)
.then(console.log)
.nodeify(done);
},
};

141
lib/actions/ssh.coffee Normal file
View File

@ -0,0 +1,141 @@
###
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
commandOptions = require('./command-options')
{ normalizeUuidProp } = require('../utils/normalization')
module.exports =
signature: 'ssh [uuid]'
description: '(beta) get a shell into the running app container of a device'
help: '''
Warning: 'resin ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to get a shell into the running application container of
your device.
Examples:
$ resin ssh MyApp
$ resin ssh 7cf02a6
$ resin ssh 7cf02a6 --port 8080
$ resin ssh 7cf02a6 -v
$ resin ssh 7cf02a6 -s
'''
permission: 'user'
primary: true
options: [
signature: 'port'
parameter: 'port'
description: 'ssh gateway port'
alias: 'p'
,
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
commandOptions.hostOSAccess,
signature: 'noproxy'
boolean: true
description: "don't use the proxy configuration for this connection.
Only makes sense if you've configured proxy globally."
]
action: (params, options, done) ->
normalizeUuidProp(params)
child_process = require('child_process')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
bash = require('bash')
hasbin = require('hasbin')
{ getSubShellCommand } = require('../utils/helpers')
patterns = require('../utils/patterns')
options.port ?= 22
verbose = if options.verbose then '-vvv' else ''
proxyConfig = global.PROXY_CONFIG
useProxy = !!proxyConfig and not options.noproxy
getSshProxyCommand = (hasTunnelBin) ->
return '' if not useProxy
if not hasTunnelBin
console.warn('''
Proxy is enabled but the `proxytunnel` binary cannot be found.
Please install it if you want to route the `resin ssh` requests through the proxy.
Alternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config
for the `ssh` requests.
Attemmpting the unproxied request for now.
''')
return ''
tunnelOptions =
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
dest: '%h:%p'
{ proxyAuth } = proxyConfig
if proxyAuth
i = proxyAuth.indexOf(':')
_.assign tunnelOptions,
user: proxyAuth.substring(0, i)
pass: proxyAuth.substring(i + 1)
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
Promise.try ->
return false if not params.uuid
return resin.models.device.has(params.uuid)
.then (uuidExists) ->
return params.uuid if uuidExists
return patterns.inferOrSelectDevice()
.then (uuid) ->
console.info("Connecting to: #{uuid}")
resin.models.device.get(uuid)
.then (device) ->
patterns.exitWithExpectedError('Device is not online') if not device.is_online
Promise.props
username: resin.auth.whoami()
uuid: device.uuid
# get full uuid
containerId: if options.host then '' else resin.models.device.getApplicationInfo(device.uuid).get('containerId')
proxyUrl: resin.settings.get('proxyUrl')
hasTunnelBin: if useProxy then hasbin('proxytunnel') else null
.then ({ username, uuid, containerId, proxyUrl, hasTunnelBin }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
sshProxyCommand = getSshProxyCommand(hasTunnelBin)
if options.host
accessCommand = "host #{uuid}"
else
accessCommand = "enter #{uuid} #{containerId}"
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
#{sshProxyCommand} \
-p #{options.port} #{username}@ssh.#{proxyUrl} #{accessCommand}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)

View File

@ -1,6 +1,5 @@
/*
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -13,19 +12,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
*/
(function() {
exports.version = {
signature: 'version',
description: 'output the version number',
help: 'Display the Resin CLI version.',
action: function(params, options, done) {
var packageJSON;
packageJSON = require('../../package.json');
console.log(packageJSON.version);
return done();
}
};
}).call(this);
import * as ResinSync from 'resin-sync';
export = ResinSync.capitano('resin-cli');

56
lib/actions/util.coffee Normal file
View File

@ -0,0 +1,56 @@
###
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
_ = require('lodash')
exports.availableDrives =
# TODO: dedupe with https://github.com/resin-io-modules/resin-cli-visuals/blob/master/lib/widgets/drive/index.coffee
signature: 'util available-drives'
description: 'list available drives'
help: """
Use this command to list your machine's drives usable for writing the OS image to.
Skips the system drives.
"""
action: ->
Promise = require('bluebird')
drivelist = require('drivelist')
driveListAsync = Promise.promisify(drivelist.list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
formatDrive = (drive) ->
size = drive.size / 1000000000
return {
device: drive.device
size: "#{size.toFixed(1)} GB"
description: drive.description
}
getDrives = ->
driveListAsync().then (drives) ->
return _.reject(drives, system: true)
getDrives()
.then (drives) ->
if not drives.length
console.error("#{chalk.red('x')} No available drives were detected, plug one in!")
return
console.log visuals.table.horizontal drives.map(formatDrive), [
'device'
'size'
'description'
]

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -34,29 +34,28 @@ exports.wizard =
'''
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
resin = require('resin-sdk')
resin = require('resin-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
{ runCommand } = require('../utils/helpers')
resin.auth.isLoggedIn().then (isLoggedIn) ->
return if isLoggedIn
console.info('Looks like you\'re not logged in yet!')
console.info('Lets go through a quick wizard to get you started.\n')
return capitano.runAsync('login')
console.info("Let's go through a quick wizard to get you started.\n")
return runCommand('login')
.then ->
return if params.name?
patterns.selectOrCreateApplication().tap (applicationName) ->
resin.models.application.has(applicationName).then (hasApplication) ->
return applicationName if hasApplication
capitano.runAsync("app create #{applicationName}")
runCommand("app create #{applicationName}")
.then (applicationName) ->
params.name = applicationName
.then ->
return capitano.runAsync("device init --application #{params.name}")
return runCommand("device init --application #{params.name}")
.tap(patterns.awaitDevice)
.then (uuid) ->
return capitano.runAsync("device #{uuid}")
return runCommand("device #{uuid}")
.then ->
return resin.models.application.get(params.name)
.then (application) ->

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,26 +14,87 @@ See the License for the specific language governing permissions and
limitations under the License.
###
_ = require('lodash')
Raven = require('raven')
Raven.disableConsoleAlerts()
Raven.config require('./config').sentryDsn,
captureUnhandledRejections: true,
autoBreadcrumbs: true,
release: require('../package.json').version
.install (logged, error) ->
console.error(error)
process.exit(1)
Raven.setContext
extra:
args: process.argv
node_version: process.version
validNodeVersions = require('../package.json').engines.node
if not require('semver').satisfies(process.version, validNodeVersions)
console.warn """
Warning: this version of Node does not match the requirements of this package.
This package expects #{validNodeVersions}, but you're using #{process.version}.
This may cause unexpected behaviour.
To upgrade your Node, visit https://nodejs.org/en/download/
"""
# Doing this before requiring any other modules,
# including the 'resin-sdk', to prevent any module from reading the http proxy config
# before us
globalTunnel = require('global-tunnel-ng')
settings = require('resin-settings-client')
try
proxy = settings.get('proxy') or null
catch
proxy = null
# Init the tunnel even if the proxy is not configured
# because it can also get the proxy from the http(s)_proxy env var
# If that is not set as well the initialize will do nothing
globalTunnel.initialize(proxy)
# TODO: make this a feature of capitano https://github.com/resin-io/capitano/issues/48
global.PROXY_CONFIG = globalTunnel.proxyConfig
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
resin = require('resin-sdk')
capitano = require('capitano')
capitanoExecuteAsync = Promise.promisify(capitano.execute)
# We don't yet use resin-sdk directly everywhere, but we set up shared
# options correctly so we can do safely in submodules
ResinSdk = require('resin-sdk')
ResinSdk.setSharedOptions(
apiUrl: settings.get('apiUrl')
imageMakerUrl: settings.get('imageMakerUrl')
dataDirectory: settings.get('dataDirectory')
retries: 2
)
resin = ResinSdk.fromSharedOptions()
actions = require('./actions')
errors = require('./errors')
events = require('./events')
plugins = require('./utils/plugins')
update = require('./utils/update')
{ exitWithExpectedError } = require('./utils/patterns')
# Assign bluebird as the global promise library
# stream-to-promise will produce native promises if not
# for this module, which could wreak havoc in this
# bluebird-only codebase.
require('any-promise/register/bluebird')
capitano.permission 'user', (done) ->
resin.auth.isLoggedIn().then (isLoggedIn) ->
if not isLoggedIn
throw new Error '''
exitWithExpectedError('''
You have to log in to continue
Run the following command to go through the login wizard:
$ resin login
'''
''')
.nodeify(done)
capitano.command
@ -41,6 +102,11 @@ capitano.command
action: ->
capitano.execute(command: 'help')
capitano.globalOption
signature: 'help'
boolean: true
alias: 'h'
# ---------- Info Module ----------
capitano.command(actions.info.version)
@ -50,11 +116,8 @@ capitano.command(actions.help.help)
# ---------- Wizard Module ----------
capitano.command(actions.wizard.wizard)
# ---------- Auth Module ----------
capitano.command(actions.auth.login)
capitano.command(actions.auth.logout)
capitano.command(actions.auth.signup)
capitano.command(actions.auth.whoami)
# ---------- Api key module ----------
capitano.command(actions.apiKey.generate)
# ---------- App Module ----------
capitano.command(actions.app.create)
@ -63,13 +126,25 @@ capitano.command(actions.app.remove)
capitano.command(actions.app.restart)
capitano.command(actions.app.info)
# ---------- Auth Module ----------
capitano.command(actions.auth.login)
capitano.command(actions.auth.logout)
capitano.command(actions.auth.signup)
capitano.command(actions.auth.whoami)
# ---------- Device Module ----------
capitano.command(actions.device.list)
capitano.command(actions.device.supported)
capitano.command(actions.device.rename)
capitano.command(actions.device.init)
capitano.command(actions.device.remove)
capitano.command(actions.device.identify)
capitano.command(actions.device.reboot)
capitano.command(actions.device.shutdown)
capitano.command(actions.device.enableDeviceUrl)
capitano.command(actions.device.disableDeviceUrl)
capitano.command(actions.device.getDeviceUrl)
capitano.command(actions.device.hasDeviceUrl)
capitano.command(actions.device.register)
capitano.command(actions.device.move)
capitano.command(actions.device.info)
@ -90,13 +165,16 @@ capitano.command(actions.env.rename)
capitano.command(actions.env.remove)
# ---------- OS Module ----------
capitano.command(actions.os.versions)
capitano.command(actions.os.download)
capitano.command(actions.os.buildConfig)
capitano.command(actions.os.configure)
capitano.command(actions.os.initialize)
# ---------- Config Module ----------
capitano.command(actions.config.read)
capitano.command(actions.config.write)
capitano.command(actions.config.inject)
capitano.command(actions.config.reconfigure)
capitano.command(actions.config.generate)
@ -106,12 +184,51 @@ capitano.command(actions.settings.list)
# ---------- Logs Module ----------
capitano.command(actions.logs)
# ---------- Sync Module ----------
capitano.command(actions.sync)
# ---------- Preload Module ----------
capitano.command(actions.preload)
# ---------- SSH Module ----------
capitano.command(actions.ssh)
# ---------- Local ResinOS Module ----------
capitano.command(actions.local.configure)
capitano.command(actions.local.flash)
capitano.command(actions.local.logs)
capitano.command(actions.local.push)
capitano.command(actions.local.ssh)
capitano.command(actions.local.scan)
capitano.command(actions.local.stop)
# ---------- Public utils ----------
capitano.command(actions.util.availableDrives)
# ---------- Internal utils ----------
capitano.command(actions.internal.osInit)
capitano.command(actions.internal.scanDevices)
capitano.command(actions.internal.sudo)
#------------ Local build and deploy -------
capitano.command(actions.build)
capitano.command(actions.deploy)
#------------ Push/remote builds -------
capitano.command(actions.push.push)
#------------ Join/Leave -------
capitano.command(actions.join.join)
capitano.command(actions.leave.leave)
update.notify()
plugins.register(/^resin-plugin-(.+)$/).then ->
cli = capitano.parse(process.argv)
events.trackCommand(cli).then ->
capitano.executeAsync(cli)
cli = capitano.parse(process.argv)
runCommand = ->
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitanoExecuteAsync(cli)
Promise.all([events.trackCommand(cli), runCommand()])
.catch(errors.handle)

63
lib/auth/index.coffee Normal file
View File

@ -0,0 +1,63 @@
###
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
###*
# @module auth
###
open = require('opn')
resin = require('resin-sdk').fromSharedOptions()
server = require('./server')
utils = require('./utils')
###*
# @summary Login to the Resin CLI using the web dashboard
# @function
# @public
#
# @description
# This function opens the user's default browser and points it
# to the Resin.io dashboard where the session token exchange will
# take place.
#
# Once the the token is retrieved, it's automatically persisted.
#
# @fulfil {String} - session token
# @returns {Promise}
#
# @example
# auth.login().then (sessionToken) ->
# console.log('I\'m logged in!')
# console.log("My session token is: #{sessionToken}")
###
exports.login = ->
options =
port: 8989
path: '/auth'
# Needs to be 127.0.0.1 not localhost, because the ip only is whitelisted
# from mixed content warnings (as the target of a form in the result page)
callbackUrl = "http://127.0.0.1:#{options.port}#{options.path}"
return utils.getDashboardLoginURL(callbackUrl).then (loginUrl) ->
# Leave a bit of time for the
# server to get up and runing
setTimeout ->
open(loginUrl, { wait: false })
, 1000
return server.awaitForToken(options)
.tap(resin.auth.loginWithToken)

21
lib/auth/pages/error.ejs Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Error</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
</head>
<body>
<div class="center">
<img class="icon" src="./static/images/sad.png" inline>
<h1>Something went wrong</h1>
<p>You couldn't login to the Resin CLI for some reason</p>
<br>
<br>
<a href="https://forums.resin.io/" class="button danger">Get help in our forums</a>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,60 @@
html,
body {
height: 100%;
}
body {
text-align: center;
background-color: #fff;
color: rgb(24, 24, 24);
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
position: relative;
}
.center {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 50%;
height: 50%;
}
.icon {
display: block;
width: 40px;
height: 45px;
margin: 0 auto;
margin-bottom: 15px;
}
h1 {
font-size: 3rem;
margin: 0;
margin-bottom: 12px;
}
p {
color: rgb(99, 99, 99);
font-size: 1.1rem;
margin: 0;
margin-bottom: 15px;
}
a.button {
padding: 15px 25px;
border-radius: 5px;
text-decoration: none;
}
a.button.danger {
background-color: rgb(235, 110, 111);
color: #fff;
}
a.button.normal {
background-color: rgb(252, 191, 44);
color: #fff;
}

View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Success</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
</head>
<body>
<div class="center">
<img class="icon" src="./static/images/happy.png" inline>
<h1>Success!</h1>
<p>You successfully logged in the Resin CLI</p>
<br>
<br>
<a href="<%= dashboardUrl %>" class="button normal">Go to the dashboard</a>
</div>
</body>
</html>

101
lib/auth/server.coffee Normal file
View File

@ -0,0 +1,101 @@
###
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
express = require('express')
path = require('path')
bodyParser = require('body-parser')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
utils = require('./utils')
createServer = ({ port, isDev } = {}) ->
app = express()
app.use bodyParser.urlencoded
extended: true
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'pages'))
if isDev
app.use(express.static(path.join(__dirname, 'pages', 'static')))
server = app.listen(port)
return { app, server }
###*
# @summary Await for token
# @function
# @protected
#
# @param {Object} options - options
# @param {String} options.path - callback path
# @param {Number} options.port - http port
#
# @example
# server.awaitForToken
# path: '/auth'
# port: 9001
# .then (token) ->
# console.log(token)
###
exports.awaitForToken = (options) ->
{ app, server } = createServer(port: options.port)
return new Promise (resolve, reject) ->
closeServer = (errorMessage, successPayload) ->
server.close ->
if errorMessage
reject(new Error(errorMessage))
return
resolve(successPayload)
renderAndDone = ({ request, response, viewName, errorMessage, statusCode, token }) ->
return getContext(viewName)
.then (context) ->
response.status(statusCode || 200).render(viewName, context)
request.connection.destroy()
closeServer(errorMessage, token)
app.post options.path, (request, response) ->
token = request.body.token?.trim()
Promise.try ->
if not token
throw new Error('No token')
return utils.loginIfTokenValid(token)
.tap (loggedIn) ->
if not loggedIn
throw new Error('Invalid token')
.then ->
renderAndDone({ request, response, viewName: 'success', token })
.catch (error) ->
renderAndDone({
request, response, viewName: 'error',
statusCode: 401, errorMessage: error.message
})
app.use (request, response) ->
response.status(404).send('Not found')
closeServer('Unknown path or verb')
exports.getContext = getContext = (viewName) ->
if viewName is 'success'
return Promise.props
dashboardUrl: resin.settings.get('dashboardUrl')
return Promise.resolve({})

80
lib/auth/utils.coffee Normal file
View File

@ -0,0 +1,80 @@
###
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
resin = require('resin-sdk').fromSharedOptions()
_ = require('lodash')
url = require('url')
Promise = require('bluebird')
###*
# @summary Get dashboard CLI login URL
# @function
# @protected
#
# @param {String} callbackUrl - callback url
# @fulfil {String} - dashboard login url
# @returns {Promise}
#
# @example
# utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
# console.log(url)
###
exports.getDashboardLoginURL = (callbackUrl) ->
# Encode percentages signs from the escaped url
# characters to avoid angular getting confused.
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25')
resin.settings.get('dashboardUrl').then (dashboardUrl) ->
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
###*
# @summary Log in using a token, but only if the token is valid
# @function
# @protected
#
# @description
# This function checks that the token is not only well-structured
# but that it also authenticates with the server successfully.
#
# If authenticated, the token is persisted, if not then the previous
# login state is restored.
#
# @param {String} token - session token or api key
# @fulfil {Boolean} - whether the login was successful or not
# @returns {Promise}
#
# utils.loginIfTokenValid('...').then (loggedIn) ->
# if loggedIn
# console.log('Token is valid!')
###
exports.loginIfTokenValid = (token) ->
if not token? or _.isEmpty(token.trim())
return Promise.resolve(false)
return resin.auth.getToken()
.catchReturn(undefined)
.then (currentToken) ->
resin.auth.loginWithToken(token)
.return(token)
.then(resin.auth.isLoggedIn)
.tap (isLoggedIn) ->
return if isLoggedIn
if currentToken?
return resin.auth.loginWithToken(currentToken)
else
return resin.auth.logout()

2
lib/config.ts Normal file
View File

@ -0,0 +1,2 @@
export const sentryDsn =
'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239';

125
lib/errors.ts Normal file
View File

@ -0,0 +1,125 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as _ from 'lodash';
import * as os from 'os';
import * as Raven from 'raven';
import * as Promise from 'bluebird';
import { stripIndent } from 'common-tags';
import * as patterns from './utils/patterns';
const captureException = Promise.promisify<string, Error>(
Raven.captureException,
{ context: Raven },
);
function hasCode(error: any): error is Error & { code: string } {
return error.code != null;
}
function treatFailedBindingAsMissingModule(error: any): void {
if (error.message.startsWith('Could not locate the bindings file.')) {
error.code = 'MODULE_NOT_FOUND';
}
}
function interpret(error: any): string | undefined {
if (!(error instanceof Error)) {
return;
}
treatFailedBindingAsMissingModule(error);
if (hasCode(error)) {
const errorCodeHandler = messages[error.code];
const message = errorCodeHandler && errorCodeHandler(error);
if (message) {
return message;
}
if (!_.isEmpty(error.message)) {
return `${error.code}: ${error.message}`;
}
} else {
return error.message;
}
}
const messages: {
[key: string]: (error: Error & { path?: string }) => string;
} = {
EISDIR: error => `File is a directory: ${error.path}`,
ENOENT: error => `No such file or directory: ${error.path}`,
ENOGIT: () => stripIndent`
Git is not installed on this system.
Head over to http://git-scm.com to install it and run this command again.`,
EPERM: () => stripIndent`
You don't have enough privileges to run this operation.
${
os.platform() === 'win32'
? 'Run a new Command Prompt as administrator and try running this command again.'
: 'Try running this command again prefixing it with `sudo`.'
}
If this is not the case, and you're trying to burn an SDCard, check that the write lock is not set.`,
EACCES: e => messages.EPERM(e),
ETIMEDOUT: () =>
'Oops something went wrong, please check your connection and try again.',
MODULE_NOT_FOUND: () => stripIndent`
Part of the CLI could not be loaded. This typically means your CLI install is in a broken state.
${
os.arch() === 'x64'
? 'You can normally fix this by uninstalling and reinstalling the CLI.'
: stripIndent`
You're using an unsupported architecture (${os.arch()}), so this is typically caused by missing native modules.
Reinstalling may help, but pay attention to errors in native module build steps en route.
`
}
`,
ResinExpiredToken: () => stripIndent`
Looks like your session token is expired.
Please try logging in again with:
$ resin login`,
};
exports.handle = function(error: any) {
let message = interpret(error);
if (message == null) {
return;
}
if (process.env.DEBUG) {
message = error.stack;
}
patterns.printErrorMessage(message!);
return captureException(error)
.timeout(1000)
.catch(function() {
// Ignore any errors (from error logging, or timeouts)
})
.finally(() => process.exit(error.exitCode || 1));
};

View File

@ -1,24 +0,0 @@
_ = require('lodash')
Mixpanel = require('mixpanel')
Promise = require('bluebird')
resin = require('resin-sdk')
packageJSON = require('../package.json')
exports.getLoggerInstance = _.memoize ->
return resin.models.config.getMixpanelToken().then(Mixpanel.init)
exports.trackCommand = (capitanoCommand) ->
return Promise.props
resinUrl: resin.settings.get('resinUrl')
username: resin.auth.whoami()
mixpanel: exports.getLoggerInstance()
.then (data) ->
data.mixpanel.track "[CLI] #{capitanoCommand.command}",
distinct_id: data.username
argv: process.argv.join(' ')
version: packageJSON.version
node: process.version
arch: process.arch
resinUrl: data.resinUrl
platform: process.platform
command: capitanoCommand

48
lib/events.ts Normal file
View File

@ -0,0 +1,48 @@
import * as Capitano from 'capitano';
import _ = require('lodash');
import Mixpanel = require('mixpanel');
import Raven = require('raven');
import Promise = require('bluebird');
import ResinSdk = require('resin-sdk');
import packageJSON = require('../package.json');
const resin = ResinSdk.fromSharedOptions();
const getMatchCommandAsync = Promise.promisify(Capitano.state.getMatchCommand);
const getMixpanel = _.memoize<any>(() =>
resin.models.config
.getAll()
.get('mixpanelToken')
.then(Mixpanel.init),
);
export function trackCommand(capitanoCli: Capitano.Cli) {
return Promise.props({
resinUrl: resin.settings.get('resinUrl'),
username: resin.auth.whoami().catchReturn(undefined),
mixpanel: getMixpanel(),
})
.then(({ username, resinUrl, mixpanel }) => {
return getMatchCommandAsync(capitanoCli.command).then(command => {
Raven.mergeContext({
user: {
id: username,
username,
},
});
return mixpanel.track(`[CLI] ${command.signature.toString()}`, {
distinct_id: username,
argv: process.argv.join(' '),
version: packageJSON.version,
node: process.version,
arch: process.arch,
resinUrl,
platform: process.platform,
command: capitanoCli,
});
});
})
.timeout(100)
.catchReturn(undefined);
}

747
lib/utils/compose.coffee Normal file
View File

@ -0,0 +1,747 @@
Promise = require('bluebird')
path = require('path')
exports.appendProjectOptions = appendProjectOptions = (opts) ->
opts.concat [
{
signature: 'projectName'
parameter: 'projectName'
description: 'Specify an alternate project name; default is the directory name'
alias: 'n'
},
]
exports.appendOptions = (opts) ->
appendProjectOptions(opts).concat [
{
signature: 'emulated'
description: 'Run an emulated build using Qemu'
boolean: true
alias: 'e'
},
{
signature: 'logs'
description: 'Display full log output'
boolean: true
},
]
exports.generateOpts = (options) ->
fs = require('mz/fs')
fs.realpath(options.source || '.').then (projectPath) ->
projectName: options.projectName
projectPath: projectPath
inlineLogs: !!options.logs
compositionFileNames = [
'docker-compose.yml'
'docker-compose.yaml'
]
# look into the given directory for valid compose files and return
# the contents of the first one found.
exports.resolveProject = resolveProject = (rootDir) ->
fs = require('mz/fs')
Promise.any compositionFileNames.map (filename) ->
fs.readFile(path.join(rootDir, filename), 'utf-8')
# Parse the given composition and return a structure with info. Input is:
# - composePath: the *absolute* path to the directory containing the compose file
# - composeStr: the contents of the compose file, as a string
createProject = (composePath, composeStr, projectName = null) ->
yml = require('js-yaml')
compose = require('resin-compose-parse')
# both methods below may throw.
composition = yml.safeLoad(composeStr, schema: yml.FAILSAFE_SCHEMA)
composition = compose.normalize(composition)
projectName ?= path.basename(composePath)
descriptors = compose.parse(composition).map (descr) ->
# generate an image name based on the project and service names
# if one is not given and the service requires a build
if descr.image.context? and not descr.image.tag?
descr.image.tag = [ projectName, descr.serviceName ].join('_').toLowerCase()
return descr
return {
path: composePath,
name: projectName,
composition,
descriptors
}
# high-level function resolving a project and creating a composition out
# of it in one go. if image is given, it'll create a default project for
# that without looking for a project. falls back to creating a default
# project if none is found at the given projectPath.
exports.loadProject = (logger, projectPath, projectName, image) ->
compose = require('resin-compose-parse')
logger.logDebug('Loading project...')
Promise.try ->
if image?
logger.logInfo("Creating default composition with image: #{image}")
return compose.defaultComposition(image)
logger.logDebug('Resolving project...')
resolveProject(projectPath)
.tap ->
logger.logInfo('Compose file detected')
.catch (e) ->
logger.logDebug("Failed to resolve project: #{e}")
logger.logInfo("Creating default composition with source: #{projectPath}")
return compose.defaultComposition()
.then (composeStr) ->
logger.logDebug('Creating project...')
createProject(projectPath, composeStr, projectName)
toPosixPath = (systemPath) ->
path = require('path')
systemPath.replace(new RegExp('\\' + path.sep, 'g'), '/')
exports.tarDirectory = tarDirectory = (dir) ->
tar = require('tar-stream')
klaw = require('klaw')
path = require('path')
fs = require('mz/fs')
streamToPromise = require('stream-to-promise')
{ FileIgnorer } = require('./ignore')
getFiles = ->
streamToPromise(klaw(dir))
.filter((item) -> not item.stats.isDirectory())
.map((item) -> item.path)
ignore = new FileIgnorer(dir)
pack = tar.pack()
getFiles(dir)
.each (file) ->
type = ignore.getIgnoreFileType(path.relative(dir, file))
if type?
ignore.addIgnoreFile(file, type)
.filter(ignore.filter)
.map (file) ->
relPath = path.relative(path.resolve(dir), file)
Promise.join relPath, fs.stat(file), fs.readFile(file),
(filename, stats, data) ->
pack.entry({ name: toPosixPath(filename), size: stats.size, mode: stats.mode }, data)
.then ->
pack.finalize()
return pack
truncateString = (str, len) ->
return str if str.length < len
str = str.slice(0, len)
# return everything up to the last line. this is a cheeky way to avoid
# having to deal with splitting the string midway through some special
# character sequence.
return str.slice(0, str.lastIndexOf('\n'))
LOG_LENGTH_MAX = 512 * 1024 # 512KB
exports.buildProject = (
docker, logger,
projectPath, projectName, composition,
arch, deviceType,
emulated, buildOpts,
inlineLogs
) ->
_ = require('lodash')
humanize = require('humanize')
compose = require('resin-compose-parse')
builder = require('resin-multibuild')
transpose = require('docker-qemu-transpose')
qemu = require('./qemu')
logger.logInfo("Building for #{arch}/#{deviceType}")
imageDescriptors = compose.parse(composition)
imageDescriptorsByServiceName = _.keyBy(imageDescriptors, 'serviceName')
if inlineLogs
renderer = new BuildProgressInline(logger.streams['build'], imageDescriptors)
else
tty = require('./tty')(process.stdout)
renderer = new BuildProgressUI(tty, imageDescriptors)
renderer.start()
qemu.installQemuIfNeeded(emulated, logger)
.tap (needsQemu) ->
return if not needsQemu
logger.logInfo('Emulation is enabled')
# Copy qemu into all build contexts
Promise.map imageDescriptors, (d) ->
return if not d.image.context? # external image
return qemu.copyQemu(path.join(projectPath, d.image.context))
.then (needsQemu) ->
# Tar up the directory, ready for the build stream
tarDirectory(projectPath)
.then (tarStream) ->
builder.splitBuildStream(composition, tarStream)
.tap (tasks) ->
# Updates each task as a side-effect
builder.performResolution(tasks, arch, deviceType)
.map (task) ->
if not task.external and not task.resolved
throw new Error(
"Project type for service '#{task.serviceName}' could not be determined. " +
'Please add a Dockerfile'
)
.map (task) ->
d = imageDescriptorsByServiceName[task.serviceName]
# multibuild parses the composition internally so any tags we've
# set before are lost; re-assign them here
task.tag ?= [ projectName, task.serviceName ].join('_').toLowerCase()
if d.image.context?
d.image.tag = task.tag
# configure build opts appropriately
task.dockerOpts ?= {}
_.merge(task.dockerOpts, buildOpts, { t: task.tag })
if d.image.context?.args?
task.dockerOpts.buildargs ?= {}
_.merge(task.dockerOpts.buildargs, d.image.context.args)
# Get the service-specific log stream
# Caveat: `multibuild.BuildTask` defines no `logStream` property
# but it's convenient to store it there; it's JS ultimately.
task.logStream = renderer.streams[task.serviceName]
task.logBuffer = []
# Setup emulation if needed
return [ task, null ] if task.external or not needsQemu
binPath = qemu.qemuPathInContext(path.join(projectPath, task.context))
transpose.transposeTarStream task.buildStream,
hostQemuPath: toPosixPath(binPath)
containerQemuPath: "/tmp/#{qemu.QEMU_BIN_NAME}"
.then (stream) ->
task.buildStream = stream
.return([ task, binPath ])
.map ([ task, qemuPath ]) ->
Promise.resolve(task).tap (task) ->
captureStream = buildLogCapture(task.external, task.logBuffer)
if task.external
# External image -- there's no build to be performed,
# just follow pull progress.
captureStream.pipe(task.logStream)
task.progressHook = pullProgressAdapter(captureStream)
else
task.streamHook = (stream) ->
stream = createLogStream(stream)
if qemuPath?
buildThroughStream = transpose.getBuildThroughStream
hostQemuPath: toPosixPath(qemuPath)
containerQemuPath: "/tmp/#{qemu.QEMU_BIN_NAME}"
rawStream = stream.pipe(buildThroughStream)
else
rawStream = stream
# `stream` sends out raw strings in contrast to `task.progressHook`
# where we're given objects. capture these strings as they come
# before we parse them.
rawStream
.pipe(dropEmptyLinesStream())
.pipe(captureStream)
.pipe(buildProgressAdapter(inlineLogs))
.pipe(task.logStream)
.then (tasks) ->
logger.logDebug 'Prepared tasks; building...'
builder.performBuilds(tasks, docker)
.map (builtImage) ->
if not builtImage.successful
builtImage.error.serviceName = builtImage.serviceName
throw builtImage.error
d = imageDescriptorsByServiceName[builtImage.serviceName]
task = _.find(tasks, serviceName: builtImage.serviceName)
image =
serviceName: d.serviceName
name: d.image.tag ? d.image
logs: truncateString(task.logBuffer.join('\n'), LOG_LENGTH_MAX)
props:
dockerfile: builtImage.dockerfile
projectType: builtImage.projectType
# Times here are timestamps, so test whether they're null
# before creating a date out of them, as `new Date(null)`
# creates a date representing UNIX time 0.
if (startTime = builtImage.startTime)
image.props.startTime = new Date(startTime)
if (endTime = builtImage.endTime)
image.props.endTime = new Date(endTime)
docker.getImage(image.name).inspect().get('Size').then (size) ->
image.props.size = size
.return(image)
.tap (images) ->
summary = _(images).map ({ serviceName, props }) ->
[ serviceName, "Image size: #{humanize.filesize(props.size)}" ]
.fromPairs()
.value()
renderer.end(summary)
.finally(renderer.end)
createRelease = (apiEndpoint, auth, userId, appId, composition) ->
_ = require('lodash')
crypto = require('crypto')
releaseMod = require('resin-release')
client = releaseMod.createClient({ apiEndpoint, auth })
releaseMod.create
client: client
user: userId
application: appId
composition: composition
source: 'local'
commit: crypto.pseudoRandomBytes(16).toString('hex').toLowerCase()
.then ({ release, serviceImages }) ->
release = _.omit(release, [
'created_at'
'belongs_to__application'
'is_created_by__user'
'__metadata'
])
_.keys serviceImages, (serviceName) ->
serviceImages[serviceName] = _.omit(serviceImages[serviceName], [
'created_at'
'is_a_build_of__service'
'__metadata'
])
return { client, release, serviceImages }
tagServiceImages = (docker, images, serviceImages) ->
Promise.map images, (d) ->
serviceImage = serviceImages[d.serviceName]
imageName = serviceImage.is_stored_at__image_location
[ _match, registry, repo, tag = 'latest' ] = /(.*?)\/(.*?)(?::([^/]*))?$/.exec(imageName)
name = "#{registry}/#{repo}"
docker.getImage(d.name).tag({ repo: name, tag, force: true })
.then ->
docker.getImage("#{name}:#{tag}")
.then (localImage) ->
serviceName: d.serviceName
serviceImage: serviceImage
localImage: localImage
registry: registry
repo: repo
logs: d.logs
props: d.props
authorizePush = (tokenAuthEndpoint, registry, images) ->
_ = require('lodash')
sdk = require('resin-sdk').fromSharedOptions()
if not _.isArray(images)
images = [ images ]
sdk.request.send
baseUrl: tokenAuthEndpoint
url: '/auth/v1/token'
qs:
service: registry
scope: images.map (repo) ->
"repository:#{repo}:pull,push"
.get('body')
.get('token')
.catchReturn({})
pushAndUpdateServiceImages = (docker, token, images, afterEach) ->
chalk = require('chalk')
{ DockerProgress } = require('docker-progress')
tty = require('./tty')(process.stdout)
opts = { authconfig: registrytoken: token }
progress = new DockerProgress(dockerToolbelt: docker)
renderer = pushProgressRenderer(tty, chalk.blue('[Push]') + ' ')
reporters = progress.aggregateProgress(images.length, renderer)
Promise.using tty.cursorHidden(), ->
Promise.map images, ({ serviceImage, localImage, props, logs }, index) ->
Promise.join(
localImage.inspect().get('Size')
progress.push(localImage.name, reporters[index], opts).finally(renderer.end)
(size, digest) ->
serviceImage.image_size = size
serviceImage.content_hash = digest
serviceImage.build_log = logs
serviceImage.dockerfile = props.dockerfile
serviceImage.project_type = props.projectType
serviceImage.start_timestamp = props.startTime if props.startTime
serviceImage.end_timestamp = props.endTime if props.endTime
serviceImage.push_timestamp = new Date()
serviceImage.status = 'success'
)
.tapCatch (e) ->
serviceImage.error_message = '' + e
serviceImage.status = 'failed'
.finally ->
afterEach?(serviceImage, props)
exports.deployProject = (
docker, logger,
composition, images,
appId, userId, auth,
apiEndpoint,
skipLogUpload
) ->
_ = require('lodash')
chalk = require('chalk')
releaseMod = require('resin-release')
tty = require('./tty')(process.stdout)
prefix = chalk.cyan('[Info]') + ' '
spinner = createSpinner()
runloop = runSpinner(tty, spinner, "#{prefix}Creating release...")
createRelease(apiEndpoint, auth, userId, appId, composition)
.finally(runloop.end)
.then ({ client, release, serviceImages }) ->
logger.logDebug('Tagging images...')
tagServiceImages(docker, images, serviceImages)
.tap (images) ->
logger.logDebug('Authorizing push...')
authorizePush(apiEndpoint, images[0].registry, _.map(images, 'repo'))
.then (token) ->
logger.logInfo('Pushing images to registry...')
pushAndUpdateServiceImages docker, token, images, (serviceImage) ->
logger.logDebug("Saving image #{serviceImage.is_stored_at__image_location}")
if skipLogUpload
delete serviceImage.build_log
releaseMod.updateImage(client, serviceImage.id, serviceImage)
.finally ->
logger.logDebug('Untagging images...')
Promise.map images, ({ localImage }) ->
localImage.remove()
.then ->
release.status = 'success'
.tapCatch (e) ->
release.status = 'failed'
.finally ->
runloop = runSpinner(tty, spinner, "#{prefix}Saving release...")
release.end_timestamp = new Date()
releaseMod.updateRelease(client, release.id, release)
.finally(runloop.end)
.return(release)
# utilities
renderProgressBar = (percentage, stepCount) ->
_ = require('lodash')
percentage = _.clamp(percentage, 0, 100)
barCount = stepCount * percentage // 100
spaceCount = stepCount - barCount
bar = "[#{_.repeat('=', barCount)}>#{_.repeat(' ', spaceCount)}]"
return "#{bar} #{_.padStart(percentage, 3)}%"
pushProgressRenderer = (tty, prefix) ->
fn = (e) ->
{ error, percentage } = e
throw new Error(error) if error?
bar = renderProgressBar(percentage, 40)
tty.replaceLine("#{prefix}#{bar}\r")
fn.end = ->
tty.clearLine()
return fn
createLogStream = (input) ->
split = require('split')
stripAnsi = require('strip-ansi-stream')
return input.pipe(stripAnsi()).pipe(split())
dropEmptyLinesStream = ->
through = require('through2')
through (data, enc, cb) ->
str = data.toString('utf-8')
@push(str) if str.trim()
cb()
buildLogCapture = (objectMode, buffer) ->
through = require('through2')
through { objectMode }, (data, enc, cb) ->
# data from pull stream
if data.error
buffer.push("#{data.error}")
else if data.progress and data.status
buffer.push("#{data.progress}% #{data.status}")
else if data.status
buffer.push("#{data.status}")
# data from build stream
else
buffer.push(data)
cb(null, data)
buildProgressAdapter = (inline) ->
through = require('through2')
stepRegex = /^\s*Step\s+(\d+)\/(\d+)\s*: (.+)$/
[ step, numSteps, progress ] = [ null, null, undefined ]
through { objectMode: true }, (str, enc, cb) ->
return cb(null, str) if not str?
if inline
return cb(null, { status: str })
if /^Successfully tagged /.test(str)
progress = undefined
else
if (match = stepRegex.exec(str))
step = match[1]
numSteps ?= match[2]
str = match[3]
if step?
str = "Step #{step}/#{numSteps}: #{str}"
progress = parseInt(step, 10) * 100 // parseInt(numSteps, 10)
cb(null, { status: str, progress })
pullProgressAdapter = (outStream) ->
return ({ status, id, percentage, error, errorDetail }) ->
if status?
status = status.replace(/^Status: /, '')
if id?
status = "#{id}: #{status}"
if percentage is 100
percentage = undefined
outStream.write
status: status
progress: percentage
error: errorDetail?.message ? error
createSpinner = ->
chars = '|/-\\'
index = 0
-> chars[(index++) % chars.length]
runSpinner = (tty, spinner, msg) ->
runloop = createRunLoop ->
tty.clearLine()
tty.writeLine("#{msg} #{spinner()}")
tty.cursorUp()
runloop.onEnd = ->
tty.clearLine()
tty.writeLine(msg)
return runloop
createRunLoop = (tick) ->
timerId = setInterval(tick, 1000 / 10)
runloop = {
onEnd: ->
end: ->
clearInterval(timerId)
runloop.onEnd()
}
return runloop
class BuildProgressUI
constructor: (tty, descriptors) ->
_ = require('lodash')
chalk = require('chalk')
through = require('through2')
eventHandler = @_handleEvent
services = _.map(descriptors, 'serviceName')
streams = _(services).map (service) ->
stream = through.obj (event, _enc, cb) ->
eventHandler(service, event)
cb()
stream.pipe(tty.stream)
[ service, stream ]
.fromPairs()
.value()
@_tty = tty
@_serviceToDataMap = {}
@_services = services
# Logger magically prefixes the log line with [Build] etc., but it doesn't
# work well with the spinner we're also showing. Manually build the prefix
# here and bypass the logger.
prefix = chalk.blue('[Build]') + ' '
offset = 10 # account for escape sequences inserted for colouring
@_prefixWidth = offset + prefix.length + _.max(_.map(services, 'length'))
@_prefix = prefix
# these are to handle window wrapping
@_maxLineWidth = null
@_lineWidths = []
@_startTime = null
@_ended = false
@_cancelled = false
@_spinner = createSpinner()
@streams = streams
_handleEvent: (service, event) =>
@_serviceToDataMap[service] = event
_handleInterrupt: =>
@_cancelled = true
@end()
process.exit(130) # 128 + SIGINT
start: =>
process.on('SIGINT', @_handleInterrupt)
@_tty.hideCursor()
@_services.forEach (service) =>
@streams[service].write({ status: 'Preparing...' })
@_runloop = createRunLoop(@_display)
@_startTime = Date.now()
end: (summary = null) =>
return if @_ended
@_ended = true
process.removeListener('SIGINT', @_handleInterrupt)
@_runloop.end()
@_runloop = null
@_clear()
@_renderStatus(true)
@_renderSummary(summary ? @_getServiceSummary())
@_tty.showCursor()
_display: =>
@_clear()
@_renderStatus()
@_renderSummary(@_getServiceSummary())
@_tty.cursorUp(@_services.length + 1) # for status line
_clear: ->
@_tty.deleteToEnd()
@_maxLineWidth = @_tty.currentWindowSize().width
_getServiceSummary: ->
_ = require('lodash')
services = @_services
serviceToDataMap = @_serviceToDataMap
_(services).map (service) ->
{ status, progress, error } = serviceToDataMap[service] ? {}
if error
return "#{error}"
else if progress
bar = renderProgressBar(progress, 20)
return "#{bar} #{status}" if status
return "#{bar}"
else if status
return "#{status}"
else
return 'Waiting...'
.map (data, index) ->
[ services[index], data ]
.fromPairs()
.value()
_renderStatus: (end = false) ->
moment = require('moment')
require('moment-duration-format')(moment)
@_tty.clearLine()
@_tty.write(@_prefix)
if end and @_cancelled
@_tty.writeLine('Build cancelled')
else if end
serviceCount = @_services.length
serviceStr = if serviceCount is 1 then '1 service' else "#{serviceCount} services"
runTime = Date.now() - @_startTime
durationStr = moment.duration(runTime // 1000, 'seconds').format()
@_tty.writeLine("Built #{serviceStr} in #{durationStr}")
else
@_tty.writeLine("Building services... #{@_spinner()}")
_renderSummary: (serviceToStrMap) ->
_ = require('lodash')
chalk = require('chalk')
truncate = require('cli-truncate')
strlen = require('string-width')
@_services.forEach (service, index) =>
str = _.padEnd(@_prefix + chalk.bold(service), @_prefixWidth)
str += serviceToStrMap[service]
if @_maxLineWidth?
str = truncate(str, @_maxLineWidth)
@_lineWidths[index] = strlen(str)
@_tty.clearLine()
@_tty.writeLine(str)
class BuildProgressInline
constructor: (outStream, descriptors) ->
_ = require('lodash')
through = require('through2')
services = _.map(descriptors, 'serviceName')
eventHandler = @_renderEvent
streams = _(services).map (service) ->
stream = through.obj (event, _enc, cb) ->
eventHandler(service, event)
cb()
stream.pipe(outStream)
[ service, stream ]
.fromPairs()
.value()
offset = 10 # account for escape sequences inserted for colouring
@_prefixWidth = offset + _.max(_.map(services, 'length'))
@_outStream = outStream
@_services = services
@_startTime = null
@_ended = false
@streams = streams
start: =>
@_outStream.write('Building services...\n')
@_services.forEach (service) =>
@streams[service].write({ status: 'Preparing...' })
@_startTime = Date.now()
end: (summary = null) =>
moment = require('moment')
require('moment-duration-format')(moment)
return if @_ended
@_ended = true
if summary?
@_services.forEach (service) =>
@_renderEvent(service, summary[service])
if @_cancelled
@_outStream.write('Build cancelled\n')
else
serviceCount = @_services.length
serviceStr = if serviceCount is 1 then '1 service' else "#{serviceCount} services"
runTime = Date.now() - @_startTime
durationStr = moment.duration(runTime // 1000, 'seconds').format()
@_outStream.write("Built #{serviceStr} in #{durationStr}\n")
_renderEvent: (service, event) =>
_ = require('lodash')
chalk = require('chalk')
str = do ->
{ status, error } = event
if error
return "#{error}"
else if status
return "#{status}"
else
return 'Waiting...'
prefix = _.padEnd(chalk.bold(service), @_prefixWidth)
@_outStream.write(prefix)
@_outStream.write(str)
@_outStream.write('\n')

32
lib/utils/compose.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
import * as Bluebird from 'bluebird';
import * as Stream from 'stream';
import { Composition } from 'resin-compose-parse';
import Logger = require('./logger');
interface Image {
context: string;
tag: string;
}
interface Descriptor {
image: Image | string;
serviceName: string;
}
export function resolveProject(projectRoot: string): Bluebird<string>;
export interface ComposeProject {
path: string;
name: string;
composition: Composition;
descriptors: Descriptor[];
}
export function loadProject(
logger: Logger,
projectPath: string,
projectName: string,
image?: string,
): Bluebird<ComposeProject>;
export function tarDirectory(source: string): Promise<Stream.Readable>;

156
lib/utils/config.ts Normal file
View File

@ -0,0 +1,156 @@
/*
Copyright 2016-2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as fs from 'fs';
import Promise = require('bluebird');
import ResinSdk = require('resin-sdk');
import deviceConfig = require('resin-device-config');
import * as semver from 'resin-semver';
const resin = ResinSdk.fromSharedOptions();
function readRootCa(): Promise<string | void> {
const caFile = process.env.NODE_EXTRA_CA_CERTS;
if (!caFile) {
return Promise.resolve();
}
return Promise.fromCallback(cb =>
fs.readFile(caFile, { encoding: 'utf8' }, cb),
)
.then(pem => Buffer.from(pem).toString('base64'))
.catch({ code: 'ENOENT' }, () => {});
}
export function generateBaseConfig(
application: ResinSdk.Application,
options: { version?: string; appUpdatePollInterval?: number },
) {
if (options.appUpdatePollInterval) {
options = {
...options,
appUpdatePollInterval: options.appUpdatePollInterval * 60 * 1000,
};
}
return Promise.props({
userId: resin.auth.getUserId(),
username: resin.auth.whoami(),
apiUrl: resin.settings.get('apiUrl'),
vpnUrl: resin.settings.get('vpnUrl'),
registryUrl: resin.settings.get('registryUrl'),
deltaUrl: resin.settings.get('deltaUrl'),
apiConfig: resin.models.config.getAll(),
rootCA: readRootCa().catch(() => {
console.warn('Could not read root CA');
}),
}).then(results => {
return deviceConfig.generate(
{
application,
user: {
id: results.userId,
username: results.username,
},
endpoints: {
api: results.apiUrl,
vpn: results.vpnUrl,
registry: results.registryUrl,
delta: results.deltaUrl,
},
pubnub: results.apiConfig.pubnub,
mixpanel: {
token: results.apiConfig.mixpanelToken,
},
balenaRootCA: results.rootCA,
},
options,
);
});
}
export function generateApplicationConfig(
application: ResinSdk.Application,
options: { version?: string },
) {
return generateBaseConfig(application, options).tap(config => {
if (semver.satisfies(options.version, '>=2.7.8')) {
return addProvisioningKey(config, application.id);
} else {
return addApplicationKey(config, application.id);
}
});
}
export function generateDeviceConfig(
device: ResinSdk.Device & { belongs_to__application: ResinSdk.PineDeferred },
deviceApiKey: string | true | null,
options: { version?: string },
) {
return resin.models.application
.get(device.belongs_to__application.__id)
.then(application => {
return generateBaseConfig(application, options).tap(config => {
if (deviceApiKey) {
return addDeviceKey(config, device.uuid, deviceApiKey);
} else if (semver.satisfies(options.version, '>=2.0.3')) {
return addDeviceKey(config, device.uuid, true);
} else {
return addApplicationKey(config, application.id);
}
});
})
.then(config => {
// Associate a device, to prevent the supervisor
// from creating another one on its own.
config.registered_at = Math.floor(Date.now() / 1000);
config.deviceId = device.id;
config.uuid = device.uuid;
return config;
});
}
function addApplicationKey(config: any, applicationNameOrId: string | number) {
return resin.models.application
.generateApiKey(applicationNameOrId)
.tap(apiKey => {
config.apiKey = apiKey;
});
}
function addProvisioningKey(config: any, applicationNameOrId: string | number) {
return resin.models.application
.generateProvisioningKey(applicationNameOrId)
.tap(apiKey => {
config.apiKey = apiKey;
});
}
function addDeviceKey(
config: any,
uuid: string,
customDeviceApiKey: string | true,
) {
return Promise.try(() => {
if (customDeviceApiKey === true) {
return resin.models.device.generateDeviceKey(uuid);
} else {
return customDeviceApiKey;
}
}).tap(deviceApiKey => {
config.deviceApiKey = deviceApiKey;
});
}

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