Skip to content

Commit ff26a0a

Browse files
authored
Configurable name transformation for environment variables (#111)
1 parent 8e3f9d4 commit ff26a0a

File tree

16 files changed

+1140
-1189
lines changed

16 files changed

+1140
-1189
lines changed

.github/actions/build/action.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Builds
2+
description: Builds the repository and assumes the AWS IAM role for testing
3+
runs:
4+
using: composite
5+
steps:
6+
- name: Install dependencies
7+
run: npm ci
8+
shell: bash
9+
- name: Build the dist folder
10+
run: npm run build
11+
shell: bash
12+
- name: Determine role to assume
13+
id: role-to-assume
14+
run: |
15+
if [ "${{ github.repository_owner }}" == "aws-actions" ]; then
16+
# Use prod role for the PRs running against the main repo
17+
echo "arn=arn:aws:iam::339713045997:role/GithubActionsRole" >> "$GITHUB_OUTPUT"
18+
else
19+
# Use beta role for the PRs running against engineer forks
20+
echo "arn=arn:aws:iam::654654453185:role/GithubActionsRole" >> "$GITHUB_OUTPUT"
21+
fi
22+
shell: bash
23+
- name: Configure AWS Credentials
24+
uses: aws-actions/configure-aws-credentials@v4
25+
with:
26+
role-to-assume: ${{ steps.role-to-assume.outputs.arn }}
27+
aws-region: us-east-1

.github/workflows/tests.yml

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,134 @@
11
name: Tests
2+
23
on:
34
pull_request:
45
branches:
56
- main
67
push:
78
branches:
89
- main
10+
911
permissions:
1012
id-token: write
1113
contents: read
14+
1215
jobs:
13-
tests:
16+
unit-tests:
1417
runs-on: ubuntu-latest
15-
name: Run Tests
1618
steps:
1719
- name: Checkout
1820
uses: actions/checkout@v4
1921
- name: Run Unit Tests
2022
run: |
2123
npm ci
2224
npm run test
25+
- name: Codecov
26+
uses: codecov/[email protected]
27+
env:
28+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
29+
30+
uppercase-transformation-integration-test:
31+
runs-on: ubuntu-latest
32+
needs: unit-tests
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v4
2336
- name: Build
24-
run: npm run build
25-
- name: Configure AWS Credentials
26-
uses: aws-actions/configure-aws-credentials@v4
37+
uses: ./.github/actions/build
38+
- name: Act
39+
uses: ./
2740
with:
28-
role-to-assume: arn:aws:iam::339713045997:role/GithubActionsRole
29-
aws-region: us-east-1
30-
- name: Integration Tests Act
41+
name-transformation: uppercase
42+
parse-json-secrets: true
43+
secret-ids: |
44+
SampleSecret1
45+
/special/chars/secret
46+
0/special/chars/secret
47+
PrefixSecret*
48+
JsonSecret
49+
SAMPLESECRET1_ALIAS, SampleSecret1
50+
- name: Assert
51+
run: npm run integration-test __integration_tests__/name_transformation/uppercase.test.ts
52+
53+
lowercase-transformation-integration-test:
54+
runs-on: ubuntu-latest
55+
needs: unit-tests
56+
steps:
57+
- name: Checkout
58+
uses: actions/checkout@v4
59+
- name: Build
60+
uses: ./.github/actions/build
61+
- name: Act
3162
uses: ./
3263
with:
64+
name-transformation: lowercase
65+
parse-json-secrets: true
3366
secret-ids: |
34-
SampleSecret1
35-
SAMPLESECRET1_ALIAS, SampleSecret1
36-
/special/chars/secret
37-
0/special/chars/secret
38-
PrefixSecret*
39-
JsonSecret
67+
SampleSecret1
68+
/special/chars/secret
69+
0/special/chars/secret
70+
PrefixSecret*
71+
JsonSecret
72+
samplesecret1_alias, SampleSecret1
73+
- name: Assert
74+
run: npm run integration-test __integration_tests__/name_transformation/lowercase.test.ts
75+
76+
none-transformation-integration-test:
77+
runs-on: ubuntu-latest
78+
needs: unit-tests
79+
steps:
80+
- name: Checkout
81+
uses: actions/checkout@v4
82+
- name: Build
83+
uses: ./.github/actions/build
84+
- name: Act
85+
uses: ./
86+
with:
87+
name-transformation: none
4088
parse-json-secrets: true
41-
- name: Integration Tests Assert
42-
run: npm run integration-test
43-
- name: Codecov
44-
uses: codecov/codecov-action@v4
89+
secret-ids: |
90+
SampleSecret1
91+
/special/chars/secret
92+
0/special/chars/secret
93+
PrefixSecret*
94+
JsonSecret
95+
SampleSecret1_Alias, SampleSecret1
96+
- name: Assert
97+
run: npm run integration-test __integration_tests__/name_transformation/none.test.ts
98+
99+
default-name-transformation-param-integration-test:
100+
runs-on: ubuntu-latest
101+
needs: unit-tests
102+
steps:
103+
- name: Checkout
104+
uses: actions/checkout@v4
105+
- name: Build
106+
uses: ./.github/actions/build
107+
- name: Act
108+
uses: ./
109+
with:
110+
parse-json-secrets: true
111+
secret-ids: |
112+
SampleSecret1
113+
/special/chars/secret
114+
0/special/chars/secret
115+
PrefixSecret*
116+
JsonSecret
117+
SAMPLESECRET1_ALIAS, SampleSecret1
118+
- name: Assert
119+
run: npm run integration-test __integration_tests__/name_transformation/uppercase.test.ts
120+
121+
default-parse-json-secrets-integration-test:
122+
runs-on: ubuntu-latest
123+
needs: unit-tests
124+
steps:
125+
- name: Checkout
126+
uses: actions/checkout@v4
127+
- name: Build
128+
uses: ./.github/actions/build
129+
- name: Act
130+
uses: ./
131+
with:
132+
secret-ids: JsonSecret
133+
- name: Assert Default Is No Json Secrets
134+
run: npm run integration-test __integration_tests__/parse_json_secrets.test.ts

__integration_tests__/env_variables.test.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export function nameTransformationTest(transform: (secretName: string) => string) {
2+
const dataset = [
3+
// Standard name qualified test
4+
['SampleSecret1', 'SomeSampleSecret1'],
5+
// Special characters escaping test
6+
['_special_chars_secret', 'SomeSampleSecret2'],
7+
// Secret starting with numerical character escape test
8+
['_0_special_chars_secret', 'SomeSampleSecret3'],
9+
// Prefix matching test
10+
['PrefixSecret1', 'PrefixSecret1Value'],
11+
['PrefixSecret2', 'PrefixSecret2Value'],
12+
// Json value expansion
13+
['JsonSecret_api_user', 'user'],
14+
['JsonSecret_api_key', 'key'],
15+
['JsonSecret_config_active', 'true'],
16+
// Alias test
17+
['SampleSecret1_Alias', 'SomeSampleSecret1']
18+
].map(([secretName, expectedValue]) => [transform(secretName), expectedValue]);
19+
20+
test.each(dataset)('Secret with name %s test', (secretName, expectedValue) => {
21+
const secretValue = process.env[secretName];
22+
expect(secretValue).toBe(expectedValue);
23+
});
24+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { nameTransformationTest } from "../name_transformation.base";
2+
3+
describe('Lowercased Transformation Variables Assert', () => {
4+
nameTransformationTest(secretName => secretName.toLowerCase());
5+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { nameTransformationTest } from "../name_transformation.base";
2+
3+
describe('No Transformation Variables Assert', () => {
4+
nameTransformationTest(secretName => secretName);
5+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { nameTransformationTest } from "../name_transformation.base";
2+
3+
describe('Uppercased Transformation Variables Assert', () => {
4+
nameTransformationTest(secretName => secretName.toUpperCase());
5+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
describe('parse-json-secrets: false Variables Assert', () => {
2+
it('Has secret name, does not have json keys ', () => {
3+
expect(process.env.JSONSECRET).not.toBeUndefined();
4+
expect(process.env.JSONSECRET_API_USER).toBeUndefined();
5+
expect(process.env.JSONSECRET_API_KEY).toBeUndefined();
6+
expect(process.env.JSONSECRET_CONFIG_ACTIVE).toBeUndefined();
7+
});
8+
});

__tests__/index.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jest.mock('@actions/core', () => {
4949
return {
5050
getMultilineInput: jest.fn(),
5151
getBooleanInput: jest.fn(),
52+
getInput: jest.fn(),
5253
setFailed: jest.fn(),
5354
info: jest.fn(),
5455
debug: jest.fn(),
@@ -75,6 +76,7 @@ describe('Test main action', () => {
7576
const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue(
7677
[TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT, BLANK_ALIAS_INPUT]
7778
);
79+
const nameTransformationSpy = jest.spyOn(core, 'getInput').mockReturnValue('uppercase');
7880

7981
// Mock all Secrets Manager calls
8082
smMockClient
@@ -106,8 +108,8 @@ describe('Test main action', () => {
106108
.resolves({ Name: BLANK_NAME, SecretString: SECRET_FOR_BLANK });
107109

108110
await run();
109-
expect(core.exportVariable).toHaveBeenCalledTimes(10);
110111
expect(core.setFailed).not.toHaveBeenCalled();
112+
expect(core.exportVariable).toHaveBeenCalledTimes(10);
111113

112114
// JSON secrets should be parsed
113115
expect(core.exportVariable).toHaveBeenCalledWith('TEST_ONE_USER', 'admin');
@@ -137,6 +139,7 @@ describe('Test main action', () => {
137139

138140
booleanSpy.mockClear();
139141
multilineInputSpy.mockClear();
142+
nameTransformationSpy.mockClear();
140143
});
141144

142145
test('Defaults to correct behavior with empty string alias', async () => {
@@ -152,8 +155,8 @@ describe('Test main action', () => {
152155
.resolves({ Name: BLANK_NAME_3, SecretString: SECRET_FOR_BLANK_3 });
153156

154157
await run();
155-
expect(core.exportVariable).toHaveBeenCalledTimes(3);
156158
expect(core.setFailed).not.toHaveBeenCalled();
159+
expect(core.exportVariable).toHaveBeenCalledTimes(3);
157160

158161
// Case when alias is blank, but still comma delimited in workflow and no json is parsed
159162
// ex: ,test/blank2
@@ -192,6 +195,7 @@ describe('Test main action', () => {
192195
const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue(
193196
[TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT]
194197
);
198+
const nameTransformationSpy = jest.spyOn(core, 'getInput').mockReturnValue('uppercase');
195199

196200
smMockClient
197201
.on(GetSecretValueCommand, { SecretId: TEST_NAME_1})
@@ -226,5 +230,6 @@ describe('Test main action', () => {
226230

227231
booleanSpy.mockClear();
228232
multilineInputSpy.mockClear();
233+
nameTransformationSpy.mockClear();
229234
});
230235
});

__tests__/utils.test.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
injectSecret,
1515
isSecretArn,
1616
extractAliasAndSecretIdFromInput,
17-
transformToValidEnvName
17+
transformToValidEnvName,
18+
parseTransformationFunction,
19+
TransformationFunc
1820
} from "../src/utils";
1921

2022
import { CLEANUP_NAME, LIST_SECRETS_MAX_RESULTS } from "../src/constants";
@@ -367,8 +369,8 @@ describe('Test secret parsing and handling', () => {
367369
expect(transformToValidEnvName('0Admin')).toBe('_0ADMIN')
368370
});
369371

370-
test('Transforms to uppercase for environment name', () => {
371-
expect(transformToValidEnvName('secret3')).toBe('SECRET3')
372+
test('Transformation function is applied', () => {
373+
expect(transformToValidEnvName('secret3', (x) => x.toUpperCase())).toBe('SECRET3')
372374
});
373375

374376
/*
@@ -401,4 +403,19 @@ describe('Test secret parsing and handling', () => {
401403
test('Test valid nested JSON { "a": "yes", "options": { "opt_a": "yes", "opt_b": "no"} } ', () => {
402404
expect(isJSONString('{ "a": "yes", "options": { "opt_a": "yes", "opt_b": "no"} }')).toBe(true)
403405
});
406+
407+
test.each([
408+
[ 'Uppercase', (x: string) => x.toUpperCase() ],
409+
[ 'uppercase', (x: string) => x.toUpperCase() ],
410+
[ 'lowErCase', (x: string) => x.toLowerCase() ],
411+
[ 'none', (x: string) => x ]
412+
])('NameTransformation parsing of string %s should pass.', (name: string, transformation: TransformationFunc) => {
413+
const sampleString = '$abcdEFGijk_';
414+
const parsedTransformation = parseTransformationFunction(name);
415+
expect(parsedTransformation(sampleString)).toEqual(transformation(sampleString));
416+
});
417+
418+
test.each([ 'something', '' ])('NameTransformation parsing of string %s should fail.', (input) => {
419+
expect(() => parseTransformationFunction(input)).toThrow();
420+
});
404421
});

0 commit comments

Comments
 (0)