Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { ImportMode } from "./enums";
import { OperationTimeoutError, ArgumentError } from "./errors";
import { AdaptiveTaskManager } from "./internal/adaptiveTaskManager";
import { ImportProgress, KeyLabelLookup } from "./models";
import { ImportProgress, KeyLabelLookup, ConfigurationDiff } from "./models";
import { isConfigSettingEqual } from "./internal/utils";
import { v4 as uuidv4 } from "uuid";
import { Constants } from "./internal/constants";
Expand Down Expand Up @@ -43,7 +43,8 @@
* @param timeout - Seconds of entire import progress timeout
* @param progressCallback - Callback for report the progress of import
* @param importMode - Determines the behavior when importing key-values. The default value, 'All' will import all key-values in the input file to App Configuration. 'Ignore-Match' will only import settings that have no matching key-value in App Configuration.
* @param dryRun - When dry run is enabled, no updates will be performed to App Configuration. Instead, any updates that would have been performed in a normal run will be printed to the console for review
* @param dryRun - When enabled, no updates will be performed to App Configuration. Returns a ConfigurationDiff object and prints changes to console for review.
* @returns ConfigurationDiff when dryRun=true, otherwise void
*/
public async Import(
configSettingsSource: ConfigurationSettingsSource,
Expand All @@ -52,18 +53,47 @@
progressCallback?: (progress: ImportProgress) => unknown,
importMode?: ImportMode,
dryRun?: boolean
) {
): Promise<ConfigurationDiff | void> {
if (importMode == undefined) {
importMode = ImportMode.IgnoreMatch;
}
if (dryRun == undefined) {
dryRun = false;
}
this.validateImportMode(importMode);

// Generate correlation ID for operations
const customCorrelationRequestId: string = uuidv4();
const customHeadersOption: OperationOptions = {
requestOptions: {
customHeaders: {
[Constants.CorrelationRequestIdHeader]: customCorrelationRequestId
}
}
};

const configSettings = await configSettingsSource.GetConfigurationSettings();
const configurationDiff: ConfigurationDiff = await this.analyzeConfigurationChanges(configSettingsSource, strict, importMode, customHeadersOption);

if (dryRun) {
this.printUpdatesToConsole([...configurationDiff.Added, ...configurationDiff.Modified], configurationDiff.Deleted);
return configurationDiff;
}
else {
await this.applyUpdatesToServer(configurationDiff.Added, configurationDiff.Deleted, timeout, customHeadersOption, progressCallback);
}
}

private async analyzeConfigurationChanges(
configSettingsSource: ConfigurationSettingsSource,
strict: boolean,
importMode: ImportMode,
customHeadersOption: OperationOptions
): Promise<ConfigurationDiff> {
const configSettings = await configSettingsSource.GetConfigurationSettings();

const configurationSettingToDelete: ConfigurationSetting<string>[] = [];
const configurationSettingToModify: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[] = [];
const configurationSettingToAdd: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[] = [];
const srcKeyLabelLookUp: KeyLabelLookup = {};

configSettings.forEach((config: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>) => {
Expand All @@ -73,42 +103,45 @@
srcKeyLabelLookUp[config.key][config.label || ""] = true;
});

// generate correlationRequestId for operations in the same activity
const customCorrelationRequestId: string = uuidv4();
const customHeadersOption: OperationOptions = {
requestOptions: {
customHeaders: {
[Constants.CorrelationRequestIdHeader]: customCorrelationRequestId
}
}
};

if (strict || importMode == ImportMode.IgnoreMatch) {
for await (const existing of this.configurationClient.listConfigurationSettings({...configSettingsSource.FilterOptions, ...customHeadersOption})) {
configurationSettingToAdd.push(...configSettings);

const isKeyLabelPresent: boolean = srcKeyLabelLookUp[existing.key] && srcKeyLabelLookUp[existing.key][existing.label || ""];
for await (const existing of this.configurationClient.listConfigurationSettings({...configSettingsSource.FilterOptions, ...customHeadersOption})) {
const isKeyLabelPresent: boolean = srcKeyLabelLookUp[existing.key] && srcKeyLabelLookUp[existing.key][existing.label || ""];
if (strict && !isKeyLabelPresent) {
configurationSettingToDelete.push(existing);
}

const incoming = configSettings.find(configSetting => configSetting.key == existing.key &&
configSetting.label == existing.label);

if (incoming) {
const settingsAreEqual: boolean = isConfigSettingEqual(incoming, existing);

if (strict && !isKeyLabelPresent) {
configurationSettingToDelete.push(existing);
if (!settingsAreEqual) {
configurationSettingToModify.push(incoming);
// Remove from add list since it's a modification, not an addition
const addIndex: number = configurationSettingToAdd.findIndex(addSetting =>
addSetting.key === incoming.key && addSetting.label === incoming.label);
if (addIndex !== -1) {
configurationSettingToAdd.splice(addIndex, 1);
}
}

if (importMode == ImportMode.IgnoreMatch) {
const incoming = configSettings.find(configSetting => configSetting.key == existing.key &&
configSetting.label == existing.label);

if (incoming && isConfigSettingEqual(incoming, existing)) {
configSettings.splice(configSettings.indexOf(incoming), 1);
else if (importMode == ImportMode.IgnoreMatch) {
// Remove unchanged settings from add list
const addIndex = configurationSettingToAdd.findIndex(addSetting =>
addSetting.key === incoming.key && addSetting.label === incoming.label);
if (addIndex !== -1) {
configurationSettingToAdd.splice(addIndex, 1);
}
}
}
}

if (dryRun) {
this.printUpdatesToConsole(configSettings, configurationSettingToDelete);
}
else {
await this.applyUpdatesToServer(configSettings, configurationSettingToDelete, timeout, customHeadersOption, progressCallback);
}

return {
Added: configurationSettingToAdd,
Modified: configurationSettingToModify,
Deleted: configurationSettingToDelete
};
}

private printUpdatesToConsole(
Expand Down Expand Up @@ -146,7 +179,7 @@
await this.executeTasksWithTimeout(importTaskManager, timeout, progressCallback);
}

private newAdaptiveTaskManager<T>(task: (setting: T) => Promise<any>, configurationSettings: Array<T>) {

Check warning on line 182 in libraries/azure-app-configuration-importer/src/appConfigurationImporter.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
let index = 0;
return new AdaptiveTaskManager(() => {
if (index == configurationSettings.length) {
Expand Down
2 changes: 1 addition & 1 deletion libraries/azure-app-configuration-importer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export {
} from "./importOptions";
export * from "./enums";
export * from "./errors";
export { ImportProgress as ImportResult } from "./models";
export { ImportProgress as ImportResult, ConfigurationDiff } from "./models";
export { StringConfigurationSettingsSource } from "./settingsImport/stringConfigurationSettingsSource";
export { ConfigurationSettingsSource } from "./settingsImport/configurationSettingsSource";
export { IterableConfigurationSettingsSource } from "./settingsImport/iterableConfigurationSettingsSource";
Expand Down
13 changes: 13 additions & 0 deletions libraries/azure-app-configuration-importer/src/models.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import {
SecretReferenceValue,
ConfigurationSetting,
SetConfigurationSettingParam,
FeatureFlagValue
} from "@azure/app-configuration";

/**
* @internal
*/
Expand Down Expand Up @@ -42,4 +49,10 @@ export interface KeyLabelLookup {
[key: string]: {
[label: string] : boolean
}
}

export interface ConfigurationDiff {
Deleted: ConfigurationSetting<string>[];
Modified: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[];
Added: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[];
}
Loading