-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(push): push only updated parameters
Check before pushing parameters which parameters need to be changed. This makes browsing in AWS Console more clear when checking parameter history. Before this change there could be 2 history records with the same value.
- Loading branch information
poikaa
committed
Oct 11, 2021
1 parent
45f088e
commit 00015d7
Showing
7 changed files
with
163 additions
and
111 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,41 @@ | ||
import { SSMClient } from '@aws-sdk/client-ssm' | ||
|
||
export function createClient({ region, accessKeyId, secretAccessKey }) { | ||
const credentials = | ||
accessKeyId && secretAccessKey | ||
? { | ||
accessKeyId, | ||
secretAccessKey, | ||
} | ||
: undefined | ||
|
||
return new SSMClient({ region, credentials }) | ||
} | ||
|
||
async function delay(time) { | ||
return new Promise((resolve) => setTimeout(resolve, time)) | ||
} | ||
|
||
export function addThrottleMiddleware(client, { batchSize, wait }) { | ||
let currentBatchSize = 0 | ||
|
||
// https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/ | ||
client.middlewareStack.add( | ||
(next) => async (args) => { | ||
if (currentBatchSize > batchSize) { | ||
currentBatchSize = 0 | ||
|
||
await delay(wait) | ||
} | ||
|
||
currentBatchSize++ | ||
|
||
return await next(args) | ||
}, | ||
{ | ||
step: 'initialize', | ||
} | ||
) | ||
|
||
return client | ||
} |
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,29 @@ | ||
import _ from 'lodash' | ||
import { paginateGetParametersByPath } from '@aws-sdk/client-ssm' | ||
import { createClient } from './client.mjs' | ||
|
||
export async function pullParameters({ client, prefix, ...config }) { | ||
const paginator = paginateGetParametersByPath( | ||
{ | ||
client: client ?? createClient(config), | ||
}, | ||
{ | ||
Path: prefix, | ||
Recursive: true, | ||
WithDecryption: true, | ||
} | ||
) | ||
|
||
const parameterList = [] | ||
for await (const page of paginator) { | ||
parameterList.push(...page.Parameters) | ||
} | ||
|
||
return _.sortBy(parameterList, 'Name').reduce( | ||
(parameters, { Name, Value }) => ({ | ||
...parameters, | ||
[Name.substr(prefix.length)]: Value, | ||
}), | ||
{} | ||
) | ||
} |
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,87 @@ | ||
import _ from 'lodash' | ||
import { PutParameterCommand, ParameterType } from '@aws-sdk/client-ssm' | ||
import { parseDotenv } from './dotenv.mjs' | ||
import { createClient, addThrottleMiddleware } from './client.mjs' | ||
import { pullParameters } from './pull.mjs' | ||
|
||
async function analyze({ client, prefix, file }) { | ||
const parsedParameters = parseDotenv(file) | ||
|
||
const remoteParameters = await pullParameters({ client, prefix }) | ||
|
||
let skipped = 0 | ||
let updated = 0 | ||
let created = 0 | ||
|
||
const parametersToUpdate = _.reduce( | ||
parsedParameters, | ||
(toUpdate, value, name) => { | ||
if (_.has(remoteParameters, name)) { | ||
if (remoteParameters[name] === value) { | ||
skipped++ | ||
} else { | ||
updated++ | ||
toUpdate[name] = value | ||
} | ||
} else { | ||
created++ | ||
toUpdate[name] = value | ||
} | ||
|
||
return toUpdate | ||
}, | ||
{} | ||
) | ||
|
||
return { | ||
total: _.size(parsedParameters), | ||
skipped, | ||
updated, | ||
created, | ||
parameters: parametersToUpdate, | ||
} | ||
} | ||
|
||
function printStat({ total, skipped, updated, created }) { | ||
const padSize = _.max([skipped, updated, created]).toString().length | ||
|
||
console.log( | ||
[ | ||
`Total ${total} of parameters:`, | ||
` ${_.padStart(skipped, padSize)} up-to-date`, | ||
`~ ${_.padStart(updated, padSize)} updated`, | ||
`+ ${_.padStart(created, padSize)} created`, | ||
].join('\n') | ||
) | ||
} | ||
|
||
/** | ||
* Parameters are sent one by one with 1 second delay after each 10 requests to avoid `ThrottlingException: Rate exceeded`. | ||
* See docs: | ||
* https://aws.amazon.com/premiumsupport/knowledge-center/ssm-parameter-store-rate-exceeded/ | ||
* https://docs.aws.amazon.com/general/latest/gr/ssm.html#limits_ssm | ||
*/ | ||
export async function pushParameters({ prefix, file, ...config }) { | ||
const client = createClient(config) | ||
|
||
const { parameters, ...stat } = await analyze({ client, prefix, file }) | ||
|
||
addThrottleMiddleware(client, { batchSize: 10, wait: 1000 }) | ||
|
||
const putCommands = _.map( | ||
parameters, | ||
(value, name) => | ||
new PutParameterCommand({ | ||
Type: ParameterType.STRING, | ||
Overwrite: true, | ||
Name: `${prefix}${name}`, | ||
Value: value, | ||
}) | ||
) | ||
|
||
for (const putCommand of putCommands) { | ||
await client.send(putCommand) | ||
} | ||
|
||
printStat(stat) | ||
} |
This file was deleted.
Oops, something went wrong.