mirror of
https://github.com/hashicorp/vault-action.git
synced 2026-07-26 00:13:16 +03:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b8899915e | |||
| a5f6c67fe1 | |||
| d9197ec2d2 | |||
| cb841f2c86 | |||
| 0010502df7 | |||
| 65d7a12a80 | |||
| b138504969 | |||
| e926631bb2 | |||
| 5213b69445 | |||
| 357cb9c034 | |||
| b9f4d16071 | |||
| 62aa8bb4c4 | |||
| ec2980c187 | |||
| 166100bd2a | |||
| dc4f72debb | |||
| a87a71c289 |
@@ -0,0 +1,22 @@
|
||||
name: Lint GitHub Actions Workflows
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
- name: "Lint workflow files"
|
||||
uses: docker://docker.mirror.hashicorp.services/rhysd/actionlint:latest
|
||||
with:
|
||||
# Ignore actionlint errors from strict typing for outputs that we use
|
||||
# in our e2e tests.
|
||||
# This error occurs because vault-action's outputs are dynamic but
|
||||
# actionlint expects action.yml to define them.
|
||||
args: >
|
||||
-ignore "property \"othersecret\" is not defined in object type"
|
||||
-ignore "property \"jsonstring\" is not defined in object type"
|
||||
-ignore "property \"jsonstringmultiline\" is not defined in object type"
|
||||
+21
-47
@@ -1,9 +1,5 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -11,8 +7,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
@@ -40,8 +34,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault
|
||||
@@ -76,8 +68,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault-enterprise
|
||||
@@ -114,8 +104,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault
|
||||
@@ -174,11 +162,8 @@ jobs:
|
||||
/cubbyhole/test foo ;
|
||||
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
||||
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
# The ordering of these two Test Vault Action Overwrites Env Vars In Subsequent Action steps matters
|
||||
# They should come before the Verify Vault Action Outputs step
|
||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
|
||||
uses: ./
|
||||
with:
|
||||
@@ -186,6 +171,7 @@ jobs:
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test secret | SUBSEQUENT_TEST_SECRET;
|
||||
|
||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 2/2)
|
||||
uses: ./
|
||||
with:
|
||||
@@ -194,13 +180,27 @@ jobs:
|
||||
secrets: |
|
||||
secret/data/subsequent-test secret | SUBSEQUENT_TEST_SECRET;
|
||||
|
||||
- name: Test JSON Secrets
|
||||
uses: ./
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test-json-data jsonData;
|
||||
secret/data/test-json-string jsonString;
|
||||
secret/data/test-json-string-multiline jsonStringMultiline;
|
||||
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
|
||||
e2e-tls:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault-tls
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
|
||||
- name: Test Vault Action (default KV V2)
|
||||
uses: ./
|
||||
id: kv-secrets
|
||||
id: kv-secrets-tls
|
||||
with:
|
||||
url: https://localhost:8200
|
||||
token: ${{ env.VAULT_TOKEN }}
|
||||
@@ -285,30 +285,4 @@ jobs:
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e-tls
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
# Removing publish step for now.
|
||||
# publish:
|
||||
# if: github.event_name == 'push' && contains(github.ref, 'main')
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: [build, integration, e2e]
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: '16.14.0'
|
||||
# - name: setup npm cache
|
||||
# uses: actions/cache@v1
|
||||
# with:
|
||||
# path: ~/.npm
|
||||
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-node-
|
||||
# - name: npm install
|
||||
# run: npm ci
|
||||
# - name: release
|
||||
# if: success() && endsWith(github.ref, 'main')
|
||||
# run: npx semantic-release
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets-tls.outputs.otherSecret }}
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
|
||||
JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
|
||||
with:
|
||||
teams-array: '["applications-eco"]'
|
||||
teams-array: '["ecosystem", "applications-eco"]'
|
||||
|
||||
@@ -1,24 +1,61 @@
|
||||
# This is a sample workflow to help test contributions
|
||||
# Change the branch name, url and token to fit with your own environment
|
||||
|
||||
# Use 'on: push' instead of 'on: local-test' if you wish to run the test on github
|
||||
# If running locally with act, run the workflow with 'act local-test'
|
||||
# To run this locally with act use:
|
||||
# act workflow_dispatch -j local-test
|
||||
#
|
||||
# If you have permissions, you can run this workflow via the GitHub UI.
|
||||
# Otherwise, use 'on: push' instead of 'on: workflow_dispatch'.
|
||||
|
||||
# Don't forget to revert the file changes and invalidate any tokens that were committed before opening a pull-request
|
||||
on: local-test
|
||||
# Don't forget to revert the file changes and invalidate any tokens that were
|
||||
# committed before opening a pull request.
|
||||
on: workflow_dispatch
|
||||
|
||||
name: local-test
|
||||
|
||||
jobs:
|
||||
build:
|
||||
local-test:
|
||||
name: local-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
|
||||
- name: NPM Install
|
||||
run: npm ci
|
||||
|
||||
- name: NPM Build
|
||||
run: npm run build
|
||||
|
||||
- name: Setup Vault
|
||||
run: node ./integrationTests/e2e/setup.js
|
||||
env:
|
||||
VAULT_HOST: localhost
|
||||
VAULT_PORT: 8200
|
||||
|
||||
- name: Import Secrets
|
||||
uses: hashicorp/vault-action@YOUR_BRANCH_NAME
|
||||
id: import-secrets
|
||||
# use the local changes
|
||||
uses: ./
|
||||
# run against a specific version of vault-action
|
||||
# uses: hashicorp/vault-action@v2.1.2
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
method: token
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test secret | SAMPLE_SECRET;
|
||||
secret/data/test-json-string jsonString;
|
||||
|
||||
- name: Check Secrets
|
||||
run: |
|
||||
touch secrets.json
|
||||
echo "${{ steps.import-secrets.outputs.jsonString }}" >> secrets.json
|
||||
|
||||
- name: Check json file format
|
||||
run: |
|
||||
echo
|
||||
cat secrets.json
|
||||
jq -c . < secrets.json
|
||||
|
||||
@@ -59,3 +59,6 @@ typings/
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# GoLand IDE project files
|
||||
.idea
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
## Unreleased
|
||||
|
||||
* Add changes here
|
||||
|
||||
## 2.7.3 (July 13, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Revert to the handling of secrets in JSON format since v2.1.2 [GH-478](https://github.com/hashicorp/vault-action/pull/478)
|
||||
|
||||
## 2.7.2 (July 6, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Fix a regression that broke support for secrets in JSON format [GH-473](https://github.com/hashicorp/vault-action/pull/473)
|
||||
|
||||
## 2.7.1 (July 3, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Revert [GH-466](https://github.com/hashicorp/vault-action/pull/466) which caused a regression in secrets stored as JSON strings [GH-471](https://github.com/hashicorp/vault-action/pull/471)
|
||||
|
||||
## 2.7.0 (June 21, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Fix a regression that broke support for secrets in JSON format [GH-466](https://github.com/hashicorp/vault-action/pull/466)
|
||||
|
||||
Improvements:
|
||||
|
||||
* Fix a warning about outputToken being an unexpected input [GH-461](https://github.com/hashicorp/vault-action/pull/461)
|
||||
|
||||
## 2.6.0 (June 7, 2023)
|
||||
|
||||
Features:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.PHONY: local-test
|
||||
local-test:
|
||||
docker compose down; docker-compose up -d vault && act workflow_dispatch -j local-test
|
||||
@@ -76,7 +76,7 @@ For example, a common pattern is to save all the secrets in a JSON file:
|
||||
- name: Step following 'Import Secrets'
|
||||
run: |
|
||||
touch secrets.json
|
||||
echo "${{ toJson(steps.import-secrets.outputs) }}" >> secrets.json
|
||||
echo '${{ toJson(steps.import-secrets.outputs) }}' >> secrets.json
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -373,6 +373,13 @@ with:
|
||||
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
|
||||
```
|
||||
You can specify a wildcard * for the key name to get all keys in the path. If you provide an output name with the wildcard, the name will be prepended to the key name:
|
||||
|
||||
```yaml
|
||||
with:
|
||||
secrets: |
|
||||
secret/data/ci/aws * | MYAPP_ ;
|
||||
```
|
||||
|
||||
## Other Secret Engines
|
||||
|
||||
@@ -546,18 +553,23 @@ $ npm run test:integration:basic # Choose one of: basic, enterprise, e2e, e2e-tl
|
||||
|
||||
### Running the action locally
|
||||
|
||||
You can use the [act](https://github.com/nektos/act) command to test your changes locally if desired. Unfortunately it is not currently possible to use uncommitted local changes for a shared workfow. You will still need to push
|
||||
the changes you would like to validate beforehand. Even if a commit is necessary, this is still a more detailed and faster feedback loop than waiting for the action to be executed by Github in a different repository.
|
||||
You can use the [act](https://github.com/nektos/act) command to test your
|
||||
changes locally.
|
||||
|
||||
Edit the ./.github/workflows/local-test.yaml file and add any steps necessary
|
||||
to test your changes. You may have to additionally edit the Vault url, token
|
||||
and secret path if you are not using one of the provided containerized
|
||||
instances. The `local-test` job will call the ./integrationTests/e2e/setup.js
|
||||
script to bootstrap your local Vault instance with secrets.
|
||||
|
||||
Run your feature branch locally:
|
||||
|
||||
Push your changes into a feature branch.
|
||||
```sh
|
||||
$ git checkout -b my-feature-branch
|
||||
$ git commit -m "testing new changes"
|
||||
$ git push
|
||||
act workflow_dispatch -j local-test
|
||||
```
|
||||
|
||||
Edit the ./.github/workflows/local-test.yaml file to use your new feature branch. You may have to additionally edit the vault url, token and secret path if you are not using one of the provided containerized instance.
|
||||
Run your feature branch locally.
|
||||
Or use the provided make target which will also spin up a Vault container:
|
||||
|
||||
```sh
|
||||
$ act local-test
|
||||
make local-test
|
||||
```
|
||||
@@ -56,6 +56,10 @@ inputs:
|
||||
description: 'Whether or not export Vault token as environment variables.'
|
||||
default: 'false'
|
||||
required: false
|
||||
outputToken:
|
||||
description: 'Whether or not to set the `vault_token` output to contain the Vault token after authentication.'
|
||||
default: 'false'
|
||||
required: false
|
||||
caCertificate:
|
||||
description: 'Base64 encoded CA certificate to verify the Vault server certificate.'
|
||||
required: false
|
||||
|
||||
Vendored
+130
-32
@@ -3702,6 +3702,7 @@ function asPromise(normalizedOptions) {
|
||||
request._beforeError(new types_1.HTTPError(response));
|
||||
return;
|
||||
}
|
||||
request.destroy();
|
||||
resolve(request.options.resolveBodyOnly ? response.body : response);
|
||||
});
|
||||
const onError = (error) => {
|
||||
@@ -18516,6 +18517,9 @@ const core = __nccwpck_require__(2186);
|
||||
const command = __nccwpck_require__(7351);
|
||||
const got = (__nccwpck_require__(3061)["default"]);
|
||||
const jsonata = __nccwpck_require__(4245);
|
||||
const { normalizeOutputKey } = __nccwpck_require__(1608);
|
||||
const { WILDCARD } = __nccwpck_require__(4438);
|
||||
|
||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = __nccwpck_require__(4351);
|
||||
|
||||
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
|
||||
@@ -18684,7 +18688,7 @@ function parseSecretsInput(secretsInput) {
|
||||
const selectorAst = jsonata(selectorQuoted).ast();
|
||||
const selector = selectorQuoted.replace(new RegExp('"', 'g'), '');
|
||||
|
||||
if ((selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
if (selector !== WILDCARD && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
|
||||
}
|
||||
|
||||
@@ -18704,20 +18708,6 @@ function parseSecretsInput(secretsInput) {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace('.', '__').replace(new RegExp('-', 'g'), '').replace(/[^\p{L}\p{N}_-]/gu, '');
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} inputKey
|
||||
* @param {any} inputOptions
|
||||
@@ -18746,11 +18736,11 @@ function parseHeadersInput(inputKey, inputOptions) {
|
||||
module.exports = {
|
||||
exportSecrets,
|
||||
parseSecretsInput,
|
||||
normalizeOutputKey,
|
||||
parseHeadersInput
|
||||
parseHeadersInput,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4915:
|
||||
@@ -18917,6 +18907,17 @@ module.exports = {
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4438:
|
||||
/***/ ((module) => {
|
||||
|
||||
const WILDCARD = '*';
|
||||
|
||||
module.exports = {
|
||||
WILDCARD
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4351:
|
||||
@@ -18936,8 +18937,8 @@ module.exports = {
|
||||
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
||||
|
||||
const jsonata = __nccwpck_require__(4245);
|
||||
|
||||
|
||||
const { WILDCARD } = __nccwpck_require__(4438);
|
||||
const { normalizeOutputKey } = __nccwpck_require__(1608);
|
||||
/**
|
||||
* @typedef {Object} SecretRequest
|
||||
* @property {string} path
|
||||
@@ -18960,7 +18961,8 @@ const jsonata = __nccwpck_require__(4245);
|
||||
*/
|
||||
async function getSecrets(secretRequests, client) {
|
||||
const responseCache = new Map();
|
||||
const results = [];
|
||||
let results = [];
|
||||
|
||||
for (const secretRequest of secretRequests) {
|
||||
let { path, selector } = secretRequest;
|
||||
|
||||
@@ -18983,22 +18985,53 @@ async function getSecrets(secretRequests, client) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"'
|
||||
}
|
||||
selector = "data." + selector
|
||||
body = JSON.parse(body)
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
if (selector == WILDCARD) {
|
||||
let keys = body.data;
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector
|
||||
keys = keys.data;
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
results.push({
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse
|
||||
});
|
||||
for (let key in keys) {
|
||||
let newRequest = Object.assign({},secretRequest);
|
||||
newRequest.selector = key;
|
||||
|
||||
if (secretRequest.selector === secretRequest.outputVarName) {
|
||||
newRequest.outputVarName = key;
|
||||
newRequest.envVarName = key;
|
||||
}
|
||||
else {
|
||||
newRequest.outputVarName = secretRequest.outputVarName+key;
|
||||
newRequest.envVarName = secretRequest.envVarName+key;
|
||||
}
|
||||
|
||||
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
|
||||
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
|
||||
|
||||
selector = key;
|
||||
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
newRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -19010,6 +19043,7 @@ async function getSecrets(secretRequests, client) {
|
||||
async function selectData(data, selector) {
|
||||
const ata = jsonata(selector);
|
||||
let result = JSON.stringify(await ata.evaluate(data));
|
||||
|
||||
// Compat for custom engines
|
||||
if (!result && ((ata.ast().type === "path" && ata.ast()['steps'].length === 1) || ata.ast().type === "string") && selector !== 'data' && 'data' in data) {
|
||||
result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
|
||||
@@ -19023,11 +19057,75 @@ async function selectData(data, selector) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses selectData with the selector to get the value and then appends it to the
|
||||
* results. Returns a new array with all of the results.
|
||||
* @param {string} selector
|
||||
* @param {object} body
|
||||
* @param {object} cachedResponse
|
||||
* @param {TRequest} secretRequest
|
||||
* @param {SecretResponse<TRequest>[]} results
|
||||
* @return {Promise<SecretResponse<TRequest>[]>}
|
||||
*/
|
||||
const selectAndAppendResults = async (
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
) => {
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"';
|
||||
}
|
||||
selector = "data." + selector;
|
||||
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector;
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
return [
|
||||
...results,
|
||||
{
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSecrets,
|
||||
selectData
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1608:
|
||||
/***/ ((module) => {
|
||||
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace(".", "__")
|
||||
.replace(new RegExp("-", "g"), "")
|
||||
.replace(/[^\p{L}\p{N}_-]/gu, "");
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
normalizeOutputKey
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9491:
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
version: "3.0"
|
||||
services:
|
||||
vault:
|
||||
image: vault:latest
|
||||
image: hashicorp/vault:latest
|
||||
environment:
|
||||
VAULT_DEV_ROOT_TOKEN_ID: testtoken
|
||||
ports:
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
- 8200:8200
|
||||
privileged: true
|
||||
vault-tls:
|
||||
image: vault:latest
|
||||
image: hashicorp/vault:latest
|
||||
hostname: vault-tls
|
||||
environment:
|
||||
VAULT_CAPATH: /etc/vault/ca.crt
|
||||
|
||||
@@ -171,6 +171,26 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard secrets', async () => {
|
||||
mockInput(`secret/data/test * ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard secrets with name prefix', async () => {
|
||||
mockInput(`secret/data/test * | GROUP_ ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET');
|
||||
});
|
||||
|
||||
it('leading slash kvv2', async () => {
|
||||
mockInput('/secret/data/foobar fookv2');
|
||||
|
||||
@@ -195,6 +215,34 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get K/V v1 wildcard secrets', async () => {
|
||||
mockInput(`secret-kv1/test * ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get K/V v1 wildcard secrets with name prefix', async () => {
|
||||
mockInput(`secret-kv1/test * | GROUP_ ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard nested secret from K/V v1', async () => {
|
||||
mockInput('secret-kv1/nested/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('leading slash kvv1', async () => {
|
||||
mockInput('/secret-kv1/foobar fookv1');
|
||||
|
||||
@@ -225,6 +273,17 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
|
||||
});
|
||||
|
||||
it('wildcard supports cubbyhole', async () => {
|
||||
mockInput('/cubbyhole/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(2);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
|
||||
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
|
||||
});
|
||||
|
||||
it('caches responses', async () => {
|
||||
mockInput(`
|
||||
/cubbyhole/test foo ;
|
||||
|
||||
@@ -10,5 +10,8 @@ describe('e2e', () => {
|
||||
expect(process.env.FOO).toBe("bar");
|
||||
expect(process.env.NAMED_CUBBYSECRET).toBe("zap");
|
||||
expect(process.env.SUBSEQUENT_TEST_SECRET).toBe("SUBSEQUENT_TEST_SECRET");
|
||||
expect(process.env.JSONSTRING).toBe('{"x":1,"y":"qux"}');
|
||||
expect(process.env.JSONSTRINGMULTILINE).toBe('{"x": 1, "y": "q\\nux"}');
|
||||
expect(process.env.JSONDATA).toBe('{"x":1,"y":"qux"}');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@ const got = require('got');
|
||||
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.VAULT_TOKEN}` : "testtoken";
|
||||
|
||||
const jsonStringMultiline = '{"x": 1, "y": "q\\nux"}';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// Verify Connection
|
||||
@@ -36,6 +38,44 @@ const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.V
|
||||
}
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-string`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
// this is stored in Vault as a string
|
||||
jsonString: '{"x":1,"y":"qux"}',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-data`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
// this is stored in Vault as a map
|
||||
jsonData: {"x":1,"y":"qux"},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-string-multiline`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
jsonStringMultiline,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/sys/mounts/my-secret`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -72,6 +72,22 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets', async () => {
|
||||
mockInput('secret/data/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets with name prefix', async () => {
|
||||
mockInput('secret/data/test * | GROUP_');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get nested secret', async () => {
|
||||
mockInput('secret/data/nested/test otherSecret');
|
||||
|
||||
@@ -103,6 +119,22 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets from K/V v1', async () => {
|
||||
mockInput('my-secret/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets from K/V v1 with name prefix', async () => {
|
||||
mockInput('my-secret/test * | GROUP_');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get nested secret from K/V v1', async () => {
|
||||
mockInput('my-secret/nested/test otherSecret');
|
||||
|
||||
|
||||
Generated
+7
-7
@@ -9,7 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"got": "^11.8.5",
|
||||
"got": "^11.8.6",
|
||||
"jsonata": "^2.0.3",
|
||||
"jsrsasign": "^10.8.6"
|
||||
},
|
||||
@@ -2150,9 +2150,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/got": {
|
||||
"version": "11.8.5",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
|
||||
"integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
|
||||
"version": "11.8.6",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
|
||||
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
@@ -5975,9 +5975,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"got": {
|
||||
"version": "11.8.5",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
|
||||
"integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
|
||||
"version": "11.8.6",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
|
||||
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
|
||||
"requires": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/hashicorp/vault-action#readme",
|
||||
"dependencies": {
|
||||
"got": "^11.8.5",
|
||||
"got": "^11.8.6",
|
||||
"jsonata": "^2.0.3",
|
||||
"jsrsasign": "^10.8.6"
|
||||
},
|
||||
|
||||
+6
-17
@@ -3,6 +3,9 @@ const core = require('@actions/core');
|
||||
const command = require('@actions/core/lib/command');
|
||||
const got = require('got').default;
|
||||
const jsonata = require('jsonata');
|
||||
const { normalizeOutputKey } = require('./utils');
|
||||
const { WILDCARD } = require('./constants');
|
||||
|
||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
|
||||
|
||||
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
|
||||
@@ -171,7 +174,7 @@ function parseSecretsInput(secretsInput) {
|
||||
const selectorAst = jsonata(selectorQuoted).ast();
|
||||
const selector = selectorQuoted.replace(new RegExp('"', 'g'), '');
|
||||
|
||||
if ((selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
if (selector !== WILDCARD && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
|
||||
}
|
||||
|
||||
@@ -191,20 +194,6 @@ function parseSecretsInput(secretsInput) {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace('.', '__').replace(new RegExp('-', 'g'), '').replace(/[^\p{L}\p{N}_-]/gu, '');
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} inputKey
|
||||
* @param {any} inputOptions
|
||||
@@ -233,6 +222,6 @@ function parseHeadersInput(inputKey, inputOptions) {
|
||||
module.exports = {
|
||||
exportSecrets,
|
||||
parseSecretsInput,
|
||||
normalizeOutputKey,
|
||||
parseHeadersInput
|
||||
parseHeadersInput,
|
||||
};
|
||||
|
||||
|
||||
+73
-1
@@ -220,6 +220,55 @@ describe('exportSecrets', () => {
|
||||
expect(core.setOutput).toBeCalledWith('key', '1');
|
||||
});
|
||||
|
||||
it('JSON data secret retrieval', async () => {
|
||||
const jsonData = {"x":1,"y":2};
|
||||
|
||||
let result = JSON.stringify(jsonData);
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonData,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', result);
|
||||
expect(core.setOutput).toBeCalledWith('key', result);
|
||||
});
|
||||
|
||||
it('JSON string secret retrieval', async () => {
|
||||
const jsonString = '{"x":1,"y":2}';
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonString,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
|
||||
expect(core.setOutput).toBeCalledWith('key', jsonString);
|
||||
});
|
||||
|
||||
it('multi-line JSON string secret retrieval', async () => {
|
||||
const jsonString = `
|
||||
{
|
||||
"x":1,
|
||||
"y":"bar"
|
||||
}
|
||||
`;
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonString,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
|
||||
expect(core.setOutput).toBeCalledWith('key', jsonString);
|
||||
});
|
||||
|
||||
it('intl secret retrieval', async () => {
|
||||
mockInput('测试 测试');
|
||||
mockVaultData({
|
||||
@@ -334,7 +383,30 @@ describe('exportSecrets', () => {
|
||||
expect(core.setOutput).toBeCalledWith('key', 'secret');
|
||||
})
|
||||
|
||||
it('multi-line secret gets masked for each line', async () => {
|
||||
it('multi-line secret', async () => {
|
||||
const multiLineString = `ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
|
||||
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
|
||||
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
|
||||
NrRFi9wrf+M7Q==`;
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: multiLineString
|
||||
});
|
||||
mockExportToken("false")
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.setSecret).toBeCalledTimes(5); // 1 for each non-empty line + VAULT_TOKEN
|
||||
|
||||
expect(core.setSecret).toBeCalledWith("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU");
|
||||
expect(core.setSecret).toBeCalledWith("GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3");
|
||||
expect(core.setSecret).toBeCalledWith("Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA");
|
||||
expect(core.setSecret).toBeCalledWith("NrRFi9wrf+M7Q==");
|
||||
expect(core.setOutput).toBeCalledWith('key', multiLineString);
|
||||
})
|
||||
|
||||
it('multi-line secret gets masked for each non-empty line', async () => {
|
||||
const multiLineString = `a multi-line string
|
||||
|
||||
with blank lines
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const WILDCARD = '*';
|
||||
|
||||
module.exports = {
|
||||
WILDCARD
|
||||
};
|
||||
+85
-15
@@ -1,6 +1,6 @@
|
||||
const jsonata = require("jsonata");
|
||||
|
||||
|
||||
const { WILDCARD } = require("./constants");
|
||||
const { normalizeOutputKey } = require("./utils");
|
||||
/**
|
||||
* @typedef {Object} SecretRequest
|
||||
* @property {string} path
|
||||
@@ -23,7 +23,8 @@ const jsonata = require("jsonata");
|
||||
*/
|
||||
async function getSecrets(secretRequests, client) {
|
||||
const responseCache = new Map();
|
||||
const results = [];
|
||||
let results = [];
|
||||
|
||||
for (const secretRequest of secretRequests) {
|
||||
let { path, selector } = secretRequest;
|
||||
|
||||
@@ -46,22 +47,53 @@ async function getSecrets(secretRequests, client) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"'
|
||||
}
|
||||
selector = "data." + selector
|
||||
body = JSON.parse(body)
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
if (selector == WILDCARD) {
|
||||
let keys = body.data;
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector
|
||||
keys = keys.data;
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
results.push({
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse
|
||||
});
|
||||
for (let key in keys) {
|
||||
let newRequest = Object.assign({},secretRequest);
|
||||
newRequest.selector = key;
|
||||
|
||||
if (secretRequest.selector === secretRequest.outputVarName) {
|
||||
newRequest.outputVarName = key;
|
||||
newRequest.envVarName = key;
|
||||
}
|
||||
else {
|
||||
newRequest.outputVarName = secretRequest.outputVarName+key;
|
||||
newRequest.envVarName = secretRequest.envVarName+key;
|
||||
}
|
||||
|
||||
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
|
||||
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
|
||||
|
||||
selector = key;
|
||||
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
newRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -73,6 +105,7 @@ async function getSecrets(secretRequests, client) {
|
||||
async function selectData(data, selector) {
|
||||
const ata = jsonata(selector);
|
||||
let result = JSON.stringify(await ata.evaluate(data));
|
||||
|
||||
// Compat for custom engines
|
||||
if (!result && ((ata.ast().type === "path" && ata.ast()['steps'].length === 1) || ata.ast().type === "string") && selector !== 'data' && 'data' in data) {
|
||||
result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
|
||||
@@ -86,6 +119,43 @@ async function selectData(data, selector) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses selectData with the selector to get the value and then appends it to the
|
||||
* results. Returns a new array with all of the results.
|
||||
* @param {string} selector
|
||||
* @param {object} body
|
||||
* @param {object} cachedResponse
|
||||
* @param {TRequest} secretRequest
|
||||
* @param {SecretResponse<TRequest>[]} results
|
||||
* @return {Promise<SecretResponse<TRequest>[]>}
|
||||
*/
|
||||
const selectAndAppendResults = async (
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
) => {
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"';
|
||||
}
|
||||
selector = "data." + selector;
|
||||
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector;
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
return [
|
||||
...results,
|
||||
{
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSecrets,
|
||||
selectData
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace(".", "__")
|
||||
.replace(new RegExp("-", "g"), "")
|
||||
.replace(/[^\p{L}\p{N}_-]/gu, "");
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
normalizeOutputKey
|
||||
};
|
||||
Reference in New Issue
Block a user