v2.0.1: Consolidate and promise (#5)

* v2.0: Native promise support, consolidation

This version is a breaking change and now only supports native
promises vs callbacks. It is 2020.

This also removes the deps for keyvault config resolver, env
resolver, and config-as-code, as those libraries have now been
archived. Their source lives on here.

* Removing unused library

* Updating library for 2.0.1 launch
This commit is contained in:
Jeff Wilcox 2020-04-22 14:31:14 -07:00 коммит произвёл GitHub
Родитель df7f796149
Коммит a7db593c55
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 967 добавлений и 215 удалений

5
.gitignore поставляемый
Просмотреть файл

@ -16,6 +16,11 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
# local testing
test.js
test.json
.vscode
# nyc test coverage
.nyc_output

Просмотреть файл

@ -2,6 +2,8 @@
Yet another opinionated Node.js configuration library providing a set of default resolvers to enable rapid, rich configuration object graphs powered by the deployment environment, config-as-code, and Azure KeyVault secrets.
This library now requires a modern Node LTS+ version and uses native promises.
## Resolving variables into a simple configuration graph
This library takes an object (a configuration graph) containing simple variables or
@ -22,12 +24,11 @@ In lieu of a configuration graph object, a special `config/` directory structure
with JSON and JS files can be used to build the configuration object at startup,
making it easy to compartmentalize values.
## Part of the painless-config family
## Built on painless-config
This module is a part of the `painless-config` family of configuration libraries.
- [painless-config](https://github.com/Microsoft/painless-config): resolving a variable from an `env.json` file or the environment with a simple `get(key)` method
- [painless-config-as-code](https://github.com/Microsoft/painless-config-as-code): resolving a variable from an environment-specific configuration file located within a repository, enabling configuration-as-code, including code reviews and pull requests for config changes.
## How to use
@ -41,13 +42,10 @@ const graph = {
app: 'my app',
};
resolver.resolve(graph, (error, config) => {
if (error) {
throw error;
}
async function myApp() {
const config = await resolver.resolve(graph);
// ... config has the resolved values ...
});
}
```
After calling `resolve` the `config` object might look like:
@ -59,7 +57,13 @@ After calling `resolve` the `config` object might look like:
}
```
### Unofficial but useful
## Consolidation
As of v2.0.0, this library has merged in the `painless-config-as-code`, environment, and keyvault
environment providers to make it easier to keep the library up-to-date. This admits that this library
is really coupled with KeyVault enough that it is OK to include those dependencies.
## Unofficial but useful
This component was developed by the Open Source Programs Office at Microsoft. The OSPO team
uses Node.js for some of its applications and has found this component to be useful. We are
@ -85,6 +89,12 @@ with any additional questions or comments.
# Changes
## 2.0.0
- Node >= 10.x required (suggest LTS 12+)
- Callbacks removed. The library is built on native Promises now.
- Merges dependent modules `painless-config-as-code`, `environment-configuration-resolver`, `keyvault-configuration-resolver` as native capabilities to reduce the package count and improve updates, publishing and debugging
## 1.1.4
- Updated dependencies

185
docs/KEYVAULT-README.MD Normal file
Просмотреть файл

@ -0,0 +1,185 @@
> This is the former README for the `keyvault-configuration-resolver-node` repo that has been integrated
# Azure KeyVault configuration secrets resolver
This library extends the official Azure KeyVault client library for Node.js with a new
method, `resolveObjectSecrets`, that takes an object graph (typically a set of configuration
data), walks the object to identify strings that use a custom syntax to identify KeyVault
secrets, and then gets the secret(s) using the KeyVault client, updating the value with the
resolved secret.
At this time the library is available on npm with the module name `keyvault-configuration-resolver`.
```
npm install keyvault-configuration-resolver --save
```
## Intended scenario
The purpose of this library is to help resolve a bunch of secrets at app startup, or other
times.
The goal is to prevent developer mistakes by keeping secrets in a KeyVault instead of checking in
credentials to a repository. It also helps keep production secrets more secret by not authorizing
the production key vault instance to work with other applications.
Since the resolution of key vault secrets in this case requires a client ID and secret, anyone
with the client ID and secret could resolve the secrets, but for us it's enough to prevent most
mistakes.
## Limitations/ known issues
- `npm-shrinkwrap.json`: shrinkwrap files may break this script
### Unofficial but useful
This component was developed by the Open Source Programs Office at Microsoft. The OSPO team
uses Node.js for some of its applications and has found this component to be useful. We are
sharing this in the hope that others may find it useful.
It's important to understand that this library was developed for use by a team at Microsoft, but
that this is not an official library or module built by the KeyVault team.
### Why a custom URI protocol syntax
We use a custom URI syntax to identify KeyVault secrets that we would like to address inside of
an object.
This is as opposed to trying to identify strings that are URIs and contain KeyVault addresses; we
have many valid scenarios where we resolve KeyVault secrets at runtime, so we do not want to
resolve them just once.
The custom syntax makes it very clear that the developer/configuration explicitly wants to take
the string with the `keyvault://` syntax and resolve it.
## How to use the library
The library simply extends the provided Azure KeyVault client for Node.js, or creates a new
instance, with a new method called `getObjectSecrets`.
### getObjectSecrets method
The get object secrets method takes in an object and a callback. Anything identified as a
KeyVault string URI (by having the custom `keyvault://` scheme) will
be resolved using KeyVault. The application will need to have permission to the vault(s)
or it will error out.
### example
Here is the environment that we are making available to this application:
```
AZUREAD_CLIENT_ID=our_AAD_app_id
AZUREAD_CLIENT_SECRET=our_AAD_app_secret
SESSION_SALT=keyvault://keyvaultname.vault.azure.net/secrets/our-app-session-salt
AZURE_STORAGE_ACCOUNT=keyvault://account@keyvaultname.vault.azure.net/secrets/azure-storage-account/7038c64d4b094ee897bc62dd43b29640
AZURE_STORAGE_KEY=keyvault://keyvaultname.vault.azure.net/secrets/azure-storage-account/7038c64d4b094ee897bc62dd43b29640
```
Note that this AAD client ID has been authorized as a service principal to have secret GET
access to the KeyVault. You can configure this in the Azure portal or using PowerShell, etc.
Our Azure storage key in this example also has a custom "tag" set called "account" that
includes the account name. This is to demonstrate how to refer to tag values stored
alongside the secret version.
Here is a sample object graph that contains some configuration information that our
application would like to use:
```
let config = {
session: {
salt: process.env.SESSION_SALT,
},
storage: {
account: process.env.AZURE_STORAGE_ACCOUNT,
key: process.env.AZURE_STORAGE_KEY,
},
};
```
And here is how we go ahead at startup inside of our Express app to resolve these secrets:
```
const keyVaultResolver = require('keyvault-configuration-resolver');
const keyVaultConfig = {
clientId: process.env.AZUREAD_CLIENT_ID,
clientSecret: process.env.AZUREAD_CLIENT_SECRET,
};
keyVaultResolver(keyVaultConfig).getObjectSecrets(config, (resolutionError) => {
if (resolutionError) {
throw resolutionError;
}
// at this point, config values such as config.storage.key now have the secrets
// ... continue your app work, or store in middleware
});
```
## How to instantiate the library
### With an AAD client ID and secret
Pass in the `clientId` and `clientSecret` as options to the library.
Alternatively, you can pass in a function called `getClientCredentials` that will be called when they are needed. `clientId` and `clientSecret` values are expected at this time.
### With an existing KeyVault credentials instance
Pass in the credentials as a property called `credentials`, and the KeyVaultClient will be
instantiated for you, along with the new method `getObjectSecrets`.
### With an existing KeyVault client instance
Simply pass in the client instance as the value of a property called `client`. The client
will then have a `getObjectSecrets` method.
You may also just pass in a KeyVault client instance. This is detected by the presence of
a function called "getSecret" on the passed-in object.
### With an AAD client certificate
_Not supported_
This work is not yet complete but may be available in a future update. At this time the app
secret must be provided for KeyVault resolution to work with Node.js apps.
# License
MIT
# Contributing
Pull requests will gladly be considered! A CLA may be needed.
This project has adopted the [Microsoft Open Source Code of
Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com)
with any additional questions or comments.
# Changes
## 1.0.4
- Thrown errors in validation by the key vault clients are propagated
## 1.0.3
- Newer dependencies
- Removes `eslint` dev dep
## 1.0.1
- Uses a newer KeyVault client
## 1.0.0
- For each resolution call (a set of changes through the object graph), a cache of secret responses is maintained. This improves performance for apps that include many sub-secret tag values or reuse the same secret many times.
## 0.9.7
- First stable release

74
docs/PCAC-README.md Normal file
Просмотреть файл

@ -0,0 +1,74 @@
> This is the former README for the `painless-config-as-code` repo that has been integrated
# painless-config-as-code
Environment variable resolution using configuration-as-code logic on top of painless-config. For Node.js apps.
## Environment value resolution
order | type | provider of value | notes
------------ | -------------------- | ----------------------- | ----------
1 | process env variable | painless-config | process.env in node
2 | env.json file value | painless-config | will walk up the directory hierarchy until finding an env.json
3 | _env_.json file val | painless-config-as-code | will look for an ./env/_env_.json, such as ./env/prod.json
4 | env package | npm package | package defined in package.json or ENVIRONMENT_MODULES_NAME
## How to use the library
```
const painlessConfigAsCode = require('painless-config-as-code');
const someValue = painlessConfigAsCode.get('SOME_VALUE');
```
### Unofficial but useful
This component was developed by the Open Source Programs Office at Microsoft. The OSPO team
uses Node.js for some of its applications and has found this component to be useful. We are
sharing this in the hope that others may find it useful.
It's important to understand that this library was developed for use by a team at Microsoft, but
that this is not an official library or module built by the KeyVault team.
# Other Node configuration libraries
There are many other configuration libraries for Node, but of course everyone
has their own favorite. Consider one of these if you'd like a more fully supported
library.
This library is most like `node-config`, with the different being that it is
limited to just JSON files at this time for values, and its use of `painless-config`
to resolve environment variables or other configuration values located up the
directory hierarchy.
In deciding to build this library many other libraries were considered.
# License
MIT
# Contributing
Pull requests will gladly be considered! A CLA may be needed.
This project has adopted the [Microsoft Open Source Code of
Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com)
with any additional questions or comments.
# Changes
## 0.0.2
- Adds support for environment-containing npm package(s)
- Multiple packages are supported, with the first package values winning
- Packages can be defined in an app's `package.json` as well as environment variables such as `ENVIRONMENT_MODULES`
- Environment-variable based package names have higher precedence than `package.json`-based
- Can be required without calling as a function when no custom initialization options are needed
- The environment directory name can now be configured via the `ENVIRONMENT_DIRECTORY` key (and also `ENVIRONMENT_DIRECTORY_KEY` to change that variable name)
- The variable keys used to define the configuration environment can now be customized via `CONFIGURATION_ENVIRONMENT_KEYS`
## 0.0.1
- Initial release

Просмотреть файл

@ -0,0 +1,127 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
'use strict';
const objectPath = require('object-path');
const url = require('url');
// Configuration Assumptions:
// In URL syntax, we define a custom scheme of "env://" which resolves
// an environment variable in the object, directly overwriting the
// original value.
//
// For example:
// "env://HOSTNAME" will resolve on a Windows machine to its hostname
//
// Note that this use of a custom scheme called "env" is not an officially
// recommended or supported thing, but it has worked great for us!
const envProtocol = 'env:';
function getUrlIfEnvironmentVariable(value) {
try {
const u = url.parse(value, true /* parse query string */);
if (u.protocol === envProtocol) {
return u;
}
}
catch (typeError) {
/* ignore */
}
return undefined;
}
function identifyPaths(node, prefix) {
prefix = prefix !== undefined ? prefix + '.' : '';
const paths = {};
for (const property in node) {
const value = node[property];
if (typeof value === 'object') {
Object.assign(paths, identifyPaths(value, prefix + property));
continue;
}
if (typeof value !== 'string') {
continue;
}
const envUrl = getUrlIfEnvironmentVariable(value);
if (!envUrl) {
continue;
}
const originalHostname = value.substr(value.indexOf(envProtocol) + envProtocol.length + 2, envUrl.hostname.length);
if (originalHostname.toLowerCase() === envUrl.hostname.toLowerCase()) {
envUrl.hostname = originalHostname;
}
paths[prefix + property] = envUrl;
}
return paths;
}
function defaultProvider() {
return {
get: (key) => {
return process.env[key];
},
};
}
function createClient(options) {
options = options || {};
let provider = options.provider || defaultProvider();
return {
resolveObjectVariables: async (object) => {
let paths = null;
try {
paths = identifyPaths(object);
} catch(parseError) {
throw parseError;
}
const names = Object.getOwnPropertyNames(paths);
for (let i = 0; i < names.length; i++) {
const path = names[i];
const parsed = paths[path];
const variableName = parsed.hostname;
let variableValue = provider.get(variableName);
// Support for default variables
if (variableValue === undefined && parsed.query && parsed.query.default) {
variableValue = parsed.query.default;
}
// Loose equality "true" for boolean values
if (parsed.query && parsed.query.trueIf) {
variableValue = parsed.query.trueIf == /* loose */ variableValue;
}
// Cast if a type is set to 'boolean' or 'integer'
if (parsed.query && parsed.query.type) {
const currentValue = variableValue;
switch (parsed.query.type) {
case 'boolean':
case 'bool':
if (currentValue && currentValue !== 'false' && currentValue != '0' && currentValue !== 'False') {
variableValue = true;
} else {
variableValue = false;
}
break;
case 'integer':
case 'int':
variableValue = parseInt(currentValue, 10);
break;
default:
throw new Error(`The "type" parameter for the env:// string was set to "${parsed.query.type}", a type that is currently not supported.`);
}
}
objectPath.set(object, path, variableValue);
}
},
};
}
module.exports = createClient;

Просмотреть файл

@ -23,11 +23,15 @@ function jsonProcessor(api, config, p) {
return require(p);
}
module.exports = (api, dirPath, callback) => {
if (!callback && typeof (api) === 'function') {
callback = api;
api = null;
}
function fsReadDir(dirPath) {
return new Promise((resolve, reject) => {
fs.readdir(dirPath, (directoryError, files) => {
return directoryError ? reject(directoryError) : resolve(files);
});
});
}
module.exports = async (api, dirPath) => {
api = api || {};
const options = api.options || {};
@ -35,37 +39,38 @@ module.exports = (api, dirPath, callback) => {
const requireConfigurationDirectory = options.requireConfigurationDirectory || false;
const config = {};
fs.readdir(dirPath, (directoryError, files) => {
if (directoryError && requireConfigurationDirectory) {
return callback(directoryError);
let files = [];
try {
files = await fsReadDir(dirPath);
} catch (directoryError) {
// behavior change: version 1.x of this library through whenever this error'd, not just if required
if (requireConfigurationDirectory) {
throw directoryError;
}
if (directoryError) {
return callback(directoryError);
}
for (let i = 0; i < files.length; i++) {
const file = path.join(dirPath, files[i]);
const ext = path.extname(file);
const nodeName = path.basename(file, ext);
const processor = supportedExtensions.get(ext);
if (!processor) {
continue;
}
for (let i = 0; i < files.length; i++) {
const file = path.join(dirPath, files[i]);
const ext = path.extname(file);
const nodeName = path.basename(file, ext);
const processor = supportedExtensions.get(ext);
if (!processor) {
continue;
try {
const value = processor(api, config, file);
if (value && typeof(value) === 'string' && value === dirPath) {
// Skip the index.js for local hybrid package scenarios
} else if (value !== undefined) {
objectPath.set(config, nodeName, value);
}
try {
const value = processor(api, config, file);
if (value && typeof(value) === 'string' && value === dirPath) {
// Skip the index.js for local hybrid package scenarios
} else if (value !== undefined) {
objectPath.set(config, nodeName, value);
}
} catch (ex) {
ex.path = file;
if (treatErrorsAsWarnings) {
objectPath.set(config, nodeName, ex);
} else {
return callback(ex);
}
} catch (ex) {
ex.path = file;
if (treatErrorsAsWarnings) {
objectPath.set(config, nodeName, ex);
} else {
return callback(ex);
}
}
return callback(null, config);
});
}
return config;
};

Просмотреть файл

@ -5,11 +5,10 @@
'use strict';
const async = require('async');
const environmentConfigurationResolver = require('environment-configuration-resolver');
const environmentConfigurationResolver = require('./environmentConfigurationResolver');
const multiGraphBuilder = require('./multiGraphBuilder');
const keyVaultConfigurationResolver = require('keyvault-configuration-resolver');
const painlessConfigAsCode = require('painless-config-as-code');
const keyVaultConfigurationResolver = require('./keyVaultConfigurationResolver');
const painlessConfigAsCode = require('./painlessConfigAsCode');
const keyVaultClientIdFallbacks = [
// 0: the value of the KEYVAULT_CLIENT_ID_KEY variable
@ -33,11 +32,12 @@ function createDefaultResolvers(libraryOptions) {
} catch (ignoreError) {
/* ignore */
}
// let applicationName = libraryOptions.applicationName || environmentProvider.applicationName;
// KeyVault today needs a client ID and secret to bootstrap the
// resolver. This does mean that the secret cannot be stored in
// the vault.
// the vault. The newer Azure service endpoint allows for
// secrets resolution without storing such a value. This does
// not yet support that concept, sorry.
const keyVaultOptions = {
getClientCredentials: () => {
unshiftOptionalVariable(keyVaultClientIdFallbacks, environmentProvider, 'KEYVAULT_CLIENT_ID_KEY');
@ -86,21 +86,20 @@ function getEnvironmentValue(environmentProvider, potentialNames) {
}
}
function getConfigGraph(libraryOptions, options, environmentProvider, callback) {
async function getConfigGraph(libraryOptions, options, environmentProvider) {
if (options.graph) {
return callback(null, options.graph);
return options.graph;
}
let graphProvider = options.graphProvider || libraryOptions.graphProvider || multiGraphBuilder;
if (!graphProvider) {
return callback(new Error('No graph provider configured for this environment.'));
throw new Error('No graph provider configured for this environment: no options.graphProvider or libraryOptions.graphProvider or multiGraphBuilder');
}
const graphLibraryApi = {
options: options,
options,
environment: environmentProvider,
};
graphProvider(graphLibraryApi, (graphBuildError, graph) => {
return callback(graphBuildError ? graphBuildError : null, graphBuildError ? undefined : graph);
});
const graph = await graphProvider(graphLibraryApi);
return graph;
}
function initialize(libraryOptions) {
@ -111,33 +110,33 @@ function initialize(libraryOptions) {
}
const environmentProvider = resolvers.environment;
return {
resolve: function (options, callback) {
if (!callback && typeof(options) === 'function') {
callback = options;
options = null;
resolve: async function (options) {
if (typeof(options) === 'function') {
const deprecatedCallback = options;
return deprecatedCallback(new Error('This library no longer supports callbacks. Please use native JavaScript promises, i.e. const config = await painlessConfigResolver.resolve();'));
}
options = options || {};
// Find, build or dynamically generate the configuration graph
getConfigGraph(libraryOptions, options, environmentProvider, (buildGraphError, graph) => {
if (buildGraphError) {
return callback(buildGraphError);
const graph = await getConfigGraph(libraryOptions, options, environmentProvider);
if (!graph) {
throw new Error('No configuration "graph" provided as an option to this library. Unless using a configuration graph provider, the graph option must be included.');
}
try {
// Synchronously, in order, resolve the graph
for (const resolver of resolvers) {
await resolver(graph);
}
if (!graph) {
return callback(new Error('No configuration "graph" provided as an option to this library. Unless using a configuration graph provider, the graph option must be included.'));
}
// Synchronously, in order, resolve the graph
async.eachSeries(resolvers, (resolver, next) => {
resolver(graph, next);
}, (err) => {
return callback(err ? err : null, err ? null : graph);
});
});
} catch (resolveConfigurationError) {
console.warn(`Error while resolving the graph with a resolver: ${resolveConfigurationError}`);
throw resolveConfigurationError;
}
return graph;
},
};
}
initialize.resolve = function moduleWithoutInitialization(options, callback) {
initialize().resolve(options, callback);
initialize.resolve = function moduleWithoutInitialization(options) {
return initialize().resolve(options);
};
module.exports = initialize;

Просмотреть файл

@ -0,0 +1,219 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
'use strict';
const adalNode = require('adal-node');
const azureKeyVault = require('azure-keyvault');
const objectPath = require('object-path');
const url = require('url');
const URL = url.URL;
// Key Vault Configuration Assumptions:
// In URL syntax, we define a custom scheme of "keyvault://" which resolves
// a KeyVault secret ID, replacing the original. To use a tag (a custom
// attribute on a secret - could be a username for example), use the tag
// name as the auth parameter of the URL.
//
// For example:
// keyvault://myCustomTag@keyvaultname.vault.azure.net/secrets/secret-value-name/secretVersion",
//
// Would resolve the "myCustomTag" value instead of the secret value.
//
// You can also chose to leave the version off, so that the most recent version
// of the secret will be resolved during the resolution process.
//
// In the case that a KeyVault secret ID is needed inside the app, and not
// handled at startup, then the secret ID (a URI) can be included without
// the custom keyvault:// scheme.
//
// Note that this use of a custom scheme called "keyvault" is not an officially
// recommended or supported approach for KeyVault use in applications, and may
// not be endorsed by the engineering team responsible for KeyVault, but for our
// group and our Node apps, it has been very helpful.
const keyVaultProtocol = 'keyvault:';
const httpsProtocol = 'https:';
const secretsPath = '/secrets/';
function getSecretAsPromise(keyVaultClient, secretStash, secretId) {
return new Promise((resolve, reject) => {
return getSecret(keyVaultClient, secretStash, secretId, (error, result) => {
return error ? reject(error) : resolve(result);
});
});
}
function getSecret(keyVaultClient, secretStash, secretId, callback) {
const cached = secretStash.get(secretId);
if (cached) {
return callback(null, cached);
}
const secretUrl = new URL(secretId);
const vaultBaseUrl = secretUrl.origin;
const i = secretUrl.pathname.indexOf(secretsPath);
if (i < 0) {
return callback(new Error('The requested resource must be a KeyVault secret'));
}
let secretName = secretUrl.pathname.substr(i + secretsPath.length);
let version = '';
const versionIndex = secretName.indexOf('/');
if (versionIndex >= 0) {
version = secretName.substr(versionIndex + 1);
secretName = secretName.substr(0, versionIndex);
}
try {
keyVaultClient.getSecret(vaultBaseUrl, secretName, version, (getSecretError, secretResponse) => {
if (getSecretError) {
return callback(getSecretError);
}
secretStash.set(secretId, secretResponse);
return callback(null, secretResponse);
});
} catch (keyVaultValidationError) {
return callback(keyVaultValidationError);
}
}
function getUrlIfVault(value) {
try {
const keyVaultUrl = url.parse(value);
if (keyVaultUrl.protocol === keyVaultProtocol) {
return keyVaultUrl;
}
}
catch (typeError) {
/* ignore */
}
return undefined;
}
function identifyKeyVaultValuePaths(node, prefix) {
prefix = prefix !== undefined ? prefix + '.' : '';
const paths = {};
for (const property in node) {
const value = node[property];
if (typeof value === 'object') {
Object.assign(paths, identifyKeyVaultValuePaths(value, prefix + property));
continue;
}
if (typeof value !== 'string') {
continue;
}
const keyVaultUrl = getUrlIfVault(value);
if (keyVaultUrl === undefined) {
continue;
}
paths[prefix + property] = keyVaultUrl;
}
return paths;
}
function wrapClient(keyVaultClient) {
keyVaultClient.getObjectSecrets = async function resolveSecrets(object) {
let paths = null;
try {
paths = identifyKeyVaultValuePaths(object);
} catch(parseError) {
throw parseError;
}
// Build a unique list of secrets, fetch them at once
const uniqueUris = new Set();
const properties = new Map();
for (const path in paths) {
const value = paths[path];
const tag = value.auth;
value.protocol = httpsProtocol;
value.auth = null;
const uri = url.format(value);
properties.set(path, [uri, tag]);
uniqueUris.add(uri);
}
const secretStash = new Map();
const uniques = Array.from(uniqueUris.values());
for (const uniqueSecretId of uniques) {
try {
await getSecretAsPromise(keyVaultClient, secretStash, uniqueSecretId);
} catch (resolveSecretError) {
console.log(`Error resolving secret with ID ${uniqueSecretId}: ${resolveSecretError}`);
throw resolveSecretError;
}
}
for (const path in paths) {
const [uri, tag] = properties.get(path);
const secretResponse = secretStash.get(uri);
let value = undefined;
if (tag === null) {
value = secretResponse.value;
} else if (secretResponse.tags) {
value = secretResponse.tags[tag];
}
objectPath.set(object, path, value);
}
};
return keyVaultClient;
}
function createAndWrapKeyVaultClient(options) {
if (!options) {
throw new Error('No options provided for the key vault resolver.');
}
let client = options && options.getSecret && typeof(options.getSecret) === 'function' ? options : options.client;
if (options.credentials && !client) {
client = new azureKeyVault.KeyVaultClient(options.credentials);
}
if (!client) {
let clientId = null;
let clientSecret = null;
let getClientCredentials = options.getClientCredentials;
if (!getClientCredentials) {
if (!options.clientId) {
throw new Error('Must provide an Azure Active Directory "clientId" value to the key vault resolver.');
}
if (!options.clientSecret) {
throw new Error('Must provide an Azure Active Directory "clientSecret" value to the key vault resolver.');
}
clientId = options.clientId;
clientSecret = options.clientSecret;
}
const authenticator = (challenge, authCallback) => {
const context = new adalNode.AuthenticationContext(challenge.authorization);
// Support optional delayed secret resolution
if (getClientCredentials && (!clientId || !clientSecret)) {
try {
const ret = getClientCredentials();
if (ret) {
clientId = ret.clientId;
clientSecret = ret.clientSecret;
}
} catch (getClientCredentialsError) {
return authCallback(getClientCredentialsError);
}
if (!clientId || !clientSecret) {
return authCallback(new Error('After calling getClientCredentials, "clientId" and/or "clientSecret" remained unset. These values are required to authenticate with the vault.'));
}
}
return context.acquireTokenWithClientCredentials(challenge.resource, clientId, clientSecret, (tokenAcquisitionError, tokenResponse) => {
if (tokenAcquisitionError) {
return authCallback(tokenAcquisitionError);
}
const authorizationValue = `${tokenResponse.tokenType} ${tokenResponse.accessToken}`;
return authCallback(null, authorizationValue);
});
};
const credentials = new azureKeyVault.KeyVaultCredentials(authenticator);
client = new azureKeyVault.KeyVaultClient(credentials);
}
return wrapClient(client);
}
module.exports = createAndWrapKeyVaultClient;

Просмотреть файл

@ -6,18 +6,13 @@
'use strict';
const appRoot = require('app-root-path');
const async = require('async');
const deepmerge = require('deepmerge');
const fs = require('fs');
const path = require('path');
const graphBuilder = require('./graphBuilder');
function composeGraphs(api, callback) {
if (!callback && typeof (api) === 'function') {
callback = api;
api = null;
}
async function composeGraphs(api) {
api = api || {};
const options = api.options || {};
let applicationRoot = (options.applicationRoot || appRoot).toString();
@ -46,27 +41,21 @@ function composeGraphs(api, callback) {
}
if (paths.length === 0) {
return callback(new Error('No configuration packages or directories were found to process. Consider using "options.graph" as an option to the configuration resolver if you do not need to use configuration directories. Otherwise, check that you have configured your package.json or other environment values as needed.'));
throw new Error('No configuration packages or directories were found to process. Consider using "options.graph" as an option to the configuration resolver if you do not need to use configuration directories. Otherwise, check that you have configured your package.json or other environment values as needed.');
}
// Build the graph
// ---------------
let graph = {};
async.eachSeries(paths.reverse(), (p, next) => {
graphBuilder(api, p, (buildError, result) => {
if (buildError) {
return next(buildError);
}
const overwriteMerge = (destinationArray, sourceArray/* , options*/) => sourceArray;
graph = deepmerge(graph, result, { arrayMerge: overwriteMerge });
return next();
});
}, error => {
if (!error && (!graph || Object.getOwnPropertyNames(graph).length === 0)) {
error = new Error(`Successfully processed ${paths.length} configuration graph packages or directories, yet the resulting graph object did not have properties. This is likely an error or issue that should be corrected. Or, alternatively, use options.graph as an input to the resolver.`);
}
return callback(error, error ? null : graph);
});
for (const p of paths.reverse()) {
const result = await graphBuilder(api, p);
const overwriteMerge = (destinationArray, sourceArray/* , options*/) => sourceArray;
graph = deepmerge(graph, result, { arrayMerge: overwriteMerge });
}
if (!graph || Object.getOwnPropertyNames(graph).length === 0) {
throw new Error(`Successfully processed ${paths.length} configuration graph packages or directories, yet the resulting graph object did not have properties. This is likely an error or issue that should be corrected. Or, alternatively, use options.graph as an input to the resolver.`);
}
return graph;
}
function addConfigPackages(paths, applicationRoot, painlessConfigObjects) {

163
lib/painlessConfigAsCode.js Normal file
Просмотреть файл

@ -0,0 +1,163 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
'use strict';
const appRoot = require('app-root-path');
const painlessConfig = require('painless-config');
const path = require('path');
let unconfigured = null;
function objectProvider(json, applicationName) {
const appKey = applicationName ? `app:${applicationName}` : null;
return {
get: function get(key) {
if (json && json[appKey] && json[appKey][key]) {
return json[appKey][key];
}
return json[key];
},
};
}
function configurePackageEnvironments(providers, environmentModules, environment, appName) {
let environmentInstances = [];
for (let i = 0; i < environmentModules.length; i++) {
// CONSIDER: Should the name strip any @ after the first slash, in case it is a version-appended version?
const npmName = environmentModules[i].trim();
if (!npmName) {
continue;
}
let environmentPackage = null;
try {
environmentPackage = require(npmName);
} catch (packageRequireError) {
const packageMissing = new Error(`Unable to require the "${npmName}" environment package for the "${environment}" environment`);
packageMissing.innerError = packageRequireError;
throw packageMissing;
}
if (!environmentPackage) {
continue;
}
let values = null;
if (typeof(environmentPackage) === 'function') {
environmentInstances.push(environmentPackage);
try {
values = environmentPackage(environment);
} catch (problemCalling) {
const asText = problemCalling.toString();
const error = new Error(`While calling the environment package "${npmName}" for the "${environment}" environment an error was thrown: ${asText}`);
error.innerError = problemCalling;
throw error;
}
} else if (typeof(environmentPackage) === 'object') {
values = environmentPackage;
}
if (!values) {
throw new Error(`Could not determine what to do with the environment package "${npmName}" for the "${environment}" environment (no values or unexpected type)`);
}
providers.push(objectProvider(values, appName));
return environmentInstances;
}
}
function configureLocalEnvironment(providers, appRoot, directoryName, environment, applicationName) {
const envFile = `${environment}.json`;
const envPath = path.join(appRoot, directoryName, envFile);
try {
const json = require(envPath);
providers.push(objectProvider(json, applicationName));
} catch (noFile) {
// no file
}
}
function tryGetPackage(appRoot) {
try {
const packagePath = path.join(appRoot, 'package.json');
const pkg = require(packagePath);
return pkg;
} catch (noPackage) {
// If there is no package.json for the app, well, that's OK
}
}
function initialize(options) {
options = options || {};
const applicationRoot = options.applicationRoot || appRoot;
const applicationName = options.applicationName || undefined;
const provider = options.provider || painlessConfig;
let environmentInstances = null;
let configurationEnvironmentKeyNames = (provider.get('CONFIGURATION_ENVIRONMENT_KEYS') || 'CONFIGURATION_ENVIRONMENT,NODE_ENV').split(',');
if (!configurationEnvironmentKeyNames || configurationEnvironmentKeyNames.length === 0) {
throw new Error('No configuration environment key name(s) defined');
}
let environment = null;
for (let i = 0; !environment && i < configurationEnvironmentKeyNames.length; i++) {
environment = provider.get(configurationEnvironmentKeyNames[i]);
}
if (!environment) {
return provider;
}
const providers = [
provider,
];
if (provider.testConfiguration) {
providers.push(objectProvider(provider.testConfiguration[environment], applicationName));
} else {
const appRoot = applicationRoot.toString();
const pkg = tryGetPackage(appRoot);
const appName = applicationName || (pkg && pkg.painlessConfigApplicationName ? pkg.painlessConfigApplicationName : undefined);
const environmentDirectoryKey = provider.get('ENVIRONMENT_DIRECTORY_KEY') || 'ENVIRONMENT_DIRECTORY';
const directoryName = options.directoryName || provider.get(environmentDirectoryKey) || 'env';
configureLocalEnvironment(providers, appRoot, directoryName, environment, appName);
const environmentModulesKey = provider.get('ENVIRONMENT_MODULES_KEY') || 'ENVIRONMENT_MODULES';
const environmentModules = (provider.get(environmentModulesKey) || '').split(',');
let painlessConfigEnvironments = pkg ? pkg.painlessConfigEnvironments : null;
if (painlessConfigEnvironments) {
if (Array.isArray(painlessConfigEnvironments)) {
// This is ready-to-use as-is
} else if (painlessConfigEnvironments.split) {
painlessConfigEnvironments = painlessConfigEnvironments.split(',');
} else {
throw new Error('Unknown how to process the painlessConfigEnvironments values in package.json');
}
environmentModules.push(...painlessConfigEnvironments);
}
environmentInstances = configurePackageEnvironments(providers, environmentModules, environment, appName);
}
return {
environmentInstances: environmentInstances,
get: function (key) {
for (let i = 0; i < providers.length; i++) {
const value = providers[i].get(key);
if (value !== undefined) {
return value;
}
}
}
};
}
initialize.get = function getWithoutInitialize(key) {
if (!unconfigured) {
unconfigured = initialize();
}
return unconfigured.get(key);
};
module.exports = initialize;

197
package-lock.json сгенерированный
Просмотреть файл

@ -1,24 +1,24 @@
{
"name": "painless-config-resolver",
"version": "1.1.3",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "8.10.51",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.51.tgz",
"integrity": "sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q=="
"version": "8.10.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz",
"integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg=="
},
"adal-node": {
"version": "0.1.28",
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz",
"integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.2.0.tgz",
"integrity": "sha512-DzkdOpBbOnqErw2TWT0PFA5gsKgmxIfdr/ZgL+if8+ln/tz8JAkk/MtUhH3ftnInrcJVsnR7re4UhpR+KDRnTw==",
"requires": {
"@types/node": "^8.0.47",
"async": ">=0.6.0",
"async": "^2.6.3",
"date-utils": "*",
"jws": "3.x.x",
"request": ">= 2.52.0",
"request": "^2.88.0",
"underscore": ">= 1.3.1",
"uuid": "^3.1.0",
"xmldom": ">= 0.1.x",
@ -26,20 +26,20 @@
}
},
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"app-root-path": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz",
"integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz",
"integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw=="
},
"asn1": {
"version": "0.2.4",
@ -55,9 +55,12 @@
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz",
"integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ=="
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"requires": {
"lodash": "^4.17.14"
}
},
"asynckit": {
"version": "0.4.0",
@ -70,17 +73,17 @@
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
},
"azure-keyvault": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/azure-keyvault/-/azure-keyvault-3.0.4.tgz",
"integrity": "sha1-t3M9j1jZmmb5rnZkUVVus7BY2uU=",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/azure-keyvault/-/azure-keyvault-3.0.5.tgz",
"integrity": "sha512-59fzKRq9dnzv03lEuImvgXc3QjRJoSJtK0gv1WXoqCivBuPdFNK+x6hAjoEDS2WEOXG+7m3uiJWqpMh/8NW3ow==",
"requires": {
"ms-rest": "^2.3.2",
"ms-rest-azure": "^2.5.5"
"ms-rest": "^2.5.0",
"ms-rest-azure": "^2.6.0"
}
},
"bcrypt-pbkdf": {
@ -128,9 +131,9 @@
"integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q="
},
"deepmerge": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz",
"integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww=="
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"delayed-stream": {
"version": "1.0.0",
@ -159,21 +162,6 @@
"safe-buffer": "^5.0.1"
}
},
"environment-configuration-resolver": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/environment-configuration-resolver/-/environment-configuration-resolver-0.1.2.tgz",
"integrity": "sha512-oc64INP1mj94ytfTqexshn8fA2/BJmLcoWWr6LOqlb+seHi1pGxzX4pX9zDM/LuiI5TF9Z8MVNmUnDnDgnJv6A==",
"requires": {
"object-path": "0.11.3"
},
"dependencies": {
"object-path": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.3.tgz",
"integrity": "sha1-PiGkKtByNNgVQprp4VwcXzgFBVQ="
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -185,14 +173,14 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"forever-agent": {
"version": "0.6.1",
@ -311,33 +299,22 @@
"safe-buffer": "^5.0.1"
}
},
"keyvault-configuration-resolver": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/keyvault-configuration-resolver/-/keyvault-configuration-resolver-1.0.4.tgz",
"integrity": "sha512-rdDudoEDC6AygefHOSra8y2pa40bJUM9Li1NeSyLiEgwIHLE72XKr3n8NUeW3Rxkr0GluN4yTnWU0hm1LrNG6A==",
"requires": {
"adal-node": "0.1.28",
"async": "3.1.0",
"azure-keyvault": "3.0.4",
"object-path": "0.11.4"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"mime-db": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
},
"mime-types": {
"version": "2.1.24",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"requires": {
"mime-db": "1.40.0"
"mime-db": "1.43.0"
}
},
"moment": {
@ -346,9 +323,9 @@
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"ms-rest": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/ms-rest/-/ms-rest-2.5.3.tgz",
"integrity": "sha512-p0CnzrTzEkS8UTEwgCqT2O5YVK9E8KGBBlJVm3hFtMZvf0dmncKYXWFPyUa4PAsfBL7h4jfu39tOIFTu6exntg==",
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/ms-rest/-/ms-rest-2.5.4.tgz",
"integrity": "sha512-VeqCbawxRM6nhw0RKNfj7TWL7SL8PB6MypqwgylXCi+u412uvYoyY/kSmO8n06wyd8nIcnTbYToCmSKFMI1mCg==",
"requires": {
"duplexer": "^0.1.1",
"is-buffer": "^1.1.6",
@ -373,6 +350,22 @@
"uuid": "^3.2.1"
},
"dependencies": {
"adal-node": {
"version": "0.1.28",
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz",
"integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=",
"requires": {
"@types/node": "^8.0.47",
"async": ">=0.6.0",
"date-utils": "*",
"jws": "3.x.x",
"request": ">= 2.52.0",
"underscore": ">= 1.3.1",
"uuid": "^3.1.0",
"xmldom": ">= 0.1.x",
"xpath.js": "~1.1.0"
}
},
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
@ -409,24 +402,15 @@
}
}
},
"painless-config-as-code": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/painless-config-as-code/-/painless-config-as-code-1.0.0.tgz",
"integrity": "sha512-UK6QqFoV712fLmkxveRc+UtHVBOFCjX2lXWveXnzGokzlDaOzLfZ3cw1/UrmnkbDQYNo1eO2OivVM2cV0egGyw==",
"requires": {
"app-root-path": "^2.0.1",
"painless-config": "^0.1.0"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"psl": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz",
"integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"punycode": {
"version": "2.1.1",
@ -439,9 +423,9 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@ -450,7 +434,7 @@
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.0",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
@ -460,7 +444,7 @@
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
@ -497,19 +481,12 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
},
"tunnel": {
@ -531,9 +508,9 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"underscore": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
"integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
},
"uri-js": {
"version": "4.2.2",
@ -544,9 +521,9 @@
}
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"verror": {
"version": "1.10.0",
@ -564,9 +541,9 @@
"integrity": "sha1-DfjRGOMrSyhORDp50jCwBmksrnU="
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
"integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz",
"integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g=="
},
"xpath.js": {
"version": "1.1.0",

Просмотреть файл

@ -1,13 +1,13 @@
{
"name": "painless-config-resolver",
"version": "1.1.4",
"version": "2.0.1",
"description": "Yet another opinionated Node.js configuration library providing a set of default resolvers to enable rapid, rich configuration object graphs powered by the deployment environment, config-as-code, and Azure KeyVault secrets.",
"main": "lib/index.js",
"engines": {
"node": "~6.9.0"
"node": ">=10.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no tests for this project\" && exit 1"
},
"keywords": [
"config",
@ -31,12 +31,11 @@
"license": "MIT",
"repository": "Microsoft/painless-config-resolver",
"dependencies": {
"app-root-path": "2.2.1",
"async": "3.1.0",
"deepmerge": "4.0.0",
"environment-configuration-resolver": "0.1.2",
"keyvault-configuration-resolver": "1.0.4",
"adal-node": "0.2.0",
"app-root-path": "3.0.0",
"azure-keyvault": "3.0.5",
"deepmerge": "4.2.2",
"object-path": "0.11.4",
"painless-config-as-code": "1.0.0"
"painless-config": "0.1.1"
}
}