Skip to content

Commit

Permalink
Merge pull request #3 from robin-thomas/logging
Browse files Browse the repository at this point in the history
feat: add logging support
  • Loading branch information
robin-thomas committed Sep 10, 2023
2 parents 6a2c3aa + 7565d9e commit e9c4e95
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 18 deletions.
37 changes: 37 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const getServerless = (): Serverless => ({
});

describe('index.ts', () => {
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation();
});

describe('default options are set', () => {
test('secretId and secretPrefix are not set', () => {
const serverless = getServerless();
Expand Down Expand Up @@ -49,7 +53,40 @@ describe('index.ts', () => {
});
});

describe('verbose logging', () => {
afterAll(() => {
nock.cleanAll();
});

test('if verbose is not set, its default value is false', () => {
const plugin = new ServerlesssAwsSecrets(getServerless());
expect(plugin.options.verbose).toBe(false);
});

test('if verbose is set, its value is used', async () => {
const serverless = getServerless();
serverless.service.custom = { 'serverless-aws-secrets': { verbose: true } };
serverless.service.provider.environment = { MYSQL_PASSWORD: 'SECRET:MYSQL_PASSWORD' };

nock(/secretsmanager.eu-west-1.amazonaws.com/)
.post('/')
.reply(200, { SecretString: JSON.stringify({ MYSQL_PASSWORD: 'SECRET_MYSQL_PASSWORD' }) });

const plugin = new ServerlesssAwsSecrets(serverless);
expect(plugin.options.verbose).toBe(true);

await plugin.loadSecrets();
expect(console.log).toBeCalledWith(
'[serverless-aws-secrets]: Replacing MYSQL_PASSWORD with secret of MYSQL_PASSWORD',
);
});
});

describe('loading the secrets', () => {
afterEach(() => {
nock.cleanAll();
});

test('failed to connect to AWS', async () => {
nock(/secretsmanager.eu-west-1.amazonaws.com/)
.post('/')
Expand Down
42 changes: 27 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@ import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-sec
import type { Serverless, ServerlessSecretHooks, ServerlessSecretOptions } from './index.types';

class ServerlessAWSSecret {
private readonly hooks: ServerlessSecretHooks;
readonly options: ServerlessSecretOptions;
private readonly providerCopy: Serverless['service']['provider'];
private readonly region: string;
private readonly secretId: string;
private readonly secretPrefix: string;
hooks: ServerlessSecretHooks;
options: ServerlessSecretOptions;
providerCopy: Serverless['service']['provider'];
region: string;

constructor(serverless: Serverless) {
const { custom, provider } = serverless.service;
this.options = custom?.['serverless-aws-secrets'] ?? {};

this.secretId = this.getSecretId(serverless);
this.secretPrefix = this.getSecretPrefix();
this.setOptions(serverless);

const { provider } = serverless.service;
this.region = provider.region;
this.providerCopy = provider;

Expand All @@ -28,27 +23,44 @@ class ServerlessAWSSecret {

async loadSecrets() {
const client = new SecretsManagerClient({ region: this.region });
const command = new GetSecretValueCommand({ SecretId: this.secretId });
const command = new GetSecretValueCommand({ SecretId: this.options.secretId });

const { SecretString } = await client.send(command);

if (!SecretString) {
throw new Error(`Failed to retrieve the secret: ${this.secretId}`);
throw new Error(`Failed to retrieve the secret: ${this.options.secretId}`);
}

const secrets = JSON.parse(SecretString);

let replaceCount = 0;
for (const [key, value] of Object.entries(this.providerCopy.environment)) {
if (value?.startsWith(this.secretPrefix)) {
const secretKey = value.replace(this.secretPrefix, '');
if (value?.startsWith(this.options.secretPrefix!)) {
const secretKey = value.replace(this.options.secretPrefix!, '');

if (!secrets[secretKey]) {
throw new Error(`Secret ${secretKey} do not exist`);
}

if (this.options.verbose) {
console.log(`[serverless-aws-secrets]: Replacing ${key} with secret of ${secretKey}`);
}

this.providerCopy.environment[key] = secrets[secretKey];

++replaceCount;
}
}

console.log(`[serverless-aws-secrets]: Replaced ${replaceCount} secrets in environment variables`);
}

setOptions(serverless: Serverless) {
this.options = serverless.service.custom?.['serverless-aws-secrets'] ?? {};

this.options.secretId = this.getSecretId(serverless);
this.options.secretPrefix = this.getSecretPrefix();
this.options.verbose = this.options.verbose ?? false;
}

getSecretId(serverless: Serverless) {
Expand Down
16 changes: 14 additions & 2 deletions src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ export interface ServerlessSecretHooks {
}

export interface ServerlessSecretOptions {
/** @dev Environment variables with values that start with this prefix are treated as secrets */
/**
* @dev Environment variables with values that start with this prefix are treated as secrets
* @default "SECRET:"
*/
secretPrefix?: string;

/** @dev Secret to search for within AWS Secrets Manager */
/**
* @dev Secret to search for within AWS Secrets Manager
* @default `${stage}/${app}-${service}`
*/
secretId?: string;

/**
* @dev Enable verbose logging.
* @default false
*/
verbose?: boolean;
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": false
"declaration": false,
"strictPropertyInitialization": false
},
"include": [
"src"
Expand Down

0 comments on commit e9c4e95

Please sign in to comment.