Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for labels #417

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions src/dotenv-azure.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'fs'
import { URL } from 'url'
import Bottleneck from 'bottleneck'
import dotenv, { DotenvParseOptions, DotenvParseOutput } from 'dotenv'
import dotenv, { DotenvParseOptions } from 'dotenv'
import { ManagedIdentityCredential, ClientSecretCredential } from '@azure/identity'
import { SecretClient } from '@azure/keyvault-secrets'
import { AppConfigurationClient, ConfigurationSetting } from '@azure/app-configuration'
Expand Down Expand Up @@ -60,8 +60,11 @@ export default class DotenvAzure {
async config(options: DotenvAzureConfigOptions = {}): Promise<DotenvAzureConfigOutput> {
const { safe = false } = options
const dotenvResult = dotenv.config(options)

const azureVars = await this.loadFromAzure(dotenvResult.parsed)
const vars: Record<string, string | undefined> = {
...(dotenvResult.parsed || {}),
...process.env,
}
const azureVars = await this.loadFromAzure(vars)
const joinedVars = { ...azureVars, ...dotenvResult.parsed }

populateProcessEnv(azureVars)
Expand Down Expand Up @@ -96,10 +99,14 @@ export default class DotenvAzure {
* @param dotenvVars - dotenv parse() output containing azure credentials variables
* @returns an object with keys and values
*/
async loadFromAzure(dotenvVars?: DotenvParseOutput): Promise<VariablesObject> {
async loadFromAzure(dotenvVars?: Record<string, string | undefined>): Promise<VariablesObject> {
const credentials = this.getAzureCredentials(dotenvVars)
const appConfigClient = new AppConfigurationClient(credentials.connectionString)
const { appConfigVars, keyVaultReferences } = await this.getAppConfigurations(appConfigClient)
const labels = dotenvVars?.AZURE_APP_CONFIG_LABELS || ''
const { appConfigVars, keyVaultReferences } = await this.getAppConfigurations(
appConfigClient,
labels
)
const keyVaultSecrets = await this.getSecretsFromKeyVault(credentials, keyVaultReferences)
return { ...appConfigVars, ...keyVaultSecrets }
}
Expand All @@ -114,11 +121,14 @@ export default class DotenvAzure {
}
}

protected async getAppConfigurations(client: AppConfigurationClient): Promise<AppConfigurations> {
protected async getAppConfigurations(
client: AppConfigurationClient,
labels = ''
): Promise<AppConfigurations> {
const appConfigVars: VariablesObject = {}
const keyVaultReferences: KeyVaultReferences = {}

for await (const config of client.listConfigurationSettings()) {
for await (const config of client.listConfigurationSettings({ labelFilter: labels })) {
if (this.isKeyVaultReference(config)) {
keyVaultReferences[config.key] = this.getKeyVaultReferenceInfo(config)
} else {
Expand Down Expand Up @@ -194,10 +204,9 @@ export default class DotenvAzure {
)
}

private getAzureCredentials(dotenvVars: DotenvParseOutput = {}): AzureCredentials {
const vars = { ...dotenvVars, ...process.env }
private getAzureCredentials(vars: Record<string, string | undefined> = {}): AzureCredentials {
const connectionString = this.connectionString || vars.AZURE_APP_CONFIG_CONNECTION_STRING

if (!connectionString) {
throw new MissingAppConfigCredentialsError()
}
Expand Down
1 change: 1 addition & 0 deletions test/azure.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const appConfigListMock = jest.fn(() =>
isReadOnly: false,
key: 'APP_CONFIG_VAR',
value: 'ok',
label: 'the-label',
},
{
isReadOnly: true,
Expand Down
7 changes: 7 additions & 0 deletions test/dotenv-azure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const mockReadFileSync = readFileSync as jest.Mock
describe('DotenvAzure', () => {
const OLD_ENV = process.env
const AZURE_APP_CONFIG_CONNECTION_STRING = 'app-config-conneciton-string'
const AZURE_APP_CONFIG_LABELS = 'app-config-labels'
const AZURE_TENANT_ID = 'tenant-id'
const AZURE_CLIENT_ID = 'client-id'
const AZURE_CLIENT_SECRET = 'client-secret'
Expand All @@ -44,6 +45,12 @@ describe('DotenvAzure', () => {
expect(await dotenvAzure.config()).toBeDefined()
})

it('does not throw when AZURE_APP_CONFIG_LABELS is defined', async () => {
process.env = { ...OLD_ENV, AZURE_APP_CONFIG_CONNECTION_STRING, AZURE_APP_CONFIG_LABELS }
const dotenvAzure = new DotenvAzure()
expect(await dotenvAzure.config()).toBeDefined()
})

it('throws when AZURE_APP_CONFIG_URL and AZURE_APP_CONFIG_CONNECTION_STRING are not defined', () => {
const dotenvAzure = new DotenvAzure()
expect(dotenvAzure.config()).rejects.toThrowError(MissingAppConfigCredentialsError)
Expand Down