├── app
└── .gitkeep
├── addon
└── .gitkeep
├── vendor
└── .gitkeep
├── tests
├── unit
│ └── .gitkeep
├── integration
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ └── application.hbs
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ └── environment.js
├── test-helper.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── start-app.js
│ └── module-for-acceptance.js
├── .jshintrc
└── index.html
├── .watchmanconfig
├── .bowerrc
├── config
├── environment.js
└── ember-try.js
├── bower.json
├── testem.json
├── .npmignore
├── testem.js
├── .ember-cli
├── .gitignore
├── ember-cli-build.js
├── index.js
├── .jshintrc
├── templates
├── fastboot.conf
└── nginx.conf
├── .editorconfig
├── lib
├── tasks
│ ├── upload-app.js
│ ├── upload-file.js
│ ├── npm-install.js
│ └── remote-command.js
├── digitalocean-deploy-plugin.js
└── commands
│ └── provision.js
├── .travis.yml
├── LICENSE.md
├── README.md
└── package.json
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
Welcome to Ember
2 |
3 | {{outlet}}
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default function destroyApp(application) {
4 | Ember.run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-deploy-digitalocean",
3 | "dependencies": {
4 | "ember": "~2.4.1",
5 | "ember-cli-shims": "0.1.0",
6 | "ember-cli-test-loader": "0.2.2",
7 | "ember-qunit-notifications": "0.1.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "qunit",
3 | "test_page": "tests/index.html?hidepassed",
4 | "disable_watching": true,
5 | "launch_in_ci": [
6 | "PhantomJS"
7 | ],
8 | "launch_in_dev": [
9 | "PhantomJS",
10 | "Chrome"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | const Router = Ember.Router.extend({
5 | location: config.locationType
6 | });
7 |
8 | Router.map(function() {
9 | });
10 |
11 | export default Router;
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .jshintrc
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | "framework": "qunit",
4 | "test_page": "tests/index.html?hidepassed",
5 | "disable_watching": true,
6 | "launch_in_ci": [
7 | "PhantomJS"
8 | ],
9 | "launch_in_dev": [
10 | "PhantomJS",
11 | "Chrome"
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | .env.deploy*
12 | # misc
13 | /.sass-cache
14 | /connect.lock
15 | /coverage/*
16 | /libpeerconnection.log
17 | npm-debug.log
18 | testem.log
19 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | let App;
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import config from '../../config/environment';
4 |
5 | export default function startApp(attrs) {
6 | let application;
7 |
8 | let attributes = Ember.merge({}, config.APP);
9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
10 |
11 | Ember.run(() => {
12 | application = Application.create(attributes);
13 | application.setupForTesting();
14 | application.injectTestHelpers();
15 | });
16 |
17 | return application;
18 | }
19 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | /* global require, module */
3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | var app = new EmberAddon(defaults, {
7 | // Add options here
8 | });
9 |
10 | /*
11 | This build file specifies the options for the dummy test app of this
12 | addon, located in `/tests/dummy`
13 | This build file does *not* influence how the addon or the app using it
14 | behave. You most likely want to be modifying `./index.js` or app's build file
15 | */
16 |
17 | return app.toTree();
18 | };
19 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 | var DeployPluginBase = require('ember-cli-deploy-plugin');
4 | var path = require('path');
5 | var DigitalOceanDeployPlugin = require('./lib/digitalocean-deploy-plugin');
6 | var ProvisionCommand = require('./lib/commands/provision');
7 |
8 | module.exports = {
9 | name: 'ember-cli-deploy-digitalocean',
10 |
11 | includedCommands: function() {
12 | return { 'do:provision': ProvisionCommand };
13 | },
14 |
15 | createDeployPlugin: function(options) {
16 | return new DigitalOceanDeployPlugin({
17 | name: options.name
18 | });
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import startApp from '../helpers/start-app';
3 | import destroyApp from '../helpers/destroy-app';
4 |
5 | export default function(name, options = {}) {
6 | module(name, {
7 | beforeEach() {
8 | this.application = startApp();
9 |
10 | if (options.beforeEach) {
11 | options.beforeEach.apply(this, arguments);
12 | }
13 | },
14 |
15 | afterEach() {
16 | if (options.afterEach) {
17 | options.afterEach.apply(this, arguments);
18 | }
19 |
20 | destroyApp(this.application);
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esnext": true,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/templates/fastboot.conf:
--------------------------------------------------------------------------------
1 | description "A job file for starting up the Fastboot service for Ember."
2 | author "Iheanyi Ekechukwu"
3 | start on filesystem or runlevel [2345]
4 | stop on shutdown
5 |
6 | pre-start script
7 | npm install -g ember-fastboot-server
8 | echo "Starting Fastboot server" >> /var/log/fastboot.log
9 | end script
10 |
11 | script
12 | export HOME
13 | echo $$ > /var/run/fastboot.pid
14 | exec ember-fastboot /etc/nginx/sites-enabled/ember-app >> /var/log/fastboot.log 2>&1
15 | end script
16 |
17 | pre-stop script
18 | rm /var/run/fastboot.pid
19 | echo "Fastboot Server Stopping" >> /var/log/fastboot.log
20 | end script
21 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/lib/tasks/upload-app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class UploadAppTask {
4 | constructor(options) {
5 | options = options || {};
6 | this.distDir = options.distDir;
7 | this.scpClient = options.scpClient;
8 | this.scpConfig = options.scpConfig
9 | }
10 |
11 | run() {
12 | return this.runUploadApp();
13 | }
14 |
15 | runUploadApp() {
16 | return new Promise((resolve, reject) => {
17 | var scpClient = this.scpClient;
18 |
19 | scpClient.scp(this.distDir, this.scpConfig, err => {
20 | if (err) {
21 | reject(err);
22 | }
23 |
24 | resolve();
25 | });
26 | });
27 | }
28 | }
29 |
30 | module.exports = UploadAppTask;
31 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "0.12"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | env:
13 | - EMBER_TRY_SCENARIO=default
14 | - EMBER_TRY_SCENARIO=ember-1-13
15 | - EMBER_TRY_SCENARIO=ember-release
16 | - EMBER_TRY_SCENARIO=ember-beta
17 | - EMBER_TRY_SCENARIO=ember-canary
18 |
19 | matrix:
20 | fast_finish: true
21 | allow_failures:
22 | - env: EMBER_TRY_SCENARIO=ember-canary
23 |
24 | before_install:
25 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
26 | - "npm config set spin false"
27 | - "npm install -g npm@^2"
28 |
29 | install:
30 | - npm install -g bower
31 | - npm install
32 | - bower install
33 |
34 | script:
35 | - ember try $EMBER_TRY_SCENARIO test
36 |
--------------------------------------------------------------------------------
/lib/tasks/upload-file.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class UploadFileTask {
4 | constructor(options) {
5 | options = options || {};
6 | this.log = options.log;
7 | this.scpClient = options.scpClient;
8 | this.distDir = options.distDir;
9 | this.localFile = options.localFile;
10 | this.remoteFile = options.remoteFile;
11 | }
12 |
13 | run() {
14 | return this.runUploadFile();
15 | }
16 |
17 | runUploadFile() {
18 | var localFile = this.localFile;
19 | var remoteFile = this.remoteFile;
20 |
21 | return new Promise((resolve, reject) => {
22 | this.scpClient.upload(localFile, remoteFile, err => {
23 | if (err) {
24 | this.log(err, {color: 'red'});
25 | reject(err);
26 | }
27 |
28 | resolve();
29 | });
30 | });
31 | }
32 | }
33 |
34 | module.exports = UploadFileTask;
35 |
--------------------------------------------------------------------------------
/lib/tasks/npm-install.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const spawn = require('child_process').spawn;
4 |
5 | class NPMInstallTask {
6 | constructor(options) {
7 | options = options || {};
8 | this.distDir = options.distDir;
9 | this.log = options.log;
10 | }
11 |
12 | run() {
13 | return this.runNPMInstall();
14 | }
15 |
16 | runNPMInstall() {
17 | return new Promise((resolve, reject) => {
18 | let distDir = this.distDir;
19 |
20 | this.log(`running npm install in ${distDir}`);
21 |
22 | let npmInstall = spawn('npm', ['install'], {
23 | cwd: distDir
24 | });
25 |
26 | let log = data => this.log(data, { verbose: true });
27 |
28 | npmInstall.stdout.on('data', log);
29 | npmInstall.stderr.on('data', log);
30 |
31 | npmInstall.on('close', code => {
32 | if (code === 0) resolve();
33 | else reject();
34 | });
35 | });
36 | }
37 | }
38 |
39 | module.exports = NPMInstallTask;
40 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": true,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esnext": true,
51 | "unused": true
52 | }
53 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/templates/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes 5; ## Default: 1
2 |
3 | events {
4 | worker_connections 4096;
5 | }
6 |
7 | http {
8 | include /etc/nginx/mime.types*;
9 | default_type application/octet-stream;
10 | sendfile on;
11 | tcp_nopush on;
12 | tcp_nodelay on;
13 | keepalive_timeout 65;
14 | types_hash_max_size 2048;
15 |
16 | server {
17 | listen 80;
18 | root /etc/nginx/sites-enabled/ember-app;
19 | index index.html index.html;
20 |
21 | server_name localhost;
22 |
23 | #location / {
24 | # For static home page.
25 | # index index.html;
26 | #}
27 |
28 | location / {
29 | index index.html;
30 | proxy_pass http://127.0.0.1:3000;
31 | proxy_set_header X-Real-IP $remote_addr;
32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
33 | proxy_set_header Host $http_host;
34 | proxy_set_header X-NginX-Proxy true;
35 | }
36 |
37 | location ~ \.(ttf|ttc|otf|eot|woff|font.css|css)$ {
38 | add_header Access-Control-Allow-Origin "*";
39 | }
40 |
41 | location /assets {
42 | autoindex on;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{content-for "body-footer"}}
32 | {{content-for "test-body-footer"}}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'default',
6 | bower: {
7 | dependencies: { }
8 | }
9 | },
10 | {
11 | name: 'ember-1-13',
12 | bower: {
13 | dependencies: {
14 | 'ember': '~1.13.0'
15 | },
16 | resolutions: {
17 | 'ember': '~1.13.0'
18 | }
19 | }
20 | },
21 | {
22 | name: 'ember-release',
23 | bower: {
24 | dependencies: {
25 | 'ember': 'components/ember#release'
26 | },
27 | resolutions: {
28 | 'ember': 'release'
29 | }
30 | }
31 | },
32 | {
33 | name: 'ember-beta',
34 | bower: {
35 | dependencies: {
36 | 'ember': 'components/ember#beta'
37 | },
38 | resolutions: {
39 | 'ember': 'beta'
40 | }
41 | }
42 | },
43 | {
44 | name: 'ember-canary',
45 | bower: {
46 | dependencies: {
47 | 'ember': 'components/ember#canary'
48 | },
49 | resolutions: {
50 | 'ember': 'canary'
51 | }
52 | }
53 | }
54 | ]
55 | };
56 |
--------------------------------------------------------------------------------
/lib/tasks/remote-command.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class RemoteCommandTask {
4 | constructor(options) {
5 | options = options || {};
6 | this.command = options.command;
7 | this.log = options.log;
8 | this.conn = options.conn;
9 | this.ipAddress = options.ipAddress;
10 | }
11 |
12 | run() {
13 | return this.runRemoteCommand();
14 | }
15 |
16 | runRemoteCommand() {
17 | return new Promise((resolve, reject) => {
18 | var conn = this.conn;
19 | var command = this.command;
20 | conn.on('ready', () => {
21 | conn.exec(command, (err, stream) => {
22 | if (err) reject(err);
23 |
24 | stream.on('data', (data) => {
25 | this.log('STDOUT: ' + data);
26 | }).on('end', (data) => {
27 | resolve();
28 | });
29 | })
30 | }).connect({
31 | host: this.ipAddress,
32 | port: 22,
33 | username: process.env.DROPLET_USERNAME || 'root',
34 | privateKey: require('fs').readFileSync(process.env.PRIVATE_KEY_DIR),
35 | password: process.env.DROPLET_PASSWORD,
36 | passphrase: process.env.PASSPHRASE,
37 | });
38 | });
39 | }
40 | }
41 |
42 | module.exports = RemoteCommandTask;
43 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: environment,
7 | baseURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. 'with-controller': true
13 | }
14 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | }
20 | };
21 |
22 | if (environment === 'development') {
23 | // ENV.APP.LOG_RESOLVER = true;
24 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
25 | // ENV.APP.LOG_TRANSITIONS = true;
26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
27 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
28 | }
29 |
30 | if (environment === 'test') {
31 | // Testem prefers this...
32 | ENV.baseURL = '/';
33 | ENV.locationType = 'none';
34 |
35 | // keep test console output quieter
36 | ENV.APP.LOG_ACTIVE_GENERATION = false;
37 | ENV.APP.LOG_VIEW_LOOKUPS = false;
38 |
39 | ENV.APP.rootElement = '#ember-testing';
40 | }
41 |
42 | if (environment === 'production') {
43 |
44 | }
45 |
46 | return ENV;
47 | };
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ember-cli-deploy-digitalocean
2 |
3 | This README outlines the details of collaborating on this Ember addon.
4 |
5 | ## Installation
6 |
7 | * `git clone` this repository
8 | * `npm install`
9 | * `bower install`
10 |
11 | ## Environment Variables
12 |
13 | * `DO_ACCESS_TOKEN` - Your DigitalOcean API key. Generate one
14 | [here](https://cloud.digitalocean.com/settings/api/tokens).
15 | * `PRIVATE_KEY_DIR` - Location of your private SSH key used for your droplet.
16 | * `PASSPHRASE` - Passphrase for the SSH key.
17 | * `DROPLET_USERNAME` - Username to log into the droplet as (defaults to `root`)
18 | * `DROPLET_PASSWORD` - Password to log into the droplet as (can be left blank if
19 | unused)
20 |
21 | ## Deploying
22 | Run `ember do:provision` in order to provision a droplet for usage with
23 | FastBoot. Note: The best droplet image to use is the `Node` one-click installer. This step must be run in order for FastBoot to work correctly on your
24 | droplet. After this is setup, just run `ember deploy ` and you're
25 | good to go.
26 |
27 | ## Note
28 | Please make sure you also have `ember-cli-deploy-build` installed. This may be
29 | further refined through the use of a lightning pack, but for now, please just
30 | install `ember-cli-deploy-build` as well.
31 |
32 | Thanks to Tom and his [Elastic
33 | Beanstalk](https://github.com/tomdale/ember-cli-deploy-elastic-beanstalk) plugin for a good starting/reference
34 | point.
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-deploy-digitalocean",
3 | "version": "0.0.4",
4 | "description": "The default blueprint for ember-cli addons.",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "build": "ember build",
11 | "start": "ember server",
12 | "test": "ember try:testall"
13 | },
14 | "repository": "https://github.com/iheanyi/ember-cli-deploy-digitalocean",
15 | "engines": {
16 | "node": ">= 0.10.0"
17 | },
18 | "author": "",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "broccoli-asset-rev": "^2.2.0",
22 | "ember-ajax": "0.7.1",
23 | "ember-cli": "2.4.2",
24 | "ember-cli-app-version": "^1.0.0",
25 | "ember-cli-dependency-checker": "^1.2.0",
26 | "ember-cli-htmlbars": "^1.0.1",
27 | "ember-cli-htmlbars-inline-precompile": "^0.3.1",
28 | "ember-cli-inject-live-reload": "^1.3.1",
29 | "ember-cli-qunit": "^1.2.1",
30 | "ember-cli-release": "0.2.8",
31 | "ember-cli-sri": "^2.1.0",
32 | "ember-cli-uglify": "^1.2.0",
33 | "ember-data": "^2.4.0",
34 | "ember-disable-prototype-extensions": "^1.1.0",
35 | "ember-disable-proxy-controllers": "^1.0.1",
36 | "ember-export-application-global": "^1.0.4",
37 | "ember-load-initializers": "^0.5.0",
38 | "ember-resolver": "^2.0.3",
39 | "ember-try": "^0.1.2",
40 | "loader.js": "^4.0.0"
41 | },
42 | "keywords": [
43 | "ember-cli-deploy-plugin",
44 | "ember-addon"
45 | ],
46 | "dependencies": {
47 | "chalk": "^1.1.3",
48 | "do-wrapper": "^3.5.0",
49 | "ember-cli": "github:ember-cli/ember-cli",
50 | "ember-cli-babel": "^5.1.5",
51 | "ember-cli-deploy-plugin": "^0.2.2",
52 | "rsvp": "^3.2.1",
53 | "scp2": "^0.2.2",
54 | "ssh2": "^0.5.0",
55 | "yamljs": "^0.2.7"
56 | },
57 | "ember-addon": {
58 | "configPath": "tests/dummy/config",
59 | "after": "ember-cli-deploy-build"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/digitalocean-deploy-plugin.js:
--------------------------------------------------------------------------------
1 | var DeployPlugin = require('ember-cli-deploy-plugin');
2 | var SSHClient = require('ssh2').Client;
3 | var SCPClient = require('scp2');
4 | var path = require('path');
5 | var RSVP = require('rsvp');
6 | var NPMInstallTask = require('./tasks/npm-install');
7 | var RemoteCommandTask = require('./tasks/remote-command');
8 | var UploadAppTask = require('./tasks/upload-app');
9 | var UploadFileTask = require('./tasks/upload-file');
10 |
11 | const CONFIG_ENV_MAPPING = {
12 | FASTBOOT_DO_APPLICATION: 'dropletName',
13 | FASTBOOT_DO_IP: 'ipAddress',
14 | FASTBOOT_DO_ENVIRONMENT: 'environmentName',
15 | FASTBOOT_DO_ID: 'dropletId',
16 | };
17 |
18 |
19 | module.exports = DeployPlugin.extend({
20 | defaultConfig: {
21 | environment: 'production',
22 | distDir: function(context) {
23 | return context.distDir;
24 | },
25 | distFiles: function(context) {
26 | return context.distFiles || [];
27 | },
28 | fastbootDistDir: function(context) {
29 | return context.fastbootDistDir;
30 | },
31 | fastbootDistFiles: function(context) {
32 | return context.fastbootDistFiles;
33 | },
34 | },
35 |
36 | requiredConfig: ['environment'],
37 |
38 | configure: function() {
39 | var config = this.pluginConfig;
40 |
41 | for (var key in CONFIG_ENV_MAPPING) {
42 | if (process.env[key]) {
43 | config[CONFIG_ENV_MAPPING[key]] = process.env[key];
44 | }
45 | }
46 |
47 | this.conn = new SSHClient();
48 | this.scpConfig = {
49 | host: config.ipAddress,
50 | port: 22,
51 | username: process.env.DROPLET_USERNAME || 'root',
52 | privateKey: require('fs').readFileSync(process.env.PRIVATE_KEY_DIR),
53 | password: process.env.DROPLET_PASSWORD,
54 | passphrase: process.env.PASSPHRASE,
55 | path: '/etc/nginx/sites-enabled/ember-app'
56 | };
57 |
58 | this.scpClient = SCPClient;
59 |
60 | const nginxConfig = Object.assign({}, this.scpConfig, {
61 | path: '/etc/nginx/nginx.conf',
62 | });
63 |
64 |
65 | this.fileClient = new SCPClient.Client(nginxConfig);
66 | this.config = config;
67 | this._super.configure.apply(this, arguments);
68 | },
69 | willUpload: function(context) {
70 | var self = this;
71 | var distDir = context.distDir;
72 |
73 | var npmInstallTask = new NPMInstallTask({
74 | log: this.log.bind(this),
75 | distDir: distDir
76 | });
77 |
78 | var installDependenciesCommand = [
79 | 'sudo apt-get update -y',
80 | 'sudo apt-get upgrade -y',
81 | 'sudo apt-get install -y nginx gcc build-essential',
82 | 'rm /etc/nginx/sites/enabled/default'
83 | ].join(';');
84 |
85 | var provisionTask = new RemoteCommandTask({
86 | log: this.log.bind(this),
87 | command: installDependenciesCommand,
88 | conn: this.conn,
89 | ipAddress: this.config.ipAddress,
90 | });
91 |
92 | return npmInstallTask.run()
93 | .then(provisionTask.run())
94 | .then(() => this.log('Done provisioning droplet!'))
95 | .catch(err => {
96 | throw err;
97 | });
98 | },
99 | upload: function(context) {
100 | this.log('Uploading assets to the droplet!');
101 |
102 | var scpClient = this.scpClient;
103 |
104 | var fileClient = this.fileClient;
105 | var uploadAppTask = new UploadAppTask({
106 | scpClient: this.scpClient,
107 | distDir: context.distDir,
108 | scpConfig: this.scpConfig,
109 | log: this.log.bind(this),
110 | });
111 |
112 | var uploadNginxConfigTask = new UploadFileTask({
113 | localFile: './node_modules/ember-cli-deploy-digitalocean/templates/nginx.conf',
114 | remoteFile: '/etc/nginx/nginx.conf',
115 | scpClient: this.fileClient,
116 | log: this.log.bind(this),
117 | });
118 |
119 | return uploadAppTask.run()
120 | .then(() => {
121 | return uploadNginxConfigTask.run();
122 | });
123 | },
124 | didUpload: function(context) {
125 | var conn = this.conn;
126 |
127 | this.log('Done uploading!');
128 | this.log('Starting application!');
129 | var fileClient = this.fileClient;
130 |
131 | var uploadFastbootConfigTask = new UploadFileTask({
132 | localFile: './node_modules/ember-cli-deploy-digitalocean/templates/fastboot.conf',
133 | remoteFile: '/etc/init/fastboot.conf',
134 | scpClient: this.fileClient,
135 | log: this.log.bind(this),
136 | });
137 |
138 |
139 | var restartServicesCommand = [
140 | "sudo service nginx restart",
141 | "sudo service fastboot restart"
142 | ].join(';');
143 |
144 | var restartServicesTask = new RemoteCommandTask({
145 | log: this.log.bind(this),
146 | command: restartServicesCommand,
147 | conn: this.conn,
148 | ipAddress: this.config.ipAddress,
149 | });
150 |
151 | return uploadFastbootConfigTask.run()
152 | .then(() => {
153 | this.log('Finished uploading Fastboot init script.');
154 | return restartServicesTask.run()
155 | .then(() => {
156 | this.log("Fastboot application has been uploaded and started!");
157 | });
158 | });
159 | }
160 | });
161 |
--------------------------------------------------------------------------------
/lib/commands/provision.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk');
2 | var fs = require('fs');
3 | var path = require('path');
4 | var Promise = require('rsvp').Promise;
5 | var DigitalOcean = require('do-wrapper');
6 |
7 | var CREATE_NEW_DROPLET = 'Create a new droplet';
8 | var UBUNTU_IMAGE_SLUG = 'ubuntu-14-04-x64';
9 | var NODE_IMAGE_SLUG = 'node';
10 | var YAML = require('yamljs');
11 |
12 | module.exports = {
13 | name: 'do:provision',
14 |
15 | description: 'Provisions a DigitalOcean Droplet for FastBoot',
16 |
17 | run: function() {
18 | var ACCESS_TOKEN = process.env.DO_ACCESS_TOKEN;
19 | if (!ACCESS_TOKEN) {
20 | console.log('Access token not provided!');
21 | throw new Error('Environment Variable DO_ACCESS_TOKEN is not set!');
22 | }
23 |
24 | var api = new DigitalOcean(ACCESS_TOKEN);
25 | var ui = this.ui;
26 |
27 | this.api = api;
28 |
29 | var dropletsPromise = new Promise((resolve, reject) => {
30 | this.api.dropletsGetAll({}, function(err, res, body) {
31 | if (err) {
32 | reject(err);
33 | }
34 |
35 | resolve(body.droplets);
36 | });
37 | });
38 |
39 | var regionsPromise = new Promise((resolve, reject) => {
40 | this.api.regionsGetAll({}, function(err, res, body) {
41 | if (err) {
42 | reject(err);
43 | }
44 |
45 | resolve(body.regions);
46 | });
47 | });
48 |
49 | var keysPromise = new Promise((resolve, reject) => {
50 | this.api.accountGetKeys({}, function(err, res, body) {
51 | if (err) {
52 | reject(err);
53 | }
54 |
55 | resolve(body.ssh_keys);
56 | });
57 | });
58 |
59 | var dropletSizesPromise = new Promise((resolve, reject) => {
60 | this.api.sizesGetAll({}, function(err, res, body) {
61 | if (err) {
62 | reject(err);
63 | }
64 |
65 | resolve(body.sizes);
66 | });
67 | });
68 |
69 | var dropletImagesPromise = new Promise((resolve, reject) => {
70 | this.api.imagesGetAll({type: 'application'}, function(err, res, body) {
71 | if (err) {
72 | reject(err);
73 | }
74 |
75 | resolve(body.images);
76 | })
77 | });
78 |
79 | return Promise.all([dropletsPromise, regionsPromise, keysPromise, dropletSizesPromise]).then((values) => {
80 | var droplets = values[0];
81 | var regions = values[1];
82 | var userKeys = values[2];
83 | var dropletSizes = values[3];
84 |
85 | this.droplets = droplets;
86 | this.regions = regions.filter((region) => {
87 | return region.available === true;
88 | });
89 | this.userKeys = userKeys;
90 | this.dropletSizes = dropletSizes;
91 |
92 | this.ui.writeLine(chalk.green('Provisioning Droplet for Fastboot\n'));
93 |
94 | return this.askQuestions()
95 | .then(this.createDropletIfNeeded.bind(this))
96 | .then(this.writeConfiguration.bind(this));
97 | }).catch((err) => {
98 | console.log('There was an error communicating with DigitalOcean\'s API!');
99 | throw new Error(err);
100 | });
101 | },
102 | createDropletIfNeeded: function() {
103 | var answers = this.answers;
104 |
105 | if (answers.dropletName !== undefined) {
106 | // Call DigitalOcean's API and create a new droplet.
107 | var sshKeyIds = answers.sshKeys.map(_key => {
108 | return _key.id;
109 | });
110 |
111 | var payload = {
112 | name: answers.dropletName,
113 | region: answers.newDropletRegion,
114 | size: answers.newDropletSize,
115 | ssh_keys: sshKeyIds,
116 | image: NODE_IMAGE_SLUG,
117 | };
118 |
119 | return this.createDroplet(payload)
120 | .then(droplet => {
121 | answers.droplet = droplet;
122 | });
123 | }
124 |
125 | answers.dropletName = answers.droplet.name;
126 | },
127 | createYaml: function() {
128 | var sshKeys = this.answers.sshKeys.map(_key => {
129 | return _key.public_key;
130 | });
131 |
132 | var deployUser = {
133 | name: "deploy",
134 | groups: "sudo",
135 | shell: "/bin/bash",
136 | sudo: [
137 | "ALL=(ALL) NOPASSWD:ALL"
138 | ],
139 | "ssh-authorized-keys": sshKeys,
140 | };
141 |
142 | // Get the nginx.conf and copy it to the shit.
143 | // We wanna run commands and all that.
144 | var runCommands = [
145 | "sudo apt-get update; sudo apt-get install nginx"
146 | ];
147 |
148 | },
149 | createDroplet: function(payload) {
150 | var ui = this.ui;
151 | var api = this.api;
152 |
153 | var createDropletPromise = new Promise((resolve, reject) => {
154 | ui.startProgress('Creating droplet ' + payload.name);
155 |
156 |
157 |
158 | this.api.dropletsCreate(payload, (err, res, body) => {
159 | if (err) {
160 | ui.stopProgress();
161 | throw new Error(err);
162 | }
163 |
164 | var dropletId = body.droplet.id;
165 | this.api.dropletsGetById(body.droplet.id, (err, res, dropletBody) => {
166 | resolve(dropletBody.droplet);
167 | ui.stopProgress();
168 | });
169 | });
170 | });
171 |
172 | return createDropletPromise;
173 | },
174 | askQuestions: function() {
175 | var ui = this.ui;
176 | var self = this;
177 |
178 | var questions = buildQuestions(this.droplets, this.regions, this.userKeys, this.dropletSizes);
179 | return ui.prompt(questions).then((answers) => {
180 | this.answers = answers;
181 | });
182 | },
183 | writeConfiguration: function() {
184 | var environmentName = this.answers.environmentName;
185 | var dropletName = this.answers.dropletName;
186 | var ipAddress = this.answers.droplet.networks.v4[0].ip_address;
187 | var configPath = '.env.deploy.' + environmentName;
188 | var outputs = this.outputs;
189 |
190 | var config = "FASTBOOT_DO_APPLICATION=" + dropletName + "\n" +
191 | "FASTBOOT_DO_IP=" + ipAddress + "\n" +
192 | "FASTBOOT_DO_ENVIRONMENT=" + environmentName + "\n" +
193 | "FASTBOOT_DO_ID=" + this.answers.droplet.id;
194 |
195 | fs.writeFileSync(configPath, config);
196 |
197 | this.ui.writeLine('Wrote configuration to ' + configPath + ':');
198 | this.ui.write('\n' + config + '\n');
199 | this.ui.writeLine(chalk.green('Run ' +
200 | chalk.blue('ember deploy ' + environmentName) +
201 | ' to deploy your app, then visit ' + chalk.blue(ipAddress)));
202 | }
203 | };
204 |
205 | // For Distribution, default to installing on Ubuntu 14.04 x64.
206 |
207 | function buildQuestions(droplets, regions, userKeys, dropletSizes) {
208 | return [{
209 | name: 'droplet',
210 | message: 'Which DigitalOcean droplet would you like to use?',
211 | type: 'rawlist',
212 | choices: function() {
213 | var choices = droplets.map((droplet) => {
214 | return {
215 | name: droplet.name,
216 | value: droplet,
217 | };
218 | });
219 |
220 | choices.push(CREATE_NEW_DROPLET);
221 | return choices;
222 | }
223 | }, {
224 | name: 'environmentName',
225 | message: 'Which environment?',
226 | type: 'rawlist',
227 | choices: ['development', 'staging', 'production']
228 | }, {
229 | name: 'newDropletRegion',
230 | message: 'Which region do you want to create this droplet in?',
231 | type: 'rawlist',
232 | choices: function() {
233 | var choices = regions.map((region) => {
234 | return {
235 | name: region.name,
236 | value: region.slug,
237 | sizes: region.sizes,
238 | };
239 | });
240 | return choices;
241 | },
242 | when: function(answers) {
243 | return answers.droplet === CREATE_NEW_DROPLET;
244 | }
245 | }, {
246 | name: 'sshKeys',
247 | message: 'Which SSH keys do you want to use for this droplet?',
248 | type: 'checkbox',
249 | validate: function(input) {
250 | if (input.length === 0) {
251 | return 'Please add at least one SSH key for the provisioning to work correctly.';
252 | }
253 |
254 | return true;
255 | },
256 | choices: function() {
257 | var choices = userKeys.map((sshKey) => {
258 | return {
259 | name: sshKey.name,
260 | value: sshKey,
261 | };
262 | });
263 |
264 | return choices;
265 | },
266 | when: function(answers) {
267 | return answers.droplet === CREATE_NEW_DROPLET;
268 | }
269 | }, {
270 | name: 'newDropletSize',
271 | message: 'What size should this droplet be? (At least 1GB recommended)',
272 | type: 'rawlist',
273 | choices: function() {
274 | var choices = dropletSizes.map((size) => {
275 | return {
276 | name: size.slug + "/" +
277 | size.vcpus + " CPU(s) - " +
278 | size.disk + "GB SSD Disk ($" + size.price_monthly + "/mo)",
279 | value: size.slug,
280 | };
281 | });
282 |
283 | return choices;
284 | },
285 | when: function(answers) {
286 | return answers.droplet === CREATE_NEW_DROPLET;
287 | }
288 | }, {
289 | name: 'dropletName',
290 | message: 'What should we name this droplet?',
291 | type: 'input',
292 | when: function(answers) {
293 | return answers.droplet === CREATE_NEW_DROPLET;
294 | }
295 | }
296 | ];
297 | }
298 |
--------------------------------------------------------------------------------