Skip to content

Commit

Permalink
Re-introduce the tag command to update the tags of a stack without …
Browse files Browse the repository at this point in the history
…the need for synthesizing and re-deploying
  • Loading branch information
clebert committed Dec 14, 2022
1 parent 27447bf commit 81102df
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Commands:
aws-simple synthesize [options] Synthesize the configured stack using the CDK. [aliases: synth]
aws-simple upload [options] Upload all referenced files to the S3 bucket of the configured stack.
aws-simple list [options] List all deployed stacks filtered by the specified hosted zone name.
aws-simple tag [options] Update the tags of the specified stack.
aws-simple delete [options] Delete the specified stack.
aws-simple purge [options] Delete all expired stacks filtered by the specified hosted zone name.
aws-simple flush-cache [options] Flush the REST API cache of the specified stack.
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
import {redeployCommand} from './redeploy-command.js';
import {startCommand} from './start-command.js';
import {synthesizeCommand} from './synthesize-command.js';
import {tagCommand} from './tag-command.js';
import {uploadCommand} from './upload-command.js';
import {print} from './utils/print.js';

Expand All @@ -38,6 +39,7 @@ export type ConfigFileDefaultExport = (port?: number) => StackConfig;
.command(synthesizeCommand)
.command(uploadCommand)
.command(listCommand)
.command(tagCommand)
.command(deleteCommand)
.command(purgeCommand)
.command(flushCacheCommand)
Expand Down
34 changes: 34 additions & 0 deletions src/sdk/update-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
CloudFormationClient,
UpdateStackCommand,
} from '@aws-sdk/client-cloudformation';
import {findStack} from './find-stack.js';

export interface UpdateTagsOptions {
readonly stackName: string;
readonly tagsToAdd: [string, string | undefined][];
readonly tagKeysToRemove: string[];
}

export async function updateTags(options: UpdateTagsOptions): Promise<void> {
const {stackName, tagsToAdd, tagKeysToRemove} = options;
const stack = await findStack(stackName);
const client = new CloudFormationClient({});

const tagObjects = [
...(stack.Tags ?? []).filter(({Key}) =>
tagsToAdd.every(([key]) => key !== Key),
),
...tagsToAdd.map(([key, value]) => ({Key: key, Value: value || `true`})),
].filter(({Key}) => Key && !tagKeysToRemove.includes(Key));

await client.send(
new UpdateStackCommand({
StackName: stackName,
UsePreviousTemplate: true,
Capabilities: stack.Capabilities,
Parameters: stack.Parameters,
Tags: tagObjects,
}),
);
}
89 changes: 89 additions & 0 deletions src/tag-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type {CommandModule} from 'yargs';
import {readStackConfig} from './read-stack-config.js';
import {updateTags} from './sdk/update-tags.js';
import {getDomainName} from './utils/get-domain-name.js';
import {getStackName} from './utils/get-stack-name.js';
import {print} from './utils/print.js';

const commandName = `tag`;

export const tagCommand: CommandModule<
{},
{
readonly 'stack-name': string | undefined;
readonly 'add': readonly (string | number)[];
readonly 'remove': readonly (string | number)[];
readonly 'yes': boolean;
}
> = {
command: `${commandName} [options]`,
describe: `Update the tags of the specified stack.`,

builder: (argv) =>
argv
.options(`stack-name`, {
describe: `An optional stack name, if not specified it will be determined from the config file`,
string: true,
})
.options(`add`, {
describe: `Tags to add to the specified stack`,
array: true,
default: [],
})
.options(`remove`, {
describe: `Tags to remove from the specified stack`,
array: true,
default: [],
})
.options(`yes`, {
describe: `Confirm to add or remove the specified tags automatically`,
boolean: true,
default: false,
})
.example([
[
`npx $0 ${commandName} --stack-name ${getStackName(
`example.com`,
)} --add latest release --remove prerelease`,
],
[`npx $0 ${commandName} --add foo=something bar="something else"`],
[`npx $0 ${commandName} --add prerelease --yes`],
]),

handler: async (args): Promise<void> => {
const stackName =
args.stackName || getStackName(getDomainName(await readStackConfig()));

print.warning(`Stack: ${stackName}`);

if (args.yes) {
print.warning(`The tags of the specified stack will be updated.`);
} else {
const confirmed = await print.confirmation(
`Confirm to update the tags of the specified stack.`,
);

if (!confirmed) {
return;
}
}

print.info(`Updating tags...`);

await updateTags({
stackName,
tagsToAdd: args.add.map((arg) => parseTag(arg.toString())),
tagKeysToRemove: args.remove.map((arg) => arg.toString()),
});

print.success(
`The tags of the specified stack have been successfully updated.`,
);
},
};

function parseTag(arg: string): [string, string | undefined] {
const [key, value] = arg.split(`=`) as [string, ...string[]];

return [key, value];
}

0 comments on commit 81102df

Please sign in to comment.