Skip to content

Commit 02b91c7

Browse files
Add more information about the account to auth contexts (#168)
1 parent 073ceec commit 02b91c7

18 files changed

+6604
-720
lines changed

codefresh.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ steps:
1515
commands:
1616
- yarn eslint
1717

18-
# unit-tests:
19-
# title: 'Running unit tests'
20-
# image: codefresh/node-tester-image:8.8.0
21-
# commands:
22-
# - yarn test
18+
unit-tests:
19+
title: 'Running unit tests'
20+
image: codefresh/node-tester-image:8.8.0
21+
commands:
22+
- yarn test
2323

2424
build_step:
2525
type: build

lib/interface/cli/commands/auth/current-context.cmd.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const CFError = require('cf-errors');
44
const { auth } = require('../../../../logic');
55
const authManager = auth.manager;
66
const authRoot = require('../root/auth.cmd');
7-
7+
const { printTableForAuthContexts } = require('../../helpers/auth');
8+
const _ = require('lodash');
89

910
const command = new Command({
1011
command: 'current-context',
@@ -21,11 +22,12 @@ const command = new Command({
2122
},
2223
handler: async (argv) => {
2324
const currentContext = authManager.getCurrentContext();
24-
if (currentContext) {
25-
console.log(currentContext.getName());
26-
} else {
25+
if (_.isUndefined(currentContext)) {
2726
throw new CFError('There are no contexts in cfconfig file');
2827
}
28+
if (currentContext) {
29+
await printTableForAuthContexts({ filter: 'current' });
30+
}
2931
},
3032
});
3133

lib/interface/cli/commands/auth/get-contexts.cmd.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const command = new Command({
1818
.example('codefresh auth get-contexts', 'List all existing authentication contexts');
1919
},
2020
handler: async (argv) => {
21-
printTableForAuthContexts();
21+
await printTableForAuthContexts({ filter: 'all' });
2222
},
2323
});
2424

lib/interface/cli/helpers/auth.js

+47-21
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,57 @@ const _ = require('lodash');
22
const columnify = require('columnify');
33
const { auth } = require('../../../logic');
44
const authManager = auth.manager;
5+
const kefir = require('kefir');
6+
const assert = require('assert');
7+
const debug = require('debug')('codefresh:cli:helpers:auth');
8+
const colors = require('colors');
59

6-
const printTableForAuthContexts = () => {
7-
const currentContext = authManager.getCurrentContext();
8-
if (!currentContext) {
9-
console.log('No authentication contexts.');
10-
return;
10+
const printTableForAuthContexts = ({ filter = 'all' }) => {
11+
let contexts;
12+
if (filter === 'all') {
13+
contexts = _.keys(authManager.getAllContexts());
1114
}
12-
const contexts = authManager.getAllContexts();
13-
const keys = currentContext.defaultColumns;
15+
if (filter === 'current') {
16+
contexts = [_.get(authManager.getCurrentContext(), 'name', {})];
17+
}
18+
19+
1420
const res = [];
15-
let obj = [];
16-
_.forEach(contexts, (context) => {
17-
obj = [];
18-
_.forEach(keys, (key) => {
19-
if (key === 'current') {
20-
(context === currentContext) ? obj[key.toUpperCase()] = '*' : obj[key.toUpperCase()] = '';
21-
}
22-
else {
23-
obj[key.toUpperCase()] = context[key];
24-
}
21+
let mergedKeys = [];
22+
let contextsInfo = {};
23+
return kefir.sequentially(0, contexts)
24+
.flatMap((contextName) => {
25+
let context = authManager.getContextByName(contextName);
26+
debug(`context to check ${context.name}`);
27+
return kefir.fromPromise(context.addAccountInfo()
28+
.then(() => {
29+
mergedKeys = _.union(mergedKeys, context.defaultColumns);
30+
let ret = _.chain(context)
31+
.pick(context.defaultColumns)
32+
.mapValues((value, key) => {
33+
if (key === 'current') return '*'.green;
34+
return _.isString(value) ? value : JSON.stringify(value);
35+
})
36+
.value();
37+
38+
return ret;
39+
}));
40+
})
41+
.scan((prev, context) => {
42+
prev.push(context);
43+
return prev;
44+
}, [])
45+
.flatMap((info) => {
46+
info = _.sortBy(info, ['name']);
47+
output = columnify(info, { columns: mergedKeys });
48+
return kefir.constant(output);
49+
})
50+
.ignoreErrors()
51+
.toPromise()
52+
.then((output) => {
53+
console.log(output);
2554
});
26-
res.push(obj);
27-
});
28-
const columns = columnify(res);
29-
console.log(columns);
55+
3056
};
3157

3258
module.exports = { printTableForAuthContexts };

lib/logic/api/helper.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { printError } = require('../../interface/cli/helpers/general');
88

99
const { version } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json')));
1010

11-
const sendHttpRequest = async (httpOptions, authContext) => {
11+
const sendHttpRequest = async (httpOptions, authContext, throwOnUnauthorized) => {
1212
let finalAuthContext;
1313
if (!authContext) {
1414
const authManager = require('../auth').manager; // eslint-disable-line
@@ -34,16 +34,22 @@ const sendHttpRequest = async (httpOptions, authContext) => {
3434
response = await rp(finalOptions);
3535
} catch (err) {
3636
if (_.isEqual(err.statusCode, 401)) {
37-
printError(new CFError({
37+
const error = new CFError({
3838
cause: err,
39-
message: 'Error: Please create or update your authentication context'
40-
}));
41-
process.exit(1);
39+
message: 'Error: Please create or update your authentication context',
40+
});
41+
42+
if (!throwOnUnauthorized) {
43+
printError();
44+
process.exit(1);
45+
} else {
46+
throw error;
47+
}
4248
}
4349
if (_.isEqual(err.statusCode, 403)) {
4450
printError(new CFError({
4551
cause: err,
46-
message: 'Error: You do not have permissions to perform this action'
52+
message: 'Error: You do not have permissions to perform this action',
4753
}));
4854
process.exit(1);
4955
}

lib/logic/auth/contexts/APIKeyContext.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class APIKeyContext extends Context {
1111
this.type = TYPE;
1212
this.token = options.token;
1313
this.name = options.name || this.name;
14-
this.defaultColumns = ['current', 'name', 'url'];
14+
this.defaultColumns = ['current', 'name', 'url', 'account', 'status'];
1515
this.beta = options.beta || this.beta;
1616
}
1717

lib/logic/auth/contexts/Context.js

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const CFError = require('cf-errors');
22
const uuidv4 = require('uuid/v4');
3+
const { whoami } = require('./whoami');
34

45
class Context {
56
constructor(options) {
@@ -12,6 +13,17 @@ class Context {
1213
return this.beta;
1314
}
1415

16+
async addAccountInfo() {
17+
try {
18+
const { name, runtimeEnvironment } = await whoami(this);
19+
this.account = name;
20+
this.runtimeEnvironment = runtimeEnvironment;
21+
this.status = 'Valid';
22+
} catch (err) {
23+
this.status = 'Revoked';
24+
}
25+
}
26+
1527
setName(name) {
1628
this.name = name;
1729
}

lib/logic/auth/contexts/JWTContext.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class JWTContext extends Context {
1818
this.accountName = options.accountName;
1919
this.expires = new Date(options.expires * 1000);
2020
this.name = options.name || this.name;
21-
this.defaultColumns = ['current', 'name', 'url'];
21+
this.defaultColumns = ['current', 'name', 'url', 'account', 'status'];
2222
this.beta = options.beta || this.beta;
2323
}
2424

lib/logic/auth/contexts/whoami.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { sendHttpRequest } = require('../../api/helper');
2+
const _ = require('lodash');
3+
const debug = require('debug')('codefresh:logic:auth:whoami');
4+
5+
const whoami = async (context) => {
6+
debug(`context is ${context} for ${context.name}`);
7+
const userOptions = {
8+
url: '/api/user/',
9+
method: 'GET',
10+
};
11+
const user = await sendHttpRequest(userOptions, context, true);
12+
const accounts = _.get(user, 'account', {});
13+
const accountInfo = _.chain(accounts)
14+
.filter(account => account.name === user.activeAccountName)
15+
.get('[0]', {})
16+
.pick('name', 'runtimeEnvironment')
17+
.value();
18+
19+
debug(`account info ${JSON.stringify(accountInfo)}`);
20+
debug(`current account name is : ${JSON.stringify(_.get(user, 'activeAccountName'))}`);
21+
22+
return accountInfo;
23+
};
24+
25+
module.exports = { whoami };
+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/* eslint-disable */
2+
3+
const context = require('../index'); // eslint-disable-line
4+
const { whoami } = require('./whoami');
5+
const { APIKeyContext } = require('./index');
6+
7+
jest.mock('../../api/helper', () => { // eslint-disable-line
8+
const apiHelper = require.requireActual('../../api/helper');
9+
10+
let response;
11+
let validateParams;
12+
13+
const __setResponse = (x) => {
14+
response = x;
15+
};
16+
17+
const __setValidateParams = (x) => {
18+
validateParams = x;
19+
};
20+
21+
const __reset = () => {
22+
response = null;
23+
validateParams = null;
24+
};
25+
26+
const sendHttpRequest = (userOptions, context, flag) => { // eslint-disable-line
27+
if (validateParams) {
28+
validateParams(userOptions, context, flag);
29+
}
30+
31+
if (response) {
32+
return response();
33+
} else { // eslint-disable-line
34+
return apiHelper.sendHttpRequest(userOptions, context, flag);
35+
}
36+
};
37+
38+
return {
39+
__setResponse,
40+
__setValidateParams,
41+
__reset,
42+
sendHttpRequest,
43+
};
44+
});
45+
46+
const apiHelper = require('../../api/helper');
47+
48+
beforeEach(() => {
49+
apiHelper.__reset();
50+
});
51+
52+
describe('whoami tests', () => {
53+
54+
describe('api key context', () => {
55+
56+
describe('positive', () => {
57+
58+
it('should return account information in case context is valid', () => {
59+
const context = new APIKeyContext({
60+
name: 'test-context',
61+
url: 'http://test',
62+
token: 'test-token',
63+
});
64+
65+
apiHelper.__setValidateParams((userOptions, context, flag) => {
66+
expect(userOptions)
67+
.toEqual({
68+
'method': 'GET',
69+
'url': '/api/user/',
70+
});
71+
expect(flag)
72+
.toEqual(true);
73+
});
74+
75+
apiHelper.__setResponse(() => {
76+
return Promise.resolve({
77+
activeAccountName: 'account-name-1',
78+
account: [
79+
{
80+
name: 'account-name-1',
81+
runtimeEnvironment: 'runtime-environment-1',
82+
},
83+
{
84+
name: 'account-name-2',
85+
runtimeEnvironment: 'runtime-environment-2',
86+
},
87+
],
88+
});
89+
});
90+
91+
return whoami(context)
92+
.then((accountInfo) => {
93+
expect(accountInfo)
94+
.toEqual({
95+
name: 'account-name-1',
96+
runtimeEnvironment: 'runtime-environment-1',
97+
});
98+
});
99+
100+
});
101+
102+
});
103+
104+
describe('negative', () => {
105+
106+
it('should fail in case context is not valid', () => {
107+
const context = new APIKeyContext({
108+
name: 'test-context',
109+
url: 'http://test',
110+
token: 'test-token',
111+
});
112+
113+
apiHelper.__setResponse(() => {
114+
return Promise.reject(new Error('http request error'));
115+
});
116+
117+
return whoami(context)
118+
.then(() => {
119+
throw new Error('should have failed');
120+
}, (err) => {
121+
expect(err.toString())
122+
.toEqual('Error: http request error');
123+
});
124+
125+
});
126+
127+
});
128+
});
129+
130+
});

lib/logic/auth/manager.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Manager {
2323
* @returns {*}
2424
*/
2525
getContextByName(name) {
26-
return this.contexts[name];
26+
return _.get(this.contexts, name, undefined);
2727
}
2828

2929
/**
@@ -37,14 +37,15 @@ class Manager {
3737
this.addContext(context);
3838
this.currentContextName = context.name;
3939
}
40+
context.current = 'true'; // eslint-disable-line
4041
}
4142

4243
/**
4344
* returns the current active context
4445
* @returns {*}
4546
*/
4647
getCurrentContext() {
47-
return this.contexts[this.currentContextName];
48+
return _.get(this.contexts, this.currentContextName, undefined);
4849
}
4950

5051
addContext(context) {

0 commit comments

Comments
 (0)