-
Notifications
You must be signed in to change notification settings - Fork 1
Return configuration changes to caller #21
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
base: main
Are you sure you want to change the base?
Changes from 7 commits
8e39079
ff919a4
bde8c64
5640e71
3dec8f0
d0f60b6
0c44519
be23383
4a70857
8d5f1cd
04e88e4
3c436af
bc799ad
0b15321
5f9c46a
0be1539
f2c0fc5
30f8995
7b7c06d
2abfdbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, ConfigurationChanges } from "./models"; | ||
| import { isConfigSettingEqual } from "./internal/utils"; | ||
| import { v4 as uuidv4 } from "uuid"; | ||
| import { Constants } from "./internal/constants"; | ||
|
|
@@ -43,27 +43,72 @@ | |
| * @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 | ||
| * @returns void | ||
ChristineWanjau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| public async Import( | ||
| configSettingsSource: ConfigurationSettingsSource, | ||
| timeout: number, | ||
| strict = false, | ||
| progressCallback?: (progress: ImportProgress) => unknown, | ||
| importMode?: ImportMode, | ||
| dryRun?: boolean | ||
| ) { | ||
| importMode?: ImportMode | ||
MaryanneNjeri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ): Promise<void> { | ||
| if (importMode == undefined) { | ||
| importMode = ImportMode.IgnoreMatch; | ||
| } | ||
| if (dryRun == undefined) { | ||
| dryRun = false; | ||
|
|
||
|
||
| this.validateImportMode(importMode); | ||
MaryanneNjeri marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Generate correlation ID for operations | ||
|
||
| const customCorrelationRequestId: string = uuidv4(); | ||
| const customHeadersOption: OperationOptions = { | ||
| requestOptions: { | ||
| customHeaders: { | ||
| [Constants.CorrelationRequestIdHeader]: customCorrelationRequestId | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const configurationChanges: ConfigurationChanges = await this.getConfigurationChanges(configSettingsSource, strict, importMode, customHeadersOption); | ||
|
|
||
| await this.applyUpdatesToServer([...configurationChanges.Added, ...configurationChanges.Modified], configurationChanges.Deleted, timeout, customHeadersOption, progressCallback); | ||
| } | ||
|
|
||
| /** | ||
| * Get configuration changes between source settings and Azure App Configuration service without any applying changes | ||
|
||
| * | ||
| * Example usage: | ||
| * ```ts | ||
| * const fileData = fs.readFileSync("mylocalPath").toString(); | ||
| * const configurationChanges = await client.getConfigurationChanges( | ||
ChristineWanjau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * new StringConfigurationSettingsSource({data:fileData, format: ConfigurationFormat.Json}), | ||
| * false, | ||
| * ImportMode.All, | ||
| * options | ||
| * ); | ||
| * ``` | ||
| * @param configSettingsSource - A ConfigurationSettingsSource instance. | ||
| * @param strict - Use strict mode to delete settings not in source. | ||
| * @param importMode - Determines the behavior when analyzing key-values. 'All' will include all key-values. 'Ignore-Match' will exclude settings that have matching key-values in App Configuration. | ||
ChristineWanjau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * @param customHeadersOption - Custom headers for the operation. | ||
| * @returns ConfigurationChanges object containing Added, Modified, and Deleted settings | ||
| */ | ||
| public async getConfigurationChanges( | ||
|
||
| configSettingsSource: ConfigurationSettingsSource, | ||
| strict = false, | ||
| importMode?: ImportMode, | ||
| customHeadersOption?: OperationOptions | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this used in this function?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The customHeadersOptions?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. It does look like needed for this function.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use customHeadersOption to pass the correlationRequestId to link requests involved in the Import process. When the Import API calls the GetConfigurationChanges API, we pass the same correlationRequestId to associate both the read and write operations.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline. Updated to generate |
||
| ): Promise<ConfigurationChanges> { | ||
| if (importMode == undefined) { | ||
| importMode = ImportMode.IgnoreMatch; | ||
| } | ||
|
|
||
| this.validateImportMode(importMode); | ||
|
|
||
| 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>) => { | ||
|
|
@@ -73,59 +118,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); | ||
ChristineWanjau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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) { | ||
ChristineWanjau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // 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); | ||
| } | ||
| } | ||
|
|
||
| private printUpdatesToConsole( | ||
| settingsToAdd: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[], | ||
| settingsToDelete: ConfigurationSetting<string>[] | ||
| ): void { | ||
| console.log("The following settings will be removed from App Configuration:"); | ||
| for (const setting of settingsToDelete) { | ||
|
|
||
| console.log(JSON.stringify({key: setting.key, label: setting.label, contentType: setting.contentType, tags: setting.tags})); | ||
| } | ||
|
|
||
| console.log("\nThe following settings will be written to App Configuration:"); | ||
| for (const setting of settingsToAdd) { | ||
|
|
||
| console.log(JSON.stringify({key: setting.key, label: setting.label, contentType: setting.contentType, tags: setting.tags})); | ||
| } | ||
| return { | ||
| Added: configurationSettingToAdd, | ||
| Modified: configurationSettingToModify, | ||
| Deleted: configurationSettingToDelete | ||
| }; | ||
| } | ||
|
|
||
| private async applyUpdatesToServer( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about changing the argument name
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
|
|
@@ -146,7 +177,7 @@ | |
| await this.executeTasksWithTimeout(importTaskManager, timeout, progressCallback); | ||
| } | ||
|
|
||
| private newAdaptiveTaskManager<T>(task: (setting: T) => Promise<any>, configurationSettings: Array<T>) { | ||
| let index = 0; | ||
| return new AdaptiveTaskManager(() => { | ||
| if (index == configurationSettings.length) { | ||
|
|
||
| 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 | ||
| */ | ||
|
|
@@ -42,4 +49,10 @@ export interface KeyLabelLookup { | |
| [key: string]: { | ||
| [label: string] : boolean | ||
| } | ||
| } | ||
|
|
||
| export interface ConfigurationChanges { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a config hasn't change, do we also return it as part of the ConfigurationChanges
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only when import mode is equal to ALL; if a config hasn't changed, then it will be part of the changes |
||
| Deleted: ConfigurationSetting<string>[]; | ||
| Modified: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[]; | ||
| Added: SetConfigurationSettingParam<string | FeatureFlagValue | SecretReferenceValue>[]; | ||
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.