Skip to content

Commit 7d4b883

Browse files
authored
Merge pull request #900 from particle-iot/chore/sc-138087/secrets
Chore/sc 138087/secrets
2 parents 573ba8d + 95c2200 commit 7d4b883

File tree

9 files changed

+595
-151
lines changed

9 files changed

+595
-151
lines changed

src/cli/config.js

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,22 @@ module.exports = ({ commandProcessor, root }) => {
105105
}
106106
});
107107

108-
const secret = commandProcessor.createCategory(config, 'secrets', 'Manage secrets');
108+
const secret = commandProcessor.createCategory(config, 'secrets', 'Manage secrets', {
109+
inherited: {
110+
options: {
111+
'sandbox': {
112+
description: 'Target the sandbox',
113+
boolean: true
114+
},
115+
'org': {
116+
description: 'Specify the organization'
117+
}
118+
}
119+
}
120+
});
109121

110122
commandProcessor.createCommand(secret, 'list', 'List all created secrets', {
111123
options: {
112-
'org': {
113-
description: 'Specify the organization'
114-
},
115124
'json': {
116125
description: 'Show the list in json format',
117126
boolean: true
@@ -122,74 +131,46 @@ module.exports = ({ commandProcessor, root }) => {
122131
return new SecretsCommand(args).list(args);
123132
},
124133
examples: {
125-
'$0 $command': 'List all secrets',
126-
'$0 $command --org <org>': 'List all secrets from a specific org'
134+
'$0 $command --sandbox': 'List all secrets from sandbox',
135+
'$0 $command --org <org>': 'List all secrets from a specific organization'
127136
}
128137
});
129138

130139
commandProcessor.createCommand(secret, 'get', 'Get a specific secret',{
131-
options: {
132-
'org': {
133-
description: 'Specify the organization'
134-
},
135-
'name': {
136-
description: 'Secret name'
137-
}
138-
},
140+
params: '<key>',
139141
handler: (args) => {
140142
const SecretsCommand = require('../cmd/secrets');
141143
return new SecretsCommand(args).get(args);
142-
}
143-
});
144-
145-
commandProcessor.createCommand(secret, 'create', 'Creates a new secret', {
146-
options: {
147-
'org': {
148-
description: 'Specify the organization'
149-
},
150-
'name': {
151-
description: 'Secret name'
152-
},
153-
'value': {
154-
description: 'Secret value'
155-
}
156144
},
157-
handler: (args) => {
158-
const SecretsCommand = require('../cmd/secrets');
159-
return new SecretsCommand(args).create(args);
145+
examples: {
146+
'$0 $command <key> --sandbox': 'Get a secret from sandbox',
147+
'$0 $command <key> --org <org>': 'Get a secret from a specific organization'
160148
}
161149
});
162150

163-
commandProcessor.createCommand(secret, 'update', 'Updates the value of an existing secret', {
164-
options: {
165-
'org': {
166-
description: 'Specify the organization'
167-
},
168-
'name': {
169-
description: 'Secret name'
170-
},
171-
'value': {
172-
description: 'Secret value'
173-
}
174-
},
151+
commandProcessor.createCommand(secret, 'set', 'Set a secret', {
152+
params: '<key> [value]',
175153
handler: (args) => {
176154
const SecretsCommand = require('../cmd/secrets');
177-
return new SecretsCommand(args).update(args);
155+
return new SecretsCommand(args).set(args);
156+
},
157+
examples: {
158+
'$0 $command <key> <value> --sandbox': 'Set secret to user\'s sandbox (space format)',
159+
'$0 $command <key=value> --sandbox': 'Set secret to user\'s sandbox (equal sign format)',
160+
'$0 $command <key> <value> --org <org>': 'Set secret for an organization',
161+
'$0 $command <key=value> --org <org>': 'Set secret for an organization (equal sign format)'
178162
}
179163
});
180164

181-
commandProcessor.createCommand(secret, 'remove', 'Remove a specific secret',{
182-
options: {
183-
'org': {
184-
description: 'Specify the organization'
185-
},
186-
'name': {
187-
description: 'Secret name'
188-
}
189-
},
165+
commandProcessor.createCommand(secret, 'delete', 'Delete a specific secret',{
166+
params: '<key>',
190167
handler: (args) => {
191168
const SecretsCommand = require('../cmd/secrets');
192-
return new SecretsCommand(args).remove(args);
169+
return new SecretsCommand(args).deleteSecret(args);
170+
},
171+
examples: {
172+
'$0 $command <key> --sandbox': 'Delete a secret from sandbox',
173+
'$0 $command <key> --org <org>': 'Delete a secret from a specific organization'
193174
}
194175
});
195176
};

src/cmd/api.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -388,41 +388,36 @@ module.exports = class ParticleApi {
388388
}));
389389
}
390390

391-
listSecrets({ orgSlug }) {
391+
listSecrets({ sandbox, orgSlug }) {
392+
const uri = sandbox ? '/v1/secrets' : `/v1/orgs/${orgSlug}/secrets`;
392393
return this._wrap(this.api.request({
393-
uri: `/v1${orgSlug ? `/orgs/${orgSlug}` : ''}/secrets`,
394+
uri,
394395
auth: this.accessToken
395396
}));
396397
}
397398

398-
getSecret({ orgSlug, name }) {
399+
getSecret({ sandbox, orgSlug, name }) {
400+
const uri = sandbox ? `/v1/secrets/${name}` : `/v1/orgs/${orgSlug}/secrets/${name}`;
399401
return this._wrap(this.api.request({
400-
uri: `/v1${orgSlug ? `/orgs/${orgSlug}` : ''}/secrets/${name}`,
402+
uri,
401403
auth: this.accessToken
402404
}));
403405
}
404406

405-
createSecret({ orgSlug, name, value }) {
406-
return this._wrap(this.api.request({
407-
uri: `/v1${orgSlug ? `/orgs/${orgSlug}` : ''}/secrets`,
408-
method: 'post',
409-
auth: this.accessToken,
410-
data: { secret: { name, value } }
411-
}));
412-
}
413-
414-
updateSecret({ orgSlug, name, value }) {
407+
updateSecret({ sandbox, orgSlug, name, value }) {
408+
const uri = sandbox ? `/v1/secrets/${name}` : `/v1/orgs/${orgSlug}/secrets/${name}`;
415409
return this._wrap(this.api.request({
416-
uri: `/v1${orgSlug ? `/orgs/${orgSlug}` : ''}/secrets/${name}`,
410+
uri,
417411
method: 'put',
418412
auth: this.accessToken,
419413
data: { secret: { value } }
420414
}));
421415
}
422416

423-
removeSecret({ orgSlug, name }) {
417+
removeSecret({ sandbox, orgSlug, name }) {
418+
const uri = sandbox ? `/v1/secrets/${name}` : `/v1/orgs/${orgSlug}/secrets/${name}`;
424419
return this._wrap(this.api.request({
425-
uri: `/v1${orgSlug ? `/orgs/${orgSlug}` : ''}/secrets/${name}`,
420+
uri,
426421
method: 'delete',
427422
auth: this.accessToken
428423
}));

src/cmd/env.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ describe('config env Command', () => {
234234
});
235235

236236
describe('delete env vars', () => {
237-
it('delete env var for sandbox user', async () => {
237+
it('deletes env var for sandbox user', async () => {
238238
let receivedBody;
239239
const params = { key: 'FOO' };
240240
nock('https://api.particle.io/v1')
@@ -257,7 +257,7 @@ describe('config env Command', () => {
257257
expect(envCommands.ui.write).to.have.been.calledWith(`Key ${params.key} has been successfully deleted.`);
258258
});
259259

260-
it('delete env var for specific org', async () => {
260+
it('deletes env var for specific org', async () => {
261261
let receivedBody;
262262
const params = { key: 'FOO' };
263263
nock('https://api.particle.io/v1/orgs/my-org')
@@ -280,7 +280,7 @@ describe('config env Command', () => {
280280
expect(envCommands.ui.write).to.have.been.calledWith(`Key ${params.key} has been successfully deleted.`);
281281
});
282282

283-
it('delete env var for specific product', async () => {
283+
it('deletes env var for specific product', async () => {
284284
let receivedBody;
285285
const params = { key: 'FOO' };
286286
nock('https://api.particle.io/v1/products/my-product')
@@ -303,7 +303,7 @@ describe('config env Command', () => {
303303
expect(envCommands.ui.write).to.have.been.calledWith(`Key ${params.key} has been successfully deleted.`);
304304
});
305305

306-
it('delete env var for specific device', async () => {
306+
it('deletes env var for specific device', async () => {
307307
let receivedBody;
308308
const params = { key: 'FOO', value: 'bar' };
309309
const deviceId = 'abc123';

src/cmd/secrets.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,27 @@ module.exports = class SecretsCommand extends CLICommandBase {
1717
this.consoleBaseUrl = settings.isStaging ? 'https://console.staging.particle.io' : 'https://console.particle.io';
1818
}
1919

20-
async list({ org, json }) {
20+
_validateScope({ sandbox, org }) {
21+
const scopes = [
22+
{ name: 'sandbox', value: sandbox },
23+
{ name: 'org', value: org }
24+
].filter(scope => scope.value);
25+
26+
if (scopes.length === 0) {
27+
throw new Error('You must specify one of: --sandbox or --org');
28+
}
29+
30+
if (scopes.length > 1) {
31+
const scopeNames = scopes.map(s => `--${s.name}`).join(', ');
32+
throw new Error(`You can only specify one scope at a time. You provided: ${scopeNames}`);
33+
}
34+
}
35+
36+
async list({ org, sandbox, json }) {
37+
this._validateScope({ sandbox, org });
2138
const secretsData = await this.ui.showBusySpinnerUntilResolved(
2239
'Retrieving secrets',
23-
secrets.list({ org, api: this.api })
40+
secrets.list({ org, sandbox, api: this.api })
2441
);
2542
if (!json) {
2643
secretsData.forEach((secret) => this._printSecret({ ...secret, org }));
@@ -29,39 +46,56 @@ module.exports = class SecretsCommand extends CLICommandBase {
2946
}
3047
}
3148

32-
async get({ name, org }){
49+
async get({ params, org, sandbox }){
50+
this._validateScope({ sandbox, org });
51+
const name = params.key;
3352
const secretData = await this.ui.showBusySpinnerUntilResolved(
3453
'Retrieving secret',
35-
secrets.get({ api: this.api, name, org })
54+
secrets.get({ api: this.api, name, org, sandbox })
3655
);
3756
this._printSecret({ ...secretData, org });
3857
}
3958

40-
async update({ name, value, org }) {
41-
const secretData = await this.ui.showBusySpinnerUntilResolved(
42-
'Updating secret',
43-
secrets.update({ api: this.api, name, value, org })
44-
);
45-
this.ui.write(`Secret ${name} updated successfully.`);
46-
this._printSecret(secretData);
47-
}
48-
49-
async remove({ org, name }) {
59+
async deleteSecret({ params, org, sandbox }) {
60+
this._validateScope({ sandbox, org });
61+
const name = params.key;
5062
const isDeleted = await this.ui.showBusySpinnerUntilResolved(
51-
'Remove secret',
52-
secrets.remove({ api: this.api, org, name })
63+
'Deleting secret',
64+
secrets.remove({ api: this.api, org, sandbox, name })
5365
);
5466
if (isDeleted) {
55-
this.ui.write(`Secret ${name} removed successfully.`);
67+
this.ui.write(`Secret ${name} deleted successfully.`);
5668
}
5769
}
5870

59-
async create({ name, value, org }) {
60-
const secretData = await secrets.create({ api: this.api, name, org , value });
61-
this.ui.write(`Secret ${name} created successfully.`);
71+
async set({ params, org, sandbox }) {
72+
this._validateScope({ sandbox, org });
73+
const { key, value } = this._parseKeyValue(params);
74+
const secretData = await this.ui.showBusySpinnerUntilResolved(
75+
'Setting secret',
76+
secrets.update({ api: this.api, name: key, org, sandbox, value })
77+
);
78+
this.ui.write(`Secret ${key} set successfully.`);
6279
this._printSecret(secretData);
6380
}
6481

82+
_parseKeyValue(params) {
83+
if (params.key && params.value) {
84+
return { key: params.key, value: params.value };
85+
}
86+
if (params.key && params.key.includes('=')) {
87+
const [key, ...valueParts] = params.key.split('=');
88+
const value = valueParts.join('=');
89+
90+
if (!key || !value) {
91+
throw new Error('Invalid format. Use either "key value" or "key=value"');
92+
}
93+
94+
return { key, value };
95+
}
96+
throw new Error('Invalid format. Use either "key value" or "key=value"');
97+
}
98+
6599
_printSecret(secret) {
66100
const secretName = this.ui.chalk.cyan.bold(secret.name);
67101
this.ui.write(`${secretName}`);

0 commit comments

Comments
 (0)