Add Centralized Configuration Provider support (Oracle Database 23ai)

This commit is contained in:
Sharad Chandran R 2024-07-15 12:59:20 +05:30
parent d3277b428e
commit 4977cdeef4
8 changed files with 733 additions and 1 deletions

View File

@ -10,6 +10,14 @@ For deprecated and desupported features, see :ref:`Deprecations and desupported
node-oracledb `v6.6.0 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6.0>`__ (TBD) node-oracledb `v6.6.0 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6.0>`__ (TBD)
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
Common Changes
++++++++++++++
#) Added support for Centralized Configuration Providers (Azure App Configuration Store and OCI
Object Storage).
Node-oracledb extracts configuration information from the the supported provider and uses it to
connect to the database.
Thin Mode Changes Thin Mode Changes
+++++++++++++++++ +++++++++++++++++

View File

@ -0,0 +1,89 @@
// Copyright (c) 2024, Oracle and/or its affiliates.
/******************************************************************************
*
* This software is dual-licensed to you under the Universal Permissive License
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
* either license.
*
* If you elect to accept the software under the Apache License, Version 2.0,
* the following applies:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NAME
* azureProviderSample.js
*
* DESCRIPTION
* Sample program to connect to the database using DB connectstring, username and password
* which are fetched from the Azure Configuration Store using the connectstring which consists of
* required parameters to authenticate azure configuration store.
*
*****************************************************************************/
'use strict';
Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
// This example runs in both node-oracledb Thin and Thick modes.
//
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
// On Windows and macOS Intel platforms, set the environment
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
async function run() {
// replace xxxx with the corresponding values of the parameter
let connection;
const options = {
connectString: 'config-azure://testappconfig.azconfig.io/?key=testapp/testkey/&azure_client_id=xxxx&azure_client_certificate_path=xxxx&azure_tenant_id=xxxx'
};
try {
// Get a non-pooled connection
connection = await oracledb.getConnection(options);
console.log('Connection was successful!');
} catch (err) {
console.error(err);
} finally {
if (connection) {
try {
await connection.close();
} catch (err) {
console.error(err);
}
}
}
}
run();

View File

@ -0,0 +1,89 @@
// Copyright (c) 2024, Oracle and/or its affiliates.
/******************************************************************************
*
* This software is dual-licensed to you under the Universal Permissive License
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
* either license.
*
* If you elect to accept the software under the Apache License, Version 2.0,
* the following applies:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NAME
* ociProviderSample.js
*
* DESCRIPTION
* Sample program to connect to the database using DB connectstring, username and password
* which are fetched from the OCI Object Store using the connectstring which consists of
* required parameters to authenticate OCI Object Store.
*
*****************************************************************************/
'use strict';
Error.stackTraceLimit = 50;
const oracledb = require('oracledb');
// This example runs in both node-oracledb Thin and Thick modes.
//
// Optionally run in node-oracledb Thick mode
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
// On Windows and macOS Intel you can specify the directory containing the
// libraries at runtime or before Node.js starts. On other platforms (where
// Oracle libraries are available) the system library search path must always
// include the Oracle library path before Node.js starts. If the search path
// is not correct, you will get a DPI-1047 error. See the node-oracledb
// installation documentation.
let clientOpts = {};
// On Windows and macOS Intel platforms, set the environment
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
}
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
}
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
async function run() {
//replace xxxx with correct values of the parameter
let connection;
const options = {
connectString: 'config-ociobject://test.us-phoenix-1.oraclecloud.com/n/testnamespace/b/testbucket/o/testobject?oci_tenancy=xxxx&oci_user=xxxx&oci_fingerprint=xxxx&oci_key_file=xxxx',
};
try {
// Get a non-pooled connection
connection = await oracledb.getConnection(options);
console.log('Connection was successful!');
} catch (err) {
console.error(err);
} finally {
if (connection) {
try {
await connection.close();
} catch (err) {
console.error(err);
}
}
}
}
run();

View File

@ -0,0 +1,168 @@
// Copyright (c) 2024, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-----------------------------------------------------------------------------
'use strict';
let AppConfigurationClient;
let ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential;
const errors = require("../errors.js");
const { base } = require("./base.js");
class AzureProvider extends base {
constructor(provider_arg, urlExtendedPart) {
super(urlExtendedPart);
this._addParam("appconfigname", provider_arg);
}
//---------------------------------------------------------------------------
// init()
//
// Require/import modules from azure
//---------------------------------------------------------------------------
init() {
({AppConfigurationClient } = require("@azure/app-configuration"));
({ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential} = require("@azure/identity"));
}
//---------------------------------------------------------------------------
// _withChainedTokenCredential()
//
// Use of ChainedTokenCredential class which provides the ability to link
// together multiple credential instances to be tried sequentially when authenticating.
// Default authentication to try when no authentication parameter is given by the user
//---------------------------------------------------------------------------
_withChainedTokenCredential() {
const tokens = [];
if ((this.paramMap.get("azure_client_secret")))
tokens.push(new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret")));
if ((this.paramMap.get("azure_client_certificate_path")))
tokens.push(new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path")));
if ((this.paramMap.get('azure_managed_identity_client_id')))
tokens.push(this.paramMap.get('azure_managed_identity_client_id'));
tokens.push(new EnvironmentCredential());
const credential = new ChainedTokenCredential(...tokens);
return credential;
}
//---------------------------------------------------------------------------
// _returnCredential()
//
// Returns credential to access Azure Config Store on the basis of
// authentication parameters given by the user.
//---------------------------------------------------------------------------
_returnCredential() {
let auth = null;
if (this.paramMap.get('authentication')) {
auth = this.paramMap.get('authentication').toUpperCase();
}
if (auth && !(auth == 'AZURE_DEFAULT')) {
// do the given authentication
if (auth == 'AZURE_SERVICE_PRINCIPAL') {
if (this.paramMap.get("azure_client_certificate_path"))
return new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path"));
else if (this.paramMap.get("azure_client_secret"))
return new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret"));
else
errors.throwErr(errors.ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED);
} else if (auth == 'AZURE_MANAGED_IDENTITY') {
return new ManagedIdentityCredential(this.paramMap.get('azure_managed_identity_client_id'));
} else {
errors.throwErr(errors.ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED, auth);
}
} else {
//return default token credential
return this._withChainedTokenCredential();
}
}
//---------------------------------------------------------------------------
// _getConfigurationSetting()
//
// Get configuration setting from the config provider given a key
//and an optional label
//---------------------------------------------------------------------------
async _getConfigurationSetting(client, key, label) {
return await client.getConfigurationSetting({ key: key, label: label });
}
async returnConfig() {
const configObject = {};
const label = this.paramMap.get("label");
const credential = this._returnCredential();
// azure config store
const client = new AppConfigurationClient(
"https://" + this.paramMap.get("appconfigname"), // ex: <https://<your appconfig resource>.azconfig.io>
credential
);
// retrieve connect_description
configObject.connectString = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'connect_descriptor', label)).value;
// retrieve node-oracledb parameters
try {
const params = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'node-oracledb', label)).value;
const obj = JSON.parse(params);
for (const key in obj) {
var val = obj[key];
configObject[key] = val;
}
} catch {
configObject['node-oracledb'] = null;
}
try {
// retrieve user
configObject.user = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'user', label)).value;
} catch {
configObject.user = null;
}
//retrieve password
let pwdJson = null;
try {
pwdJson = await this._getConfigurationSetting(client, this.paramMap.get("key") + 'password', label);
} catch {
configObject.password = null;
}
if (pwdJson) {
let obj;
try {
obj = JSON.parse(pwdJson.value);
} catch {
obj = pwdJson.value;
}
if (obj.uri) {
const { SecretClient } = require("@azure/keyvault-secrets");
const vault_detail = await this._parsePwd(obj.uri);
const client1 = new SecretClient(vault_detail[0], credential);
configObject.password = (await client1.getSecret(vault_detail[1])).value;
} else {
configObject.password = pwdJson.value;
}
}
return configObject;
}
}
module.exports = AzureProvider;

101
lib/configProviders/base.js Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) 2024, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-----------------------------------------------------------------------------
'use strict';
class base {
constructor(url) {
const params = new URLSearchParams(url);
this.paramMap = new URLSearchParams([...params].map(([key, value]) => [key.toLowerCase(), value])); //parse the extended part and store parameters in Map
}
/**
* Sets precedence for different parameters in cloudConfig/userConfig
* @param {cloudConfig} object - object retreived from cloud
* @param {userConfig} object - user input Object
*/
modifyOptionsPrecedence(cloudConfig, userConfig) {
// create a copy of userConfig object
userConfig = { ...userConfig };
if (!userConfig.user)
userConfig.user = cloudConfig.user;
if (!userConfig.password)
userConfig.password = cloudConfig.password;
if (cloudConfig.connectString) {
userConfig.connectString = cloudConfig.connectString;
userConfig.connectionString = undefined;
}
if (cloudConfig.poolMin)
userConfig.poolMin = cloudConfig.poolMin;
if (cloudConfig.poolMax)
userConfig.poolMax = cloudConfig.poolMax;
if (cloudConfig.poolIncrement)
userConfig.poolIncrement = cloudConfig.poolIncrement;
if (cloudConfig.poolTimeout)
userConfig.poolTimeout = cloudConfig.poolTimeout;
if (cloudConfig.poolPingInterval)
userConfig.poolPingInterval = cloudConfig.poolPingInterval;
if (cloudConfig.poolPingTimeout)
userConfig.poolPingTimeout = cloudConfig.poolPingTimeout;
if (cloudConfig.stmtCacheSize)
userConfig.stmtCacheSize = cloudConfig.stmtCacheSize;
if (cloudConfig.prefetchRows)
userConfig.prefetchRows = cloudConfig.prefetchRows;
if (cloudConfig.lobPrefetch)
userConfig.lobPrefetch = cloudConfig.lobPrefetch;
return userConfig;
}
//---------------------------------------------------------------------------
// _addParam()
// Adds key,value pairs to the Map
//---------------------------------------------------------------------------
_addParam(key, value) {
const aliasKeyName = key.toLowerCase();
this.paramMap.set(aliasKeyName, value);
}
//---------------------------------------------------------------------------
// _parsePwd()
// Parse password which is in url format
// “uri”:“https://mykeyvault.vault.azure.net/secrets/secretkey”}
//---------------------------------------------------------------------------
_parsePwd(str) {
const vault_uri = new RegExp("(?<vault_url>https://[A-Za-z0-9._-]+)/secrets/(?<secretKey>[A-Za-z][A-Za-z0-9-]*)$", 'g');
const vault_detail = [];
for (const match of str.matchAll(vault_uri)) {
vault_detail[0] = match.groups.vault_url;
vault_detail[1] = match.groups.secretKey;
}
return vault_detail;
}
}
module.exports = {base};

View File

@ -0,0 +1,204 @@
// Copyright (c) 2024, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-----------------------------------------------------------------------------
'use strict';
const errors = require("../errors.js");
const { base } = require("./base.js");
const fs = require('fs');
const cloud_net_naming_pattern_oci = new RegExp("(?<objservername>[A-Za-z0-9._-]+)/n/" + "(?<namespace>[A-Za-z0-9._-]+)/b/" + "(?<bucketname>[A-Za-z0-9._-]+)/o/" + "(?<filename>[A-Za-z0-9._-]+)" + "(/c/(?<alias>.+))?$");
// object to store module references that will be populated by init()
const oci = {};
class OCIProvider extends base {
constructor(provider_arg, urlExtendedPart) {
super(urlExtendedPart);
const match = provider_arg.match(cloud_net_naming_pattern_oci);
if (match) {
this._addParam("objservername", match.groups.objservername);
this._addParam("namespace", match.groups.namespace);
this._addParam("bucketname", match.groups.bucketname);
this._addParam("filename", match.groups.filename);
if (match.groups.alias)
this._addParam("alias", match.groups.alias);
}
}
//---------------------------------------------------------------------------
// init()
//
// Require/import modules from ociobject
//---------------------------------------------------------------------------
init() {
oci.common = require('oci-common');
oci.objectstorage = require('oci-objectstorage');
}
//---------------------------------------------------------------------------
// _streamToString()
//
// Converts data stored in a Readable stream to string
//---------------------------------------------------------------------------
async _streamToString(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks).toString("utf-8");
}
//---------------------------------------------------------------------------
// _returnCredential()
//
// Returns credential to access OCI Object Store on the basis of
// authentication parameters given by the user.
//---------------------------------------------------------------------------
async _returnCredential() {
let provider = null;
let auth = null;
if (this.paramMap.get('authentication')) {
auth = this.paramMap.get('authentication').toUpperCase();
}
// authentication parameter given and its not OCI_DEFAULT
if (auth && !(auth == 'OCI_DEFAULT')) {
if (auth == 'OCI_INSTANCE_PRINCIPAL') {
provider = await new oci.common.InstancePrincipalsAuthenticationDetailsProviderBuilder().build();
} else if (auth == 'OCI_RESOURCE_PRINCIPAL') {
provider = await new oci.common.ResourcePrincipalAuthenticationDetailsProvider.builder();
} else
errors.throwErr(errors.ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED, auth);
} else {
// default authentication
try {
//authentication parameters exist in the configurationFile
provider = new oci.common.ConfigFileAuthenticationDetailsProvider(
this.paramMap.get("oci_profile_path"), //default path ~/.oci/config
this.paramMap.get("oci_profile")
);
} catch (err) {
//throw error for wrong profile or wrong path
if (!this.paramMap.get("oci_tenancy") || !this.paramMap.get("oci_user")) {
throw (err);
}
// authentication parameters are directly given in the connectString
const publicKey = fs.readFileSync(this.paramMap.get('oci_key_file'), { encoding: "utf8" });
const region = this.retrieveRegion(this.paramMap.get('objservername'));
provider = new oci.common.SimpleAuthenticationDetailsProvider(
this.paramMap.get("oci_tenancy"),
this.paramMap.get("oci_user"),
this.paramMap.get("oci_fingerprint"),
publicKey,
undefined,
oci.common.Region[region]
);
}
}
return provider;
}
//---------------------------------------------------------------------------
// returnConfig()
//
// Returns config stored in the OCI Object Store and
// parses and gets password field stored in OCI/Azure Vault
//---------------------------------------------------------------------------
async returnConfig() {
const configObject = {};
const credential = await this._returnCredential();
// oci object store
const client_oci = new (oci.objectstorage).ObjectStorageClient({
authenticationDetailsProvider: credential
});
const getObjectRequest = {
objectName: this.paramMap.get('filename'),
bucketName: this.paramMap.get('bucketname'),
namespaceName: this.paramMap.get('namespace')
};
let credential1;
const getObjectResponse = await client_oci.getObject(getObjectRequest);
const resp = await this._streamToString(getObjectResponse.value);
// Entire object we get from OCI Object Storage
let obj = JSON.parse(resp);
const userAlias = this.paramMap.get('alias');
if (userAlias) {
obj = obj[userAlias];
}
const pmSection = 'node-oracledb';
const params = obj[pmSection];
for (const key in params) {
var val = params[key];
configObject[key] = val;
}
configObject.connectString = obj.connect_descriptor;
configObject.user = obj.user;
if (obj.password) {
if (obj.password.type == "vault-azure") {
if (obj.password.authentication) {
const { SecretClient } = require("@azure/keyvault-secrets");
const {ClientSecretCredential, ClientCertificateCredential} = require("@azure/identity");
if (obj.password.authentication.azure_client_secret)
credential1 = new ClientSecretCredential(obj.password.authentication.azure_tenant_id, obj.password.authentication.azure_client_id, obj.password.authentication.azure_client_secret);
else if (obj.password.authentication.azure_client_certificate_path)
credential1 = new ClientCertificateCredential(obj.password.authentication.azure_tenant_id, obj.password.authentication.azure_client_id, obj.password.authentication.azure_client_certificate_path);
else
errors.throwErr(errors.ERR_AZURE_VAULT_AUTH_FAILED);
const vault_detail = await this._parsePwd(obj.password.value);
const client1 = new SecretClient(vault_detail[0], credential1);
configObject.password = (await client1.getSecret(vault_detail[1])).value;
} else {
errors.throwErr(errors.ERR_AZURE_VAULT_AUTH_FAILED);
}
} else if (obj.password.type == "vault-oci") {
const secrets = require('oci-secrets');
const secretClientOci = new secrets.SecretsClient({
authenticationDetailsProvider: credential
});
const getSecretBundleRequest = {
secretId: obj.password.value
};
const getSecretBundleResponse = await secretClientOci.getSecretBundle(getSecretBundleRequest);
configObject.password = getSecretBundleResponse.secretBundle.secretBundleContent.content;
} else if (obj.password) {
configObject.password = obj.password;
}
} else {
configObject.password = null;
}
return configObject;
}
//---------------------------------------------------------------------------
// retrieveRegion(objservername)
//
// returns region from the given objservername.
//---------------------------------------------------------------------------
retrieveRegion(objservername) {
const arr = objservername.split(".");
return arr[1].toUpperCase().replaceAll('-', '_');
}
}
module.exports = OCIProvider;

View File

@ -179,6 +179,13 @@ const ERR_INVALID_SERVICE_NAME = 518;
const ERR_INVALID_SID = 519; const ERR_INVALID_SID = 519;
const ERR_TNS_NAMES_FILE_MISSING = 520; const ERR_TNS_NAMES_FILE_MISSING = 520;
const ERR_CONNECTION_EOF = 521; const ERR_CONNECTION_EOF = 521;
const ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED = 522;
const ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG = 523;
const ERR_CONFIG_PROVIDER_NOT_SUPPORTED = 524;
const ERR_CONFIG_PROVIDER_LOAD_FAILED = 525;
const ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED = 526;
const ERR_AZURE_VAULT_AUTH_FAILED = 527;
const ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED = 528;
// Oracle SUCCESS_WITH_INFO warning start from 700 // Oracle SUCCESS_WITH_INFO warning start from 700
const WRN_COMPILATION_CREATE = 700; const WRN_COMPILATION_CREATE = 700;
@ -479,7 +486,20 @@ messages.set(ERR_TNS_NAMES_FILE_MISSING, // NJS-520
'cannot connect to Oracle Database. File tnsnames.ora not found in %s'); 'cannot connect to Oracle Database. File tnsnames.ora not found in %s');
messages.set(ERR_CONNECTION_EOF, // NJS-521 messages.set(ERR_CONNECTION_EOF, // NJS-521
'connection to host %s port %d received end-of-file on communication channel. (CONNECTION_ID=%s)'); 'connection to host %s port %d received end-of-file on communication channel. (CONNECTION_ID=%s)');
messages.set(ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED, // NJS-522
'Azure Authentication Failed: The authentication parameter value %s may be incorrect');
messages.set(ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG, // NJS-523
'Failed to retrieve configuration from Centralized Configuration Provider:\n %s');
messages.set(ERR_CONFIG_PROVIDER_NOT_SUPPORTED, // NJS-524
'Configuration Provider not supported: %s');
messages.set(ERR_CONFIG_PROVIDER_LOAD_FAILED, // NJS-525
'Centralized Config Provider failed to load required libraries. Please install the required libraries.\n %s');
messages.set(ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED, // NJS-526
'OCI authentication failed: The authentication parameter value %s may be incorrect');
messages.set(ERR_AZURE_VAULT_AUTH_FAILED, // NJS-527
'Azure Vault: Provide correct azure vault authentication details');
messages.set(ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED, // NJS-528
'Azure service principal authentication requires either a client certificate path or a client secret string');
// Oracle SUCCESS_WITH_INFO warning // Oracle SUCCESS_WITH_INFO warning
messages.set(WRN_COMPILATION_CREATE, // NJS-700 messages.set(WRN_COMPILATION_CREATE, // NJS-700
@ -789,6 +809,13 @@ module.exports = {
ERR_NO_CONFIG_DIR, ERR_NO_CONFIG_DIR,
ERR_TNS_ENTRY_NOT_FOUND, ERR_TNS_ENTRY_NOT_FOUND,
ERR_CONNECTION_EOF, ERR_CONNECTION_EOF,
ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED,
ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED,
ERR_AZURE_VAULT_AUTH_FAILED,
ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED,
ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG,
ERR_CONFIG_PROVIDER_NOT_SUPPORTED,
ERR_CONFIG_PROVIDER_LOAD_FAILED,
ERR_INVALID_BIND_NAME, ERR_INVALID_BIND_NAME,
ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS, ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS,
ERR_BUFFER_LENGTH_INSUFFICIENT, ERR_BUFFER_LENGTH_INSUFFICIENT,

View File

@ -129,6 +129,7 @@ async function _verifyOptions(options, inCreatePool) {
// define normalized options (value returned to caller) // define normalized options (value returned to caller)
const outOptions = {}; const outOptions = {};
options = await _checkConfigProvider(options);
// only one of "user" and "username" may be specified (and must be strings) // only one of "user" and "username" may be specified (and must be strings)
if (options.user !== undefined) { if (options.user !== undefined) {
errors.assertParamPropValue(typeof options.user === 'string', 1, "user"); errors.assertParamPropValue(typeof options.user === 'string', 1, "user");
@ -767,6 +768,51 @@ async function shutdown(a1, a2) {
await conn.close(); await conn.close();
} }
//-----------------------------------------------------------------------------
// _checkConfigProvider()
//
// Look for the config provider in the connection string and retreives
// object stored in the config Provider.
// Returns object based on precedence between input object and the one retrieved
// from Config Provider.
//-----------------------------------------------------------------------------
async function _checkConfigProvider(options) {
const url = options.connectString || options.connectionString;
if (!url)
return options;
let parsedUrl = url;
let urlExtendedPart;
const baseRegex = new RegExp("^config-(?<provider>[A-Za-z0-9]+)(://)(?<provider_arg>[^?]+)");
if (url.indexOf('?') != -1) {
parsedUrl = url.substring(0, url.indexOf('?'));
urlExtendedPart = url.substring(url.indexOf('?'), url.length); //extended part
}
const match = parsedUrl.match(baseRegex);
if (match) {
const provider = match.groups.provider;
const provider_arg = match.groups.provider_arg;
let configPckg;
try {
configPckg = require('./configProviders/' + provider);
} catch (err) {
errors.throwErr(errors.ERR_CONFIG_PROVIDER_NOT_SUPPORTED, provider);
}
const configProvider = new configPckg(provider_arg, urlExtendedPart);
try {
configProvider.init();
} catch (err) {
errors.throwErr(errors.ERR_CONFIG_PROVIDER_LOAD_FAILED, err.message);
}
let secondOpts;
try {
secondOpts = await configProvider.returnConfig();
} catch (err) {
errors.throwErr(errors.ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG, err.message);
}
options = configProvider.modifyOptionsPrecedence(secondOpts, options);
}
return options;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// startup() // startup()