-
-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): Add functionality to operate on Secrets (#504)
Co-authored-by: rajdip-b <[email protected]>
- Loading branch information
1 parent
a968e78
commit 1b4bf2f
Showing
10 changed files
with
556 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import BaseCommand from '@/commands/base.command' | ||
import CreateSecret from '@/commands/secret/create.secret' | ||
import DeleteSecret from '@/commands/secret/delete.secret' | ||
import ListSecret from '@/commands/secret/list.secret' | ||
import FetchSecretRevisions from '@/commands/secret/revisions.secret' | ||
import UpdateSecret from '@/commands/secret/update.secret' | ||
import RollbackSecret from '@/commands/secret/rollback.secret' | ||
import GetSecret from '@/commands/secret/get.secret' | ||
|
||
export default class SecretCommand extends BaseCommand { | ||
getName(): string { | ||
return 'secret' | ||
} | ||
|
||
getDescription(): string { | ||
return 'Manages the secrets on keyshade' | ||
} | ||
|
||
getSubCommands(): BaseCommand[] { | ||
return [ | ||
new CreateSecret(), | ||
new DeleteSecret(), | ||
new GetSecret(), | ||
new ListSecret(), | ||
new FetchSecretRevisions(), | ||
new UpdateSecret(), | ||
new RollbackSecret() | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import BaseCommand from '@/commands/base.command' | ||
import { text } from '@clack/prompts' | ||
import ControllerInstance from '@/util/controller-instance' | ||
import { Logger } from '@/util/logger' | ||
import { | ||
type CommandActionData, | ||
type CommandArgument, | ||
type CommandOption | ||
} from '@/types/command/command.types' | ||
|
||
export default class CreateSecret extends BaseCommand { | ||
getName(): string { | ||
return 'create' | ||
} | ||
|
||
getDescription(): string { | ||
return 'Creates a secret' | ||
} | ||
|
||
getArguments(): CommandArgument[] { | ||
return [ | ||
{ | ||
name: '<Project Slug>', | ||
description: 'Slug of the project under which you want to create' | ||
} | ||
] | ||
} | ||
|
||
getOptions(): CommandOption[] { | ||
return [ | ||
{ | ||
short: '-n', | ||
long: '--name <string>', | ||
description: 'Name of the secret. Must be unique across the project' | ||
}, | ||
{ | ||
short: '-d', | ||
long: '--note <string>', | ||
description: 'A note describing the usage of the secret.' | ||
}, | ||
{ | ||
short: '-r', | ||
long: '--rotate-after <string>', | ||
description: | ||
' The duration in days after which the value of the secret should be rotated. Accepted values are `24`, `168`, `720`, `8769` and `never`. Defaults to `never`.' | ||
}, | ||
{ | ||
short: '-e', | ||
long: '--entries [entries...]', | ||
description: | ||
'An array of key-value pair (value and environmentSlug) for the secret.' | ||
} | ||
] | ||
} | ||
|
||
async action({ args, options }: CommandActionData): Promise<void> { | ||
const { name, note, rotateAfter, entries } = await this.parseInput(options) | ||
const [projectSlug] = args | ||
|
||
if (!projectSlug) { | ||
Logger.error('Project slug is required') | ||
return | ||
} | ||
|
||
const { data, error, success } = | ||
await ControllerInstance.getInstance().secretController.createSecret( | ||
{ | ||
name, | ||
note, | ||
rotateAfter, | ||
entries, | ||
projectSlug | ||
}, | ||
this.headers | ||
) | ||
|
||
if (success) { | ||
Logger.info(`Secret ${data.name} (${data.slug}) created successfully!`) | ||
Logger.info(`Created at ${data.createdAt}`) | ||
Logger.info(`Updated at ${data.updatedAt}`) | ||
} else { | ||
Logger.error(`Failed to create secret: ${error.message}`) | ||
} | ||
} | ||
|
||
private async parseInput(options: CommandActionData['options']): Promise<{ | ||
name: string | ||
note?: string | ||
rotateAfter?: '24' | '168' | '720' | '8760' | 'never' | ||
entries: Array<{ value: string; environmentSlug: string }> | ||
}> { | ||
let { name, note, rotateAfter } = options | ||
const { entries } = options | ||
|
||
if (!name) { | ||
name = await text({ | ||
message: 'Enter the name of secret', | ||
placeholder: 'My Secret' | ||
}) | ||
} | ||
|
||
if (!entries) { | ||
throw new Error('Entries is required') | ||
} | ||
|
||
if (!note) { | ||
note = name | ||
} | ||
|
||
const parsedEntries = entries.map((entry) => { | ||
const entryObj: { value: string; environmentSlug: string } = { | ||
value: '', | ||
environmentSlug: '' | ||
} | ||
entry.split(' ').forEach((pair) => { | ||
const [key, value] = pair.split('=') | ||
entryObj[key] = value | ||
}) | ||
return entryObj | ||
}) | ||
|
||
return { | ||
name, | ||
note, | ||
rotateAfter, | ||
entries: parsedEntries | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import type { | ||
CommandActionData, | ||
CommandArgument | ||
} from '@/types/command/command.types' | ||
import BaseCommand from '@/commands/base.command' | ||
import { Logger } from '@/util/logger' | ||
import ControllerInstance from '@/util/controller-instance' | ||
|
||
export default class DeleteSecret extends BaseCommand { | ||
getName(): string { | ||
return 'delete' | ||
} | ||
|
||
getDescription(): string { | ||
return 'Deletes a secret' | ||
} | ||
|
||
getArguments(): CommandArgument[] { | ||
return [ | ||
{ | ||
name: '<Secret Slug>', | ||
description: 'Slug of the secret that you want to delete.' | ||
} | ||
] | ||
} | ||
|
||
async action({ args }: CommandActionData): Promise<void> { | ||
const [secretSlug] = args | ||
|
||
const { error, success } = | ||
await ControllerInstance.getInstance().secretController.deleteSecret( | ||
{ | ||
secretSlug | ||
}, | ||
this.headers | ||
) | ||
|
||
if (success) { | ||
Logger.info(`Secret ${secretSlug} deleted successfully!`) | ||
} else { | ||
Logger.error(`Failed to delete secret: ${error.message}`) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import type { | ||
CommandActionData, | ||
CommandArgument, | ||
CommandOption | ||
} from '@/types/command/command.types' | ||
import BaseCommand from '@/commands/base.command' | ||
import ControllerInstance from '@/util/controller-instance' | ||
import { Logger } from '@/util/logger' | ||
|
||
export default class GetSecret extends BaseCommand { | ||
getName(): string { | ||
return 'get' | ||
} | ||
|
||
getDescription(): string { | ||
return 'Get all secrets under a project' | ||
} | ||
|
||
getArguments(): CommandArgument[] { | ||
return [ | ||
{ | ||
name: '<Project Slug>', | ||
description: 'Slug of the project whose secrets you want.' | ||
} | ||
] | ||
} | ||
|
||
getOptions(): CommandOption[] { | ||
return [ | ||
{ | ||
short: '-d', | ||
long: '--decrypt-value', | ||
description: | ||
'Set this to true if the project contains the private key. If set to true, the values of the secret will be in plaintext format' | ||
} | ||
] | ||
} | ||
|
||
async action({ args, options }: CommandActionData): Promise<void> { | ||
const [projectSlug] = args | ||
const { decryptValue } = await this.parseInput(options) | ||
const { data, error, success } = | ||
await ControllerInstance.getInstance().secretController.getAllSecretsOfProject( | ||
{ | ||
projectSlug, | ||
decryptValue | ||
}, | ||
this.headers | ||
) | ||
|
||
if (success) { | ||
const secrets = data.items | ||
|
||
if (secrets.length > 0) { | ||
data.items.forEach((item: any) => { | ||
const secret = item.secret | ||
Logger.info(`- ${secret.name} (${secret.slug})`) | ||
}) | ||
} else { | ||
Logger.info('No secrets found') | ||
} | ||
} else { | ||
Logger.error(`Failed fetching secrets: ${error.message}`) | ||
} | ||
} | ||
|
||
private async parseInput(options: CommandActionData['options']): Promise<{ | ||
decryptValue: boolean | ||
}> { | ||
const { decryptValue = false } = options // defaults to false | ||
return { | ||
decryptValue | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import type { | ||
CommandActionData, | ||
CommandArgument | ||
} from '@/types/command/command.types' | ||
import BaseCommand from '@/commands/base.command' | ||
import ControllerInstance from '@/util/controller-instance' | ||
import { Logger } from '@/util/logger' | ||
|
||
export default class ListSecret extends BaseCommand { | ||
getName(): string { | ||
return 'list' | ||
} | ||
|
||
getDescription(): string { | ||
return 'List all secrets under a project and environment' | ||
} | ||
|
||
getArguments(): CommandArgument[] { | ||
return [ | ||
{ | ||
name: '<Project Slug>', | ||
description: 'Slug of the project whose secrets you want.' | ||
}, | ||
{ | ||
name: '<Environment Slug>', | ||
description: 'Slug of the environment whose secrets you want.' | ||
} | ||
] | ||
} | ||
|
||
async action({ args }: CommandActionData): Promise<void> { | ||
const [projectSlug, environmentSlug] = args | ||
const { data, error, success } = | ||
await ControllerInstance.getInstance().secretController.getAllSecretsOfEnvironment( | ||
{ | ||
projectSlug, | ||
environmentSlug | ||
}, | ||
this.headers | ||
) | ||
|
||
if (success) { | ||
console.log(data) | ||
const secrets = data | ||
if (secrets.length > 0) { | ||
data.forEach((secret: any) => { | ||
Logger.info(`- ${secret.name} (${secret.value})`) | ||
}) | ||
} else { | ||
Logger.info('No secrets found') | ||
} | ||
} else { | ||
Logger.error(`Failed fetching secrets: ${error.message}`) | ||
} | ||
} | ||
} |
Oops, something went wrong.