Skip to content

Commit 2fed893

Browse files
committed
Merge branch 'develop', prepare 0.15.0
2 parents 4a9bcd4 + ecdc0b6 commit 2fed893

File tree

9 files changed

+229
-8
lines changed

9 files changed

+229
-8
lines changed

docs/Basics.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
| list | List currently deployed projects |
1515
| rm [id] | Remove existing deployment or project |
1616
| log [id] | Get logs for existing deployment or project |
17+
| token | Generate new deployment token |
1718
| login | Login into Exoframe server |
1819
| endpoint [url] | Gets or sets the endpoint of Exoframe server |
1920
| completion | Generates bash completion script |
@@ -61,3 +62,12 @@ Currently it contains endpoint URL and list of template plugins:
6162
```yaml
6263
endpoint: 'http://localhost:8080' # your endpoint URL, defaults to localhost
6364
```
65+
66+
## Deployment tokens
67+
68+
Sometimes you might need to deploy things from environments that don't have your private key (e.g. CI/CD services).
69+
For this cases you can use deployment tokens. Here's how it works:
70+
71+
1. Make sure you are logged in to your Exoframe server
72+
2. Generate new deployment token using `exoframe token` command
73+
3. Use the new token to deploy your service without need to authenticate: `exoframe deploy -t $TOKEN`

src/commands/deploy.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,20 @@ const streamToResponse = ({tarStream, remoteUrl, options}) =>
3737

3838
exports.command = ['*', 'deploy'];
3939
exports.describe = 'deploy current folder';
40-
exports.builder = {};
41-
exports.handler = async args => {
42-
if (!isLoggedIn()) {
40+
exports.builder = {
41+
token: {
42+
alias: 't',
43+
},
44+
};
45+
exports.handler = async (args = {}) => {
46+
const deployToken = args.token;
47+
48+
// exit if not logged in and no token provided
49+
if (!deployToken && !isLoggedIn()) {
4350
return;
4451
}
4552

46-
const folder = args && args._ ? args._.filter(arg => arg !== 'deploy').shift() : undefined;
53+
const folder = args._ ? args._.filter(arg => arg !== 'deploy').shift() : undefined;
4754

4855
console.log(chalk.bold(`Deploying ${folder || 'current project'} to endpoint:`), userConfig.endpoint);
4956

@@ -52,6 +59,13 @@ exports.handler = async args => {
5259
const folderName = path.basename(workdir);
5360
const remoteUrl = `${userConfig.endpoint}/deploy`;
5461

62+
// make sure workdir exists
63+
if (!fs.existsSync(workdir)) {
64+
console.log(chalk.red(`Error! Path ${chalk.bold(workdir)} do not exists`));
65+
console.log('Please, check your arguments and try again.');
66+
return;
67+
}
68+
5569
// create config if doesn't exist
5670
const configPath = path.join(workdir, 'exoframe.json');
5771
try {
@@ -79,9 +93,14 @@ exports.handler = async args => {
7993
ignore: name => ig.ignores(name),
8094
});
8195

96+
let token = userConfig.token;
97+
if (deployToken) {
98+
token = deployToken;
99+
console.log('Deploying using given token..');
100+
}
82101
const options = {
83102
headers: {
84-
Authorization: `Bearer ${userConfig.token}`,
103+
Authorization: `Bearer ${token}`,
85104
},
86105
};
87106

src/commands/login.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ exports.command = 'login';
1717
exports.describe = 'login into exoframe server';
1818
exports.builder = {
1919
key: {
20-
short: 'k',
20+
alias: 'k',
2121
},
2222
passphrase: {
23-
short: 'p',
23+
alias: 'p',
2424
},
2525
};
2626
exports.handler = async ({key, passphrase}) => {

src/commands/token.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// npm packages
2+
const got = require('got');
3+
const chalk = require('chalk');
4+
5+
// our packages
6+
const {userConfig, isLoggedIn, logout} = require('../config');
7+
8+
exports.command = ['token'];
9+
exports.describe = 'generate new deployment token';
10+
exports.builder = {};
11+
exports.handler = async () => {
12+
if (!isLoggedIn()) {
13+
return;
14+
}
15+
16+
console.log(chalk.bold('Generating new deployment token for:'), userConfig.endpoint);
17+
18+
// services request url
19+
const remoteUrl = `${userConfig.endpoint}/deployToken`;
20+
// construct shared request params
21+
const options = {
22+
headers: {
23+
Authorization: `Bearer ${userConfig.token}`,
24+
},
25+
json: true,
26+
};
27+
// try sending request
28+
try {
29+
const {body} = await got(remoteUrl, options);
30+
const {token} = body;
31+
console.log(chalk.bold('New token generated:'));
32+
console.log('');
33+
console.log(token);
34+
console.log('');
35+
console.log(chalk.yellow('WARNING!'), 'Make sure to write it down, you will not be able to get it again!');
36+
} catch (e) {
37+
// if authorization is expired/broken/etc
38+
if (e.statusCode === 401) {
39+
logout(userConfig);
40+
console.log(chalk.red('Error: authorization expired!'), 'Please, relogin and try again.');
41+
return;
42+
}
43+
44+
console.log(chalk.red('Error generating deployment token:'), e.toString());
45+
}
46+
};

src/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// npm packages
22
const yargs = require('yargs');
33

4+
// version
5+
const pkg = require('../package.json');
6+
47
// our packages
58
const login = require('./commands/login');
69
const deploy = require('./commands/deploy');
@@ -9,10 +12,11 @@ const logs = require('./commands/logs');
912
const remove = require('./commands/remove');
1013
const endpoint = require('./commands/endpoint');
1114
const config = require('./commands/config');
15+
const token = require('./commands/token');
1216

1317
// init program
1418
yargs
15-
.version('0.7.0')
19+
.version(pkg.version)
1620
.completion('completion')
1721
.demand(1)
1822
.help()
@@ -22,4 +26,5 @@ yargs
2226
.command(list)
2327
.command(logs)
2428
.command(remove)
29+
.command(token)
2530
.command(config).argv;

test/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ module.exports = () => {
5353
inquirer.prompt.restore();
5454
// restore console
5555
console.log.restore();
56+
// remove corrupted config
57+
fs.unlinkSync(configPath);
5658
t.end();
5759
});
5860
});

test/deploy.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,47 @@ module.exports = () => {
104104
});
105105
});
106106

107+
// test
108+
tap.test('Should deploy without auth but with token', t => {
109+
// spy on console
110+
const consoleSpy = sinon.spy(console, 'log');
111+
// copy original config for restoration
112+
const originalConfig = Object.assign({}, userConfig);
113+
114+
// handle correct request
115+
const deployServer = nock('http://localhost:8080').post('/deploy').reply((uri, requestBody, cb) => {
116+
cb(null, [200, {status: 'success', deployments}]);
117+
});
118+
119+
// remove auth from config
120+
updateConfig({endpoint: 'http://localhost:8080'});
121+
122+
// execute login
123+
deploy({token: 'test-token'}).then(() => {
124+
// make sure log in was successful
125+
// check that server was called
126+
t.ok(deployServer.isDone());
127+
// first check console output
128+
t.deepEqual(
129+
consoleSpy.args,
130+
[
131+
['Deploying current project to endpoint:', 'http://localhost:8080'],
132+
['Deploying using given token..'],
133+
['Your project is now deployed as:\n'],
134+
[' ID URL Hostname \n test http://localhost test '],
135+
],
136+
'Correct log output'
137+
);
138+
// restore console
139+
console.log.restore();
140+
// tear down nock
141+
deployServer.done();
142+
// restore original config
143+
updateConfig(originalConfig);
144+
t.end();
145+
});
146+
});
147+
107148
// test
108149
tap.test('Should not deploy with broken config', t => {
109150
// spy on console
@@ -129,6 +170,29 @@ module.exports = () => {
129170
});
130171
});
131172

173+
// test
174+
tap.test('Should not deploy with non-existent path', t => {
175+
// spy on console
176+
const consoleSpy = sinon.spy(console, 'log');
177+
178+
// execute deploy
179+
deploy({_: ['i-do-not-exist']}).then(() => {
180+
// check console output
181+
t.deepEqual(
182+
consoleSpy.args,
183+
[
184+
['Deploying i-do-not-exist to endpoint:', 'http://localhost:8080'],
185+
[`Error! Path ${path.join(process.cwd(), 'i-do-not-exist')} do not exists`],
186+
['Please, check your arguments and try again.'],
187+
],
188+
'Correct log output'
189+
);
190+
// restore console
191+
console.log.restore();
192+
t.end();
193+
});
194+
});
195+
132196
// test
133197
tap.test('Should deauth on 401', t => {
134198
// copy original config for restoration

test/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const deploy = require('./deploy');
55
const logs = require('./logs');
66
const list = require('./list');
77
const config = require('./config');
8+
const token = require('./token');
89

910
// run tests
1011
login();
@@ -13,3 +14,4 @@ deploy();
1314
logs();
1415
list();
1516
config();
17+
token();

test/token.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// npm packages
2+
const tap = require('tap');
3+
const nock = require('nock');
4+
const sinon = require('sinon');
5+
6+
// our packages
7+
const {handler: token} = require('../src/commands/token');
8+
const {userConfig, updateConfig} = require('../src/config');
9+
10+
module.exports = () => {
11+
// test removal
12+
tap.test('Should generate token', t => {
13+
// handle correct request
14+
const tokenServer = nock('http://localhost:8080').get('/deployToken').reply(200, {token: 'test'});
15+
// spy on console
16+
const consoleSpy = sinon.spy(console, 'log');
17+
// execute login
18+
token().then(() => {
19+
// make sure log in was successful
20+
// check that server was called
21+
t.ok(tokenServer.isDone());
22+
// first check console output
23+
t.deepEqual(
24+
consoleSpy.args,
25+
[
26+
['Generating new deployment token for:', 'http://localhost:8080'],
27+
['New token generated:'],
28+
[''],
29+
['test'],
30+
[''],
31+
['WARNING!', 'Make sure to write it down, you will not be able to get it again!'],
32+
],
33+
'Correct log output'
34+
);
35+
// restore console
36+
console.log.restore();
37+
tokenServer.done();
38+
t.end();
39+
});
40+
});
41+
42+
// test
43+
tap.test('Should deauth on 401', t => {
44+
// copy original config for restoration
45+
const originalConfig = Object.assign({}, userConfig);
46+
// handle correct request
47+
const tokenServer = nock('http://localhost:8080').get('/deployToken').reply(401);
48+
// spy on console
49+
const consoleSpy = sinon.spy(console, 'log');
50+
// execute login
51+
token().then(() => {
52+
// make sure log in was successful
53+
// check that server was called
54+
t.ok(tokenServer.isDone());
55+
// first check console output
56+
t.deepEqual(
57+
consoleSpy.args,
58+
[
59+
['Generating new deployment token for:', 'http://localhost:8080'],
60+
['Error: authorization expired!', 'Please, relogin and try again.'],
61+
],
62+
'Correct log output'
63+
);
64+
// restore console
65+
console.log.restore();
66+
// tear down nock
67+
tokenServer.done();
68+
// restore original config
69+
updateConfig(originalConfig);
70+
t.end();
71+
});
72+
});
73+
};

0 commit comments

Comments
 (0)