From 61be36c16c921a2430e913975bd6ac6f8d987fc2 Mon Sep 17 00:00:00 2001 From: Harsha Nalluru Date: Fri, 2 Apr 2021 15:05:08 -0700 Subject: [PATCH] [App Config] FeatureFlag and SecretReference support (#14342) Reference: https://github.com/Azure/azure-sdk-for-net/pull/18487/ This PR adds the - [x] FeatureFlag support - [x] SecretReference support - [x] Tests for both - [x] Samples for both - [x] Changelog - [x] Links to the samples in the readme --- .../app-configuration/CHANGELOG.md | 8 +- .../app-configuration/README.md | 2 + .../app-configuration/package.json | 3 +- .../recording_can_add_and_get_featureflag.js | 107 +++++ ...ecording_can_add_and_update_featureflag.js | 175 +++++++++ ...d_list_and_update_multiple_featureflags.js | 261 +++++++++++++ ...cording_can_add_and_get_secretreference.js | 99 +++++ ...ding_can_add_and_update_secretreference.js | 167 ++++++++ ...st_and_update_multiple_secretreferences.js | 253 ++++++++++++ .../review/app-configuration.api.md | 75 ++++ .../app-configuration/sample.env | 13 + .../samples-dev/featureFlag.ts | 172 ++++++++ .../samples-dev/secretReference.ts | 114 ++++++ .../samples/v1/javascript/README.md | 4 + .../samples/v1/javascript/featureFlag.js | 170 ++++++++ .../samples/v1/javascript/package.json | 6 +- .../samples/v1/javascript/sample.env | 13 + .../samples/v1/javascript/secretReference.js | 110 ++++++ .../samples/v1/typescript/README.md | 4 + .../samples/v1/typescript/package.json | 6 +- .../samples/v1/typescript/sample.env | 13 + .../samples/v1/typescript/src/featureFlag.ts | 172 ++++++++ .../v1/typescript/src/secretReference.ts | 114 ++++++ .../samples/v1/typescript/tsconfig.json | 2 +- .../src/appConfigurationClient.ts | 11 +- .../app-configuration/src/featureFlag.ts | 366 ++++++++++++++++++ .../generated/src/appConfigurationContext.ts | 2 +- .../app-configuration/src/index.ts | 20 +- .../app-configuration/src/internal/helpers.ts | 51 ++- .../src/internal/jsonModels.ts | 99 +++++ .../src/internal/typeguards.ts | 47 +++ .../src/keyvaultReference.ts | 75 ++++ .../app-configuration/src/models.ts | 3 +- .../test/public/featureFlag.spec.ts | 195 ++++++++++ .../test/public/secretReference.spec.ts | 162 ++++++++ 35 files changed, 3075 insertions(+), 19 deletions(-) create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_get_featureflag.js create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_update_featureflag.js create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_list_and_update_multiple_featureflags.js create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_get_secretreference.js create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_update_secretreference.js create mode 100644 sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_list_and_update_multiple_secretreferences.js create mode 100644 sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts create mode 100644 sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts create mode 100644 sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js create mode 100644 sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js create mode 100644 sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts create mode 100644 sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts create mode 100644 sdk/appconfiguration/app-configuration/src/featureFlag.ts create mode 100644 sdk/appconfiguration/app-configuration/src/internal/jsonModels.ts create mode 100644 sdk/appconfiguration/app-configuration/src/internal/typeguards.ts create mode 100644 sdk/appconfiguration/app-configuration/src/keyvaultReference.ts create mode 100644 sdk/appconfiguration/app-configuration/test/public/featureFlag.spec.ts create mode 100644 sdk/appconfiguration/app-configuration/test/public/secretReference.spec.ts diff --git a/sdk/appconfiguration/app-configuration/CHANGELOG.md b/sdk/appconfiguration/app-configuration/CHANGELOG.md index cceb1680ff88..bbac8d015fbf 100644 --- a/sdk/appconfiguration/app-configuration/CHANGELOG.md +++ b/sdk/appconfiguration/app-configuration/CHANGELOG.md @@ -1,9 +1,13 @@ # Release History -## 1.2.0-beta.1 (Unreleased) +## 1.2.0-beta.1 (2021-04-06) +### New Features + +- New `SecretReferenceConfigurationSetting` and `FeatureFlagConfigurationSetting`types to represent configuration settings that references KeyVault Secret reference and feature flag respectively. + [#14342](https://github.com/Azure/azure-sdk-for-js/pull/14342) - Added `updateSyncToken` method to `AppConfigurationClient` to be able to provide external synchronization tokens. - [PR #14507](https://github.com/Azure/azure-sdk-for-js/pull/14507) + [#14507](https://github.com/Azure/azure-sdk-for-js/pull/14507) ## 1.1.1 (2021-03-25) diff --git a/sdk/appconfiguration/app-configuration/README.md b/sdk/appconfiguration/app-configuration/README.md index e488970ef812..abad9d0d5be8 100644 --- a/sdk/appconfiguration/app-configuration/README.md +++ b/sdk/appconfiguration/app-configuration/README.md @@ -159,6 +159,8 @@ The following samples show you the various ways you can interact with App Config - [`setReadOnlySample.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/setReadOnlySample.ts) - Marking settings as read-only to prevent modification. - [`getSettingOnlyIfChanged.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/getSettingOnlyIfChanged.ts) - Get a setting only if it changed from the last time you got it. - [`listRevisions.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/listRevisions.ts) - List the revisions of a key, allowing you to see previous values and when they were set. +- [`secretReference.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts) - SecretReference represents a configuration setting that references as KeyVault secret. +- [`featureFlag.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts) - Feature flags are settings that follow specific JSON schema for the value. More in-depth examples can be found in the [samples](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/v1/) folder on GitHub. diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index 9483613a295b..371f755f9a5b 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -2,7 +2,7 @@ "name": "@azure/app-configuration", "author": "Microsoft Corporation", "description": "An isomorphic client library for the Azure App Configuration service.", - "version": "1.1.1", + "version": "1.2.0-beta.1", "sdk-type": "client", "keywords": [ "node", @@ -96,6 +96,7 @@ "@azure/dev-tool": "^1.0.0", "@azure/eslint-plugin-azure-sdk": "^3.0.0", "@azure/identity": "^1.1.0", + "@azure/keyvault-secrets": "^4.2.0-beta.4", "@azure/test-utils-recorder": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_get_featureflag.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_get_featureflag.js new file mode 100644 index 000000000000..ce51de218009 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_get_featureflag.js @@ -0,0 +1,107 @@ +let nock = require('nock'); + +module.exports.hash = "0f3d1c1e4498c8bfdfe2a40257d26862"; + +module.exports.testInfo = {"uniqueName":{"name-1":"name-1161736249716600236"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249716600236', {"key":".appconfig.featureflag/name-1161736249716600236","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249716600236\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}"}) + .query(true) + .reply(200, {"etag":"L715LplZhnVkTcair5pIUmLYbrb","key":".appconfig.featureflag/name-1161736249716600236","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249716600236\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:37+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'ETag', + '"L715LplZhnVkTcair5pIUmLYbrb"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTQ=;sn=3036054', + 'x-ms-request-id', + '532e0b89-15a8-401d-8cb3-15b407a102f7', + 'x-ms-correlation-request-id', + '532e0b89-15a8-401d-8cb3-15b407a102f7', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/.appconfig.featureflag%2Fname-1161736249716600236') + .query(true) + .reply(200, {"etag":"L715LplZhnVkTcair5pIUmLYbrb","key":".appconfig.featureflag/name-1161736249716600236","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249716600236\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:37+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'ETag', + '"L715LplZhnVkTcair5pIUmLYbrb"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTQ=;sn=3036054', + 'x-ms-request-id', + 'cb0ff52f-e454-49b4-9cb5-48bd8d0336c0', + 'x-ms-correlation-request-id', + 'cb0ff52f-e454-49b4-9cb5-48bd8d0336c0', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/.appconfig.featureflag%2Fname-1161736249716600236') + .query(true) + .reply(200, {"etag":"L715LplZhnVkTcair5pIUmLYbrb","key":".appconfig.featureflag/name-1161736249716600236","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249716600236\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:37+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'ETag', + '"L715LplZhnVkTcair5pIUmLYbrb"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTU=;sn=3036055', + 'x-ms-request-id', + '202dd25d-3be6-4b00-acd4-68bd4483d710', + 'x-ms-correlation-request-id', + '202dd25d-3be6-4b00-acd4-68bd4483d710', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_update_featureflag.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_update_featureflag.js new file mode 100644 index 000000000000..0590d90b408d --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_and_update_featureflag.js @@ -0,0 +1,175 @@ +let nock = require('nock'); + +module.exports.hash = "3c8d196cecc4465637a5e1cf232b3075"; + +module.exports.testInfo = {"uniqueName":{"name-1":"name-1161736249818502403"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249818502403', {"key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}"}) + .query(true) + .reply(200, {"etag":"8we5v58XC63IpZHp58O2eL3jOju","key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:38+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:37 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'ETag', + '"8we5v58XC63IpZHp58O2eL3jOju"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTY=;sn=3036056', + 'x-ms-request-id', + 'd53850c6-cf46-4324-8646-82f218463eae', + 'x-ms-correlation-request-id', + 'd53850c6-cf46-4324-8646-82f218463eae', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/.appconfig.featureflag%2Fname-1161736249818502403') + .query(true) + .reply(200, {"etag":"8we5v58XC63IpZHp58O2eL3jOju","key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:38+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'ETag', + '"8we5v58XC63IpZHp58O2eL3jOju"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTY=;sn=3036056', + 'x-ms-request-id', + '98015151-7f1e-478f-bc74-52f8405b2d31', + 'x-ms-correlation-request-id', + '98015151-7f1e-478f-bc74-52f8405b2d31', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249818502403', {"key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"etag":"8we5v58XC63IpZHp58O2eL3jOju"}) + .query(true) + .reply(200, {"etag":"Ku6xNQEd3zTg0wSuhj3gmz1e72i","key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:38+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'ETag', + '"Ku6xNQEd3zTg0wSuhj3gmz1e72i"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTc=;sn=3036057', + 'x-ms-request-id', + 'a455a51b-61a3-4a26-a651-a9228e299c30', + 'x-ms-correlation-request-id', + 'a455a51b-61a3-4a26-a651-a9228e299c30', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/.appconfig.featureflag%2Fname-1161736249818502403') + .query(true) + .reply(200, {"etag":"Ku6xNQEd3zTg0wSuhj3gmz1e72i","key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:38+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:39 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'ETag', + '"Ku6xNQEd3zTg0wSuhj3gmz1e72i"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTc=;sn=3036057', + 'x-ms-request-id', + '191f5a74-27a8-4bdf-828a-494cdcaf7513', + 'x-ms-correlation-request-id', + '191f5a74-27a8-4bdf-828a-494cdcaf7513', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/.appconfig.featureflag%2Fname-1161736249818502403') + .query(true) + .reply(200, {"etag":"Ku6xNQEd3zTg0wSuhj3gmz1e72i","key":".appconfig.featureflag/name-1161736249818502403","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249818502403\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:38+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:38 GMT', + 'ETag', + '"Ku6xNQEd3zTg0wSuhj3gmz1e72i"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTg=;sn=3036058', + 'x-ms-request-id', + '631dd3e8-1d08-45fc-9ede-660491627951', + 'x-ms-correlation-request-id', + '631dd3e8-1d08-45fc-9ede-660491627951', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_list_and_update_multiple_featureflags.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_list_and_update_multiple_featureflags.js new file mode 100644 index 000000000000..38772819ca63 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__featureflag_featureflag_configuration_setting/recording_can_add_list_and_update_multiple_featureflags.js @@ -0,0 +1,261 @@ +let nock = require('nock'); + +module.exports.hash = "0cc988c098bc8745cc267bc5c3792b4a"; + +module.exports.testInfo = {"uniqueName":{"name-1":"name-1161736249956207002"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249956207002', {"key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}"}) + .query(true) + .reply(200, {"etag":"YzAo7BoElnaZyBpV7qQTmHF6yAL","key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:39+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:39 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:39 GMT', + 'ETag', + '"YzAo7BoElnaZyBpV7qQTmHF6yAL"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNTk=;sn=3036059', + 'x-ms-request-id', + '78a8d14e-50d8-4236-8bf2-6e84e1309fe6', + 'x-ms-correlation-request-id', + '78a8d14e-50d8-4236-8bf2-6e84e1309fe6', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249956207002-2', {"key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}"}) + .query(true) + .reply(200, {"etag":"h7SXoBgaTaRLWZ7bvuIbwcl5OcZ","key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:39+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:39 GMT', + 'ETag', + '"h7SXoBgaTaRLWZ7bvuIbwcl5OcZ"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjA=;sn=3036060', + 'x-ms-request-id', + '2cbf20e5-12ee-4b11-b50b-07ef6b0b1f79', + 'x-ms-correlation-request-id', + '2cbf20e5-12ee-4b11-b50b-07ef6b0b1f79', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv') + .query(true) + .reply(200, {"items":[{"etag":"YzAo7BoElnaZyBpV7qQTmHF6yAL","key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:39+00:00"},{"etag":"h7SXoBgaTaRLWZ7bvuIbwcl5OcZ","key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm a description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:39+00:00"}]}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:39 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kvset+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjA=;sn=3036060', + 'x-ms-request-id', + 'dfc4296a-5baa-4850-9ddb-6989f1df1f15', + 'x-ms-correlation-request-id', + 'dfc4296a-5baa-4850-9ddb-6989f1df1f15', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249956207002', {"key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}"}) + .query(true) + .reply(200, {"etag":"YOzTU1fuA9vA5EBTNPsM1sKe0DA","key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:40+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'ETag', + '"YOzTU1fuA9vA5EBTNPsM1sKe0DA"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjE=;sn=3036061', + 'x-ms-request-id', + '0cf64fa0-67a2-4300-bfc3-321fd139b693', + 'x-ms-correlation-request-id', + '0cf64fa0-67a2-4300-bfc3-321fd139b693', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/.appconfig.featureflag%2Fname-1161736249956207002-2', {"key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm new description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"etag":"h7SXoBgaTaRLWZ7bvuIbwcl5OcZ"}) + .query(true) + .reply(200, {"etag":"no786ahiSoamdZXNcws7U5ttPOc","key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm new description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:40+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'ETag', + '"no786ahiSoamdZXNcws7U5ttPOc"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjI=;sn=3036062', + 'x-ms-request-id', + 'e695682e-dd93-49cf-812d-ab5cdef12c54', + 'x-ms-correlation-request-id', + 'e695682e-dd93-49cf-812d-ab5cdef12c54', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv') + .query(true) + .reply(200, {"items":[{"etag":"YOzTU1fuA9vA5EBTNPsM1sKe0DA","key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:40+00:00"},{"etag":"no786ahiSoamdZXNcws7U5ttPOc","key":".appconfig.featureflag/name-1161736249956207002-2","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002-2\",\"description\":\"I'm new description\",\"enabled\":false,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:40+00:00"}]}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kvset+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjI=;sn=3036062', + 'x-ms-request-id', + '8ca384a5-c490-4e69-a145-712d63f403e1', + 'x-ms-correlation-request-id', + '8ca384a5-c490-4e69-a145-712d63f403e1', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/.appconfig.featureflag%2Fname-1161736249956207002-2') + .query(true) + .reply(204, "", [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Connection', + 'close', + 'x-ms-request-id', + '54c5dc4c-4e3e-45e8-9db0-4f4113906bad', + 'x-ms-correlation-request-id', + '54c5dc4c-4e3e-45e8-9db0-4f4113906bad', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/.appconfig.featureflag%2Fname-1161736249956207002') + .query(true) + .reply(200, {"etag":"YOzTU1fuA9vA5EBTNPsM1sKe0DA","key":".appconfig.featureflag/name-1161736249956207002","label":"label-1","content_type":"application/vnd.microsoft.appconfig.ff+json;charset=utf-8","value":"{\"id\":\"name-1161736249956207002\",\"description\":\"I'm a description\",\"enabled\":true,\"conditions\":{\"client_filters\":[{\"name\":\"Microsoft.TimeWindow\",\"parameters\":{\"Start\":\"Wed, 01 May 2019 13:59:59 GMT\",\"End\":\"Mon, 01 July 2019 00:00:00 GMT\"}},{\"name\":\"FilterX\"},{\"name\":\"Microsoft.Targeting\",\"parameters\":{\"Audience\":{\"Groups\":[{\"Name\":\"group-1\",\"RolloutPercentage\":25},{\"Name\":\"group-2\",\"RolloutPercentage\":45}],\"Users\":[\"userA\",\"userB\"],\"DefaultRolloutPercentage\":40}}},{\"name\":\"Microsoft.Percentage\",\"parameters\":{\"Value\":25}}]}}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:40+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:40 GMT', + 'ETag', + '"YOzTU1fuA9vA5EBTNPsM1sKe0DA"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjM=;sn=3036063', + 'x-ms-request-id', + '77b1a36f-1ad3-4ad7-b18d-f613b2fa6df4', + 'x-ms-correlation-request-id', + '77b1a36f-1ad3-4ad7-b18d-f613b2fa6df4', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_get_secretreference.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_get_secretreference.js new file mode 100644 index 000000000000..539648991e20 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_get_secretreference.js @@ -0,0 +1,99 @@ +let nock = require('nock'); + +module.exports.hash = "505c9a3a224a00ff0b9a9c06eefae9e4"; + +module.exports.testInfo = {"uniqueName":{"name-2":"name-2161736250170700590","name-3":"name-3161736250170707837"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250170707837', {"key":"name-3161736250170707837","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250170700590\"}"}) + .query(true) + .reply(200, {"etag":"wpeiKUlqzHn9DLVqMgvjq33XULc","key":"name-3161736250170707837","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250170700590\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:41+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'ETag', + '"wpeiKUlqzHn9DLVqMgvjq33XULc"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjQ=;sn=3036064', + 'x-ms-request-id', + 'fe55b3c8-d6b5-438a-8d68-8fbc30f41358', + 'x-ms-correlation-request-id', + 'fe55b3c8-d6b5-438a-8d68-8fbc30f41358', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/name-3161736250170707837') + .query(true) + .reply(200, {"etag":"wpeiKUlqzHn9DLVqMgvjq33XULc","key":"name-3161736250170707837","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250170700590\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:41+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:41 GMT', + 'ETag', + '"wpeiKUlqzHn9DLVqMgvjq33XULc"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjQ=;sn=3036064', + 'x-ms-request-id', + '86de76bd-ce5c-46ea-bd40-1041c468815f', + 'x-ms-correlation-request-id', + '86de76bd-ce5c-46ea-bd40-1041c468815f', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/name-3161736250170707837') + .query(true) + .reply(204, "", [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Connection', + 'close', + 'x-ms-request-id', + 'ed499149-036a-4f00-9b3d-03fefae15bcb', + 'x-ms-correlation-request-id', + 'ed499149-036a-4f00-9b3d-03fefae15bcb', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_update_secretreference.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_update_secretreference.js new file mode 100644 index 000000000000..80cad4d1bf62 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_and_update_secretreference.js @@ -0,0 +1,167 @@ +let nock = require('nock'); + +module.exports.hash = "a9957c7b9cfbfcbbecbe8e53ed0fb850"; + +module.exports.testInfo = {"uniqueName":{"name-2":"name-2161736250258906310","name-3":"name-3161736250258904150","name-4":"name-4161736250310202956"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250258904150', {"key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250258906310\"}"}) + .query(true) + .reply(200, {"etag":"SoYpTkSGhqAsCyxYoISyPDqIU6z","key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250258906310\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:42+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'ETag', + '"SoYpTkSGhqAsCyxYoISyPDqIU6z"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjU=;sn=3036065', + 'x-ms-request-id', + '9c406cb9-2a8f-4d92-a3c1-0849ec71adb1', + 'x-ms-correlation-request-id', + '9c406cb9-2a8f-4d92-a3c1-0849ec71adb1', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/name-3161736250258904150') + .query(true) + .reply(200, {"etag":"SoYpTkSGhqAsCyxYoISyPDqIU6z","key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250258906310\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:42+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:43 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'ETag', + '"SoYpTkSGhqAsCyxYoISyPDqIU6z"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjU=;sn=3036065', + 'x-ms-request-id', + '722436bc-c235-4319-b382-16f10958f49d', + 'x-ms-correlation-request-id', + '722436bc-c235-4319-b382-16f10958f49d', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250258904150', {"key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-4161736250310202956\"}","tags":{},"etag":"SoYpTkSGhqAsCyxYoISyPDqIU6z"}) + .query(true) + .reply(200, {"etag":"o9IWxpqj3axCY3cbOS5NUOODX9g","key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-4161736250310202956\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:42+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'ETag', + '"o9IWxpqj3axCY3cbOS5NUOODX9g"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjY=;sn=3036066', + 'x-ms-request-id', + 'b5ca320b-fab4-44d4-81bd-418f88e4b2fd', + 'x-ms-correlation-request-id', + 'b5ca320b-fab4-44d4-81bd-418f88e4b2fd', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv/name-3161736250258904150') + .query(true) + .reply(200, {"etag":"o9IWxpqj3axCY3cbOS5NUOODX9g","key":"name-3161736250258904150","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-4161736250310202956\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:42+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:43 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:42 GMT', + 'ETag', + '"o9IWxpqj3axCY3cbOS5NUOODX9g"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjY=;sn=3036066', + 'x-ms-request-id', + '79664871-8f36-4967-b984-9040b1e9dfe5', + 'x-ms-correlation-request-id', + '79664871-8f36-4967-b984-9040b1e9dfe5', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/name-3161736250258904150') + .query(true) + .reply(204, "", [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:43 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Connection', + 'close', + 'x-ms-request-id', + '8ca78901-1834-4236-8d1d-a8aaf32f1282', + 'x-ms-correlation-request-id', + '8ca78901-1834-4236-8d1d-a8aaf32f1282', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_list_and_update_multiple_secretreferences.js b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_list_and_update_multiple_secretreferences.js new file mode 100644 index 000000000000..eaccd61788e4 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/recordings/node/appconfigurationclient__secretreference_secretreference_configuration_setting/recording_can_add_list_and_update_multiple_secretreferences.js @@ -0,0 +1,253 @@ +let nock = require('nock'); + +module.exports.hash = "6d49fc86f35574cb53bce8021e5755f4"; + +module.exports.testInfo = {"uniqueName":{"name-2":"name-2161736250391906013","name-3":"name-3161736250391900706","name-5":"name-5161736250422907435"},"newDate":{}} + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250391900706', {"key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}"}) + .query(true) + .reply(200, {"etag":"oHhTHIwOX8S2SqoiGdkvyl8mpAt","key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:43+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:44 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:43 GMT', + 'ETag', + '"oHhTHIwOX8S2SqoiGdkvyl8mpAt"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjc=;sn=3036067', + 'x-ms-request-id', + '9335a747-8814-479e-aa92-92d2d08b0ece', + 'x-ms-correlation-request-id', + '9335a747-8814-479e-aa92-92d2d08b0ece', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250391900706-2', {"key":"name-3161736250391900706-2","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}"}) + .query(true) + .reply(200, {"etag":"PR20Km4VgoXYQ6tap64aZ9ZvgvV","key":"name-3161736250391900706-2","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:44+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:43 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:44 GMT', + 'ETag', + '"PR20Km4VgoXYQ6tap64aZ9ZvgvV"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjg=;sn=3036068', + 'x-ms-request-id', + '2d5c435d-4fc5-4171-a4ed-a61f5b28ab0f', + 'x-ms-correlation-request-id', + '2d5c435d-4fc5-4171-a4ed-a61f5b28ab0f', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv') + .query(true) + .reply(200, {"items":[{"etag":"oHhTHIwOX8S2SqoiGdkvyl8mpAt","key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:43+00:00"},{"etag":"PR20Km4VgoXYQ6tap64aZ9ZvgvV","key":"name-3161736250391900706-2","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:44+00:00"}]}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:44 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kvset+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjg=;sn=3036068', + 'x-ms-request-id', + '65b35dc2-f741-402a-8492-5312ceb4209d', + 'x-ms-correlation-request-id', + '65b35dc2-f741-402a-8492-5312ceb4209d', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/kv/name-3161736250391900706', {"key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-5161736250422907435\"}"}) + .query(true) + .reply(200, {"etag":"aU5a3tU2BQ6FOX2F4XMsjfjUEWT","key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-5161736250422907435\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:44+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:44 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:44 GMT', + 'ETag', + '"aU5a3tU2BQ6FOX2F4XMsjfjUEWT"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNjk=;sn=3036069', + 'x-ms-request-id', + '74f3434b-25a8-4b0f-b77a-ea0fd06da33f', + 'x-ms-correlation-request-id', + '74f3434b-25a8-4b0f-b77a-ea0fd06da33f', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .put('/locks/name-3161736250391900706-2') + .query(true) + .reply(200, {"etag":"utHqq8e86dQhoef4XjvhgRiz24P","key":"name-3161736250391900706-2","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":true,"last_modified":"2021-04-02T11:21:46+00:00"}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:46 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Last-Modified', + 'Fri, 02 Apr 2021 11:21:46 GMT', + 'ETag', + '"utHqq8e86dQhoef4XjvhgRiz24P"', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNzA=;sn=3036070', + 'x-ms-request-id', + 'f9b99f38-1c28-4e00-b16b-6fd4022dedae', + 'x-ms-correlation-request-id', + 'f9b99f38-1c28-4e00-b16b-6fd4022dedae', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .get('/kv') + .query(true) + .reply(200, {"items":[{"etag":"aU5a3tU2BQ6FOX2F4XMsjfjUEWT","key":"name-3161736250391900706","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-5161736250422907435\"}","tags":{},"locked":false,"last_modified":"2021-04-02T11:21:44+00:00"},{"etag":"utHqq8e86dQhoef4XjvhgRiz24P","key":"name-3161736250391900706-2","label":"label-s","content_type":"application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8","value":"{\"uri\":\"https://vault_name.vault.azure.net/secrets/name-2161736250391906013\"}","tags":{},"locked":true,"last_modified":"2021-04-02T11:21:46+00:00"}]}, [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:46 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kvset+json; charset=utf-8', + 'Transfer-Encoding', + 'chunked', + 'Connection', + 'close', + 'Sync-Token', + 'zAJw6V16=NTo1IzMwMzYwNzA=;sn=3036070', + 'x-ms-request-id', + 'cdbb73d9-4529-4775-b6ac-e7cc149bded8', + 'x-ms-correlation-request-id', + 'cdbb73d9-4529-4775-b6ac-e7cc149bded8', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/name-3161736250391900706-2') + .query(true) + .reply(204, "", [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:47 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Connection', + 'close', + 'x-ms-request-id', + 'caf4721a-da0c-4df6-964a-2bd024935eed', + 'x-ms-correlation-request-id', + 'caf4721a-da0c-4df6-964a-2bd024935eed', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); + +nock('https://myappconfig.azconfig.io:443', {"encodedQueryParams":true}) + .delete('/kv/name-3161736250391900706') + .query(true) + .reply(204, "", [ + 'Server', + 'openresty/1.17.8.2', + 'Date', + 'Fri, 02 Apr 2021 11:21:46 GMT', + 'Content-Type', + 'application/vnd.microsoft.appconfig.kv+json; charset=utf-8', + 'Connection', + 'close', + 'x-ms-request-id', + 'd13d6103-9496-439f-ad4c-f252717874bf', + 'x-ms-correlation-request-id', + 'd13d6103-9496-439f-ad4c-f252717874bf', + 'Access-Control-Allow-Origin', + '*', + 'Access-Control-Allow-Credentials', + 'true', + 'Access-Control-Expose-Headers', + 'DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, x-ms-effective-locale, WWW-Authenticate', + 'Strict-Transport-Security', + 'max-age=15724800; includeSubDomains' +]); diff --git a/sdk/appconfiguration/app-configuration/review/app-configuration.api.md b/sdk/appconfiguration/app-configuration/review/app-configuration.api.md index 8ff2c801fee2..7c392d7f68f3 100644 --- a/sdk/appconfiguration/app-configuration/review/app-configuration.api.md +++ b/sdk/appconfiguration/app-configuration/review/app-configuration.api.md @@ -74,6 +74,60 @@ export interface DeleteConfigurationSettingOptions extends HttpOnlyIfUnchangedFi export interface DeleteConfigurationSettingResponse extends SyncTokenHeaderField, HttpResponseFields, HttpResponseField { } +// @public +export interface FeatureFlag extends FeatureFlagParam, ConfigurationSetting { +} + +// @public +export const featureFlagContentType = "application/vnd.microsoft.appconfig.ff+json;charset=utf-8"; + +// @public +export interface FeatureFlagParam extends ConfigurationSettingParam { + conditions: { + clientFilters: (FeatureFlagTargetingClientFilter | FeatureFlagTimeWindowClientFilter | FeatureFlagPercentageClientFilter | Record)[]; + }; + description?: string; + enabled: boolean; +} + +// @public +export interface FeatureFlagPercentageClientFilter { + name: "Microsoft.Percentage"; + parameters: { + value: number; + }; +} + +// @public +export const featureFlagPrefix = ".appconfig.featureflag/"; + +// @public +export interface FeatureFlagTargetingClientFilter { + name: "Microsoft.Targeting"; + parameters: { + audience: { + users: string[]; + groups: { + name: string; + rolloutPercentage: number; + }[]; + defaultRolloutPercentage: number; + }; + }; +} + +// @public +export interface FeatureFlagTimeWindowClientFilter { + name: "Microsoft.TimeWindow"; + parameters: { + start: string; + end: string; + }; +} + +// @public +export type FeatureFlagType = T extends "targeting" ? FeatureFlagTargetingClientFilter : T extends "timeWindow" ? FeatureFlagTimeWindowClientFilter : T extends "percentage" ? FeatureFlagPercentageClientFilter : never; + // @public export interface GetConfigurationHeaders extends SyncTokenHeaderField { } @@ -110,6 +164,15 @@ export interface HttpResponseFields { statusCode: number; } +// @public +export function isFeatureFlag(setting: ConfigurationSetting | FeatureFlag): setting is FeatureFlag; + +// @public +export function isFeatureFlagClientFilter(type: T, obj: unknown): obj is FeatureFlagType; + +// @public +export function isSecretReference(setting: ConfigurationSetting): setting is SecretReference; + // @public export interface ListConfigurationSettingPage extends HttpResponseField { items: ConfigurationSetting[]; @@ -140,6 +203,18 @@ export interface OptionalFields { fields?: (keyof ConfigurationSetting)[]; } +// @public +export interface SecretReference extends SecretReferenceParam, ConfigurationSetting { +} + +// @public +export const secretReferenceContentType = "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8"; + +// @public +export interface SecretReferenceParam extends ConfigurationSettingParam { + secretId: string; +} + // @public export interface SetConfigurationSettingOptions extends HttpOnlyIfUnchangedField, OperationOptions { } diff --git a/sdk/appconfiguration/app-configuration/sample.env b/sdk/appconfiguration/app-configuration/sample.env index db027248c47f..b6206adee5a4 100644 --- a/sdk/appconfiguration/app-configuration/sample.env +++ b/sdk/appconfiguration/app-configuration/sample.env @@ -1,3 +1,16 @@ # This is the connection string for your app-configuration resource # You can get this from the Azure portal. APPCONFIG_CONNECTION_STRING= + +# The following is only required for the secretReference sample +# The name of the key vault to use in the samples. +# Create a Key Vault in the Azure Portal and enter its URI (e.g. https://mytest.vault.azure.net/) here. +KEYVAULT_URI= + +# Used to authenticate using Azure AD as a service principal for role-based authentication. +# +# See the documentation for `EnvironmentCredential` at the following link: +# https://docs.microsoft.com/javascript/api/@azure/identity/environmentcredential +AZURE_TENANT_ID= +AZURE_CLIENT_ID= +AZURE_CLIENT_SECRET= diff --git a/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts new file mode 100644 index 000000000000..2d94b071859e --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples-dev/featureFlag.ts @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary Feature flags are settings that follow specific JSON schema for the value. + * + * @azsdk-weight 20 + */ +import { + AppConfigurationClient, + FeatureFlag, + featureFlagContentType, + featureFlagPrefix, + isFeatureFlag, + isFeatureFlagClientFilter +} from "@azure/app-configuration"; + +// Load the .env file if it exists +import * as dotenv from "dotenv"; +dotenv.config(); + +export async function main() { + console.log(`Running featureFlag sample`); + + const featureFlag: FeatureFlag = { + conditions: { + clientFilters: [ + { + name: "Microsoft.TimeWindow", + parameters: { + start: "Wed, 01 May 2021 13:59:59 GMT", + end: "Mon, 01 July 2022 00:00:00 GMT" + } + }, + { name: "FilterX" }, + { + name: "Microsoft.Targeting", + parameters: { + audience: { + groups: [ + { name: "group-1", rolloutPercentage: 25 }, + { name: "group-2", rolloutPercentage: 45 } + ], + users: ["userA", "userB"], + defaultRolloutPercentage: 40 + } + } + }, + { name: "Microsoft.Percentage", parameters: { value: 25 } } + ] + }, + enabled: false, + isReadOnly: false, + key: `${featureFlagPrefix}new-feature-flag-${Math.ceil(100 + Math.random() * 900)}`, + contentType: featureFlagContentType, + description: "I'm a description" + }; + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([featureFlag.key], appConfigClient); + + console.log(`Add a new featureFlag with key: ${featureFlag.key}`); + await appConfigClient.addConfigurationSetting(featureFlag); + + console.log(`Get the added featureFlag with key: ${featureFlag.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + + if (isFeatureFlag(getResponse)) { + // isFeatureFlag() check for type inference to narrow down the configuration setting as a FeatureFlag + // setting is a `FeatureFlag` + const conditions = getResponse.conditions; + + // the client filters are the real meat of the FeatureFlag. + // + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + // some of the fields: + // clientFilter.parameters.audience + // clientFilter.parameters.audience.groups[0].name + // clientFilter.parameters.audience.groups[0].rolloutPercentage + // clientFilter.parameters.audience.users[0] // string + // clientFilter.parameters.audience.defaultRolloutPercentage + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + clientFilter.parameters.audience.defaultRolloutPercentage = 85; + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + // clientFilter.parameters.end; + // clientFilter.parameters.start; + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + clientFilter.parameters.start = "Wed, 01 June 2021 13:59:59 GMT"; + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + clientFilter.parameters.value = 56; + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + clientFilter.name = "FilterY"; + } + } + } + + console.log(`===> Update the featureFlag`); + await appConfigClient.setConfigurationSetting(getResponse); + const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + console.log(`Get the updated featureFlag with key: ${featureFlag.key}`); + + if (isFeatureFlag(getResponseAfterUpdate)) { + const conditions = getResponseAfterUpdate.conditions; + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + } + } + } + await cleanupSampleValues([featureFlag.key], appConfigClient); +} + +async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name: string) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts new file mode 100644 index 000000000000..481a87fd3371 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples-dev/secretReference.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary SecretReference represents a configuration setting that references as KeyVault secret. + * + * @azsdk-weight 30 + */ +import { + AppConfigurationClient, + isSecretReference, + SecretReference, + secretReferenceContentType +} from "@azure/app-configuration"; +import { SecretClient } from "@azure/keyvault-secrets"; +import { DefaultAzureCredential } from "@azure/identity"; + +// Load the .env file if it exists +import * as dotenv from "dotenv"; +dotenv.config(); + +export async function main() { + console.log(`Running secretReference sample`); + const secretReference: SecretReference = { + key: `secret${new Date().getTime()}`, + secretId: `secret-key${Math.ceil(100 + Math.random() * 900)}`, + isReadOnly: false, + contentType: secretReferenceContentType + }; + + if ( + !process.env["AZURE_TENANT_ID"] || + !process.env["AZURE_CLIENT_ID"] || + !process.env["AZURE_CLIENT_SECRET"] || + !process.env["KEYVAULT_URI"] + ) { + console.log( + `At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and KEYVAULT_URI variables is not present, + please add the missing ones in your environment and rerun the sample.` + ); + return; + } + // DefaultAzureCredential expects the following three environment variables: + // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory + // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant + // - AZURE_CLIENT_SECRET: The client secret for the registered application + const credential = new DefaultAzureCredential(); + const url = process.env["KEYVAULT_URI"] || ""; + + const secretClient = new SecretClient(url, credential); + // Create a secret + console.log( + `Create a keyvault secret with key: ${secretReference.secretId} and value: "MySecretValue"` + ); + await secretClient.setSecret(secretReference.secretId, "MySecretValue"); + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([secretReference.key], appConfigClient); + + console.log( + `Add a new secretReference with key: ${secretReference.key} and secretId: ${secretReference.secretId}` + ); + await appConfigClient.addConfigurationSetting(secretReference); + + console.log(`Get the added secretReference from App Config with key: ${secretReference.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: secretReference.key + }); + + if (isSecretReference(getResponse)) { + // isSecretReference() check for type inference to narrow down the configuration setting as a SecretReference + // setting is a `SecretReference` + // Read the secret we created + const secret = await secretClient.getSecret(getResponse.secretId); + console.log(`Get the secret from keyvault key: ${secret.name}, value: ${secret.value}`); + + console.log(`Deleting the secret from keyvault`); + await secretClient.beginDeleteSecret(getResponse.secretId); + } + + await cleanupSampleValues([secretReference.key], appConfigClient); +} + +async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name: string) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/README.md b/sdk/appconfiguration/app-configuration/samples/v1/javascript/README.md index 92e166b27ee7..634431a5ef08 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/README.md +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/README.md @@ -21,6 +21,8 @@ These sample programs show how to use the JavaScript client libraries for Azure | [getSettingOnlyIfChanged.js][getsettingonlyifchanged] | Demonstrates getting a setting only if it has changed from what you already have. (This allows your app to avoid downloading the contents of a setting if the value is unchanged.) | | [listConfigurationSettings.js][listconfigurationsettings] | Demonstrates listing multiple configuration settings using a filter for a key or label. | | [listRevisions.js][listrevisions] | Demonstrates listing revisions for a configuration setting. | +| [secretReference.js][secretreference] | SecretReference represents a configuration setting that references as KeyVault secret. | +| [featureFlag.js][featureflag] | Feature flags are settings that follow specific JSON schema for the value. | ## Prerequisites @@ -69,6 +71,8 @@ Take a look at our [API Documentation][apiref] for more information about the AP [getsettingonlyifchanged]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/javascript/getSettingOnlyIfChanged.js [listconfigurationsettings]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/javascript/listConfigurationSettings.js [listrevisions]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/javascript/listRevisions.js +[secretreference]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js +[featureflag]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js [apiref]: https://docs.microsoft.com/javascript/api/@azure/app-configuration [freesub]: https://azure.microsoft.com/free/ [createinstance_azureappconfigurationaccount]: https://docs.microsoft.com/azure/azure-app-configuration/quickstart-aspnet-core-app?tabs=core5x#create-an-app-configuration-store diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js new file mode 100644 index 000000000000..16babd7b7b75 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/featureFlag.js @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary Feature flags are settings that follow specific JSON schema for the value. + * + */ +const { + AppConfigurationClient, + featureFlagContentType, + featureFlagPrefix, + isFeatureFlag, + isFeatureFlagClientFilter +} = require("@azure/app-configuration"); + +// Load the .env file if it exists +const dotenv = require("dotenv"); +dotenv.config(); + +async function main() { + console.log(`Running featureFlag sample`); + + const featureFlag = { + conditions: { + clientFilters: [ + { + name: "Microsoft.TimeWindow", + parameters: { + start: "Wed, 01 May 2021 13:59:59 GMT", + end: "Mon, 01 July 2022 00:00:00 GMT" + } + }, + { name: "FilterX" }, + { + name: "Microsoft.Targeting", + parameters: { + audience: { + groups: [ + { name: "group-1", rolloutPercentage: 25 }, + { name: "group-2", rolloutPercentage: 45 } + ], + users: ["userA", "userB"], + defaultRolloutPercentage: 40 + } + } + }, + { name: "Microsoft.Percentage", parameters: { value: 25 } } + ] + }, + enabled: false, + isReadOnly: false, + key: `${featureFlagPrefix}new-feature-flag-${Math.ceil(100 + Math.random() * 900)}`, + contentType: featureFlagContentType, + description: "I'm a description" + }; + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([featureFlag.key], appConfigClient); + + console.log(`Add a new featureFlag with key: ${featureFlag.key}`); + await appConfigClient.addConfigurationSetting(featureFlag); + + console.log(`Get the added featureFlag with key: ${featureFlag.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + + if (isFeatureFlag(getResponse)) { + // isFeatureFlag() check for type inference to narrow down the configuration setting as a FeatureFlag + // setting is a `FeatureFlag` + const conditions = getResponse.conditions; + + // the client filters are the real meat of the FeatureFlag. + // + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + // some of the fields: + // clientFilter.parameters.audience + // clientFilter.parameters.audience.groups[0].name + // clientFilter.parameters.audience.groups[0].rolloutPercentage + // clientFilter.parameters.audience.users[0] // string + // clientFilter.parameters.audience.defaultRolloutPercentage + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + clientFilter.parameters.audience.defaultRolloutPercentage = 85; + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + // clientFilter.parameters.end; + // clientFilter.parameters.start; + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + clientFilter.parameters.start = "Wed, 01 June 2021 13:59:59 GMT"; + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + clientFilter.parameters.value = 56; + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + clientFilter.name = "FilterY"; + } + } + } + + console.log(`===> Update the featureFlag`); + await appConfigClient.setConfigurationSetting(getResponse); + const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + console.log(`Get the updated featureFlag with key: ${featureFlag.key}`); + + if (isFeatureFlag(getResponseAfterUpdate)) { + const conditions = getResponseAfterUpdate.conditions; + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + } + } + } + await cleanupSampleValues([featureFlag.key], appConfigClient); +} + +async function cleanupSampleValues(keys, client) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json b/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json index e39a6bb13b34..b962c6f89b07 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/package.json @@ -26,7 +26,9 @@ }, "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration", "dependencies": { - "@azure/app-configuration": "latest", - "dotenv": "latest" + "@azure/app-configuration": "next", + "dotenv": "latest", + "@azure/keyvault-secrets": "^4.2.0-beta.4", + "@azure/identity": "^1.1.0" } } diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/sample.env b/sdk/appconfiguration/app-configuration/samples/v1/javascript/sample.env index db027248c47f..b6206adee5a4 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/javascript/sample.env +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/sample.env @@ -1,3 +1,16 @@ # This is the connection string for your app-configuration resource # You can get this from the Azure portal. APPCONFIG_CONNECTION_STRING= + +# The following is only required for the secretReference sample +# The name of the key vault to use in the samples. +# Create a Key Vault in the Azure Portal and enter its URI (e.g. https://mytest.vault.azure.net/) here. +KEYVAULT_URI= + +# Used to authenticate using Azure AD as a service principal for role-based authentication. +# +# See the documentation for `EnvironmentCredential` at the following link: +# https://docs.microsoft.com/javascript/api/@azure/identity/environmentcredential +AZURE_TENANT_ID= +AZURE_CLIENT_ID= +AZURE_CLIENT_SECRET= diff --git a/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js b/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js new file mode 100644 index 000000000000..23bfc533157f --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples/v1/javascript/secretReference.js @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary SecretReference represents a configuration setting that references as KeyVault secret. + * + */ +const { + AppConfigurationClient, + isSecretReference, + secretReferenceContentType +} = require("@azure/app-configuration"); +const { SecretClient } = require("@azure/keyvault-secrets"); +const { DefaultAzureCredential } = require("@azure/identity"); + +// Load the .env file if it exists +const dotenv = require("dotenv"); +dotenv.config(); + +async function main() { + console.log(`Running secretReference sample`); + const secretReference = { + key: `secret${new Date().getTime()}`, + secretId: `secret-key${Math.ceil(100 + Math.random() * 900)}`, + isReadOnly: false, + contentType: secretReferenceContentType + }; + + if ( + !process.env["AZURE_TENANT_ID"] || + !process.env["AZURE_CLIENT_ID"] || + !process.env["AZURE_CLIENT_SECRET"] || + !process.env["KEYVAULT_URI"] + ) { + console.log(`At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and KEYVAULT_URI variables is not present, + please add the missing ones in your environment and rerun the sample.`); + return; + } + // DefaultAzureCredential expects the following three environment variables: + // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory + // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant + // - AZURE_CLIENT_SECRET: The client secret for the registered application + const credential = new DefaultAzureCredential(); + const url = process.env["KEYVAULT_URI"] || ""; + + const secretClient = new SecretClient(url, credential); + // Create a secret + console.log( + `Create a keyvault secret with key: ${secretReference.secretId} and value: "MySecretValue"` + ); + await secretClient.setSecret(secretReference.secretId, "MySecretValue"); + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([secretReference.key], appConfigClient); + + console.log( + `Add a new secretReference with key: ${secretReference.key} and secretId: ${secretReference.secretId}` + ); + await appConfigClient.addConfigurationSetting(secretReference); + + console.log(`Get the added secretReference from App Config with key: ${secretReference.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: secretReference.key + }); + + if (isSecretReference(getResponse)) { + // isSecretReference() check for type inference to narrow down the configuration setting as a SecretReference + // setting is a `SecretReference` + // Read the secret we created + const secret = await secretClient.getSecret(getResponse.secretId); + console.log(`Get the secret from keyvault key: ${secret.name}, value: ${secret.value}`); + + console.log(`Deleting the secret from keyvault`); + await secretClient.beginDeleteSecret(getResponse.secretId); + } + + await cleanupSampleValues([secretReference.key], appConfigClient); +} + +async function cleanupSampleValues(keys, client) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/README.md b/sdk/appconfiguration/app-configuration/samples/v1/typescript/README.md index a87eca8e1987..d92780083530 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/README.md +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/README.md @@ -21,6 +21,8 @@ These sample programs show how to use the TypeScript client libraries for Azure | [getSettingOnlyIfChanged.ts][getsettingonlyifchanged] | Demonstrates getting a setting only if it has changed from what you already have. (This allows your app to avoid downloading the contents of a setting if the value is unchanged.) | | [listConfigurationSettings.ts][listconfigurationsettings] | Demonstrates listing multiple configuration settings using a filter for a key or label. | | [listRevisions.ts][listrevisions] | Demonstrates listing revisions for a configuration setting. | +| [secretReference.ts][secretreference] | SecretReference represents a configuration setting that references as KeyVault secret. | +| [featureFlag.ts][featureflag] | Feature flags are settings that follow specific JSON schema for the value. | ## Prerequisites @@ -81,6 +83,8 @@ Take a look at our [API Documentation][apiref] for more information about the AP [getsettingonlyifchanged]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/getSettingOnlyIfChanged.ts [listconfigurationsettings]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/listConfigurationSettings.ts [listrevisions]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/listRevisions.ts +[secretreference]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts +[featureflag]: https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts [apiref]: https://docs.microsoft.com/javascript/api/@azure/app-configuration [freesub]: https://azure.microsoft.com/free/ [createinstance_azureappconfigurationaccount]: https://docs.microsoft.com/azure/azure-app-configuration/quickstart-aspnet-core-app?tabs=core5x#create-an-app-configuration-store diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json b/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json index 479e8d330209..8699931e59b8 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/package.json @@ -30,8 +30,10 @@ }, "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration", "dependencies": { - "@azure/app-configuration": "latest", - "dotenv": "latest" + "@azure/app-configuration": "next", + "dotenv": "latest", + "@azure/keyvault-secrets": "^4.2.0-beta.4", + "@azure/identity": "^1.1.0" }, "devDependencies": { "typescript": "~4.2.0", diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/sample.env b/sdk/appconfiguration/app-configuration/samples/v1/typescript/sample.env index db027248c47f..b6206adee5a4 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/sample.env +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/sample.env @@ -1,3 +1,16 @@ # This is the connection string for your app-configuration resource # You can get this from the Azure portal. APPCONFIG_CONNECTION_STRING= + +# The following is only required for the secretReference sample +# The name of the key vault to use in the samples. +# Create a Key Vault in the Azure Portal and enter its URI (e.g. https://mytest.vault.azure.net/) here. +KEYVAULT_URI= + +# Used to authenticate using Azure AD as a service principal for role-based authentication. +# +# See the documentation for `EnvironmentCredential` at the following link: +# https://docs.microsoft.com/javascript/api/@azure/identity/environmentcredential +AZURE_TENANT_ID= +AZURE_CLIENT_ID= +AZURE_CLIENT_SECRET= diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts new file mode 100644 index 000000000000..2d94b071859e --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/featureFlag.ts @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary Feature flags are settings that follow specific JSON schema for the value. + * + * @azsdk-weight 20 + */ +import { + AppConfigurationClient, + FeatureFlag, + featureFlagContentType, + featureFlagPrefix, + isFeatureFlag, + isFeatureFlagClientFilter +} from "@azure/app-configuration"; + +// Load the .env file if it exists +import * as dotenv from "dotenv"; +dotenv.config(); + +export async function main() { + console.log(`Running featureFlag sample`); + + const featureFlag: FeatureFlag = { + conditions: { + clientFilters: [ + { + name: "Microsoft.TimeWindow", + parameters: { + start: "Wed, 01 May 2021 13:59:59 GMT", + end: "Mon, 01 July 2022 00:00:00 GMT" + } + }, + { name: "FilterX" }, + { + name: "Microsoft.Targeting", + parameters: { + audience: { + groups: [ + { name: "group-1", rolloutPercentage: 25 }, + { name: "group-2", rolloutPercentage: 45 } + ], + users: ["userA", "userB"], + defaultRolloutPercentage: 40 + } + } + }, + { name: "Microsoft.Percentage", parameters: { value: 25 } } + ] + }, + enabled: false, + isReadOnly: false, + key: `${featureFlagPrefix}new-feature-flag-${Math.ceil(100 + Math.random() * 900)}`, + contentType: featureFlagContentType, + description: "I'm a description" + }; + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([featureFlag.key], appConfigClient); + + console.log(`Add a new featureFlag with key: ${featureFlag.key}`); + await appConfigClient.addConfigurationSetting(featureFlag); + + console.log(`Get the added featureFlag with key: ${featureFlag.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + + if (isFeatureFlag(getResponse)) { + // isFeatureFlag() check for type inference to narrow down the configuration setting as a FeatureFlag + // setting is a `FeatureFlag` + const conditions = getResponse.conditions; + + // the client filters are the real meat of the FeatureFlag. + // + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + // some of the fields: + // clientFilter.parameters.audience + // clientFilter.parameters.audience.groups[0].name + // clientFilter.parameters.audience.groups[0].rolloutPercentage + // clientFilter.parameters.audience.users[0] // string + // clientFilter.parameters.audience.defaultRolloutPercentage + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + clientFilter.parameters.audience.defaultRolloutPercentage = 85; + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + // clientFilter.parameters.end; + // clientFilter.parameters.start; + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + clientFilter.parameters.start = "Wed, 01 June 2021 13:59:59 GMT"; + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + clientFilter.parameters.value = 56; + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + clientFilter.name = "FilterY"; + } + } + } + + console.log(`===> Update the featureFlag`); + await appConfigClient.setConfigurationSetting(getResponse); + const getResponseAfterUpdate = await appConfigClient.getConfigurationSetting({ + key: featureFlag.key + }); + console.log(`Get the updated featureFlag with key: ${featureFlag.key}`); + + if (isFeatureFlag(getResponseAfterUpdate)) { + const conditions = getResponseAfterUpdate.conditions; + for (const clientFilter of conditions.clientFilters) { + if (isFeatureFlagClientFilter("targeting", clientFilter)) { + console.log( + ` targeting feature flag client filter => name: ${clientFilter.name}, defaultRolloutPercentage: ${clientFilter.parameters.audience.defaultRolloutPercentage}` + ); + } else if (isFeatureFlagClientFilter("timeWindow", clientFilter)) { + console.log( + ` timeWindow feature flag client filter => name: ${clientFilter.name}, start time: ${clientFilter.parameters.start}` + ); + } else if (isFeatureFlagClientFilter("percentage", clientFilter)) { + console.log( + ` percentage feature flag client filter => name: ${clientFilter.name}, value: ${clientFilter.parameters.value}` + ); + } else { + console.log( + ` name of the custom feature flag client filter => name : ${clientFilter.name}` + ); + } + } + } + await cleanupSampleValues([featureFlag.key], appConfigClient); +} + +async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name: string) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts new file mode 100644 index 000000000000..481a87fd3371 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/src/secretReference.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @summary SecretReference represents a configuration setting that references as KeyVault secret. + * + * @azsdk-weight 30 + */ +import { + AppConfigurationClient, + isSecretReference, + SecretReference, + secretReferenceContentType +} from "@azure/app-configuration"; +import { SecretClient } from "@azure/keyvault-secrets"; +import { DefaultAzureCredential } from "@azure/identity"; + +// Load the .env file if it exists +import * as dotenv from "dotenv"; +dotenv.config(); + +export async function main() { + console.log(`Running secretReference sample`); + const secretReference: SecretReference = { + key: `secret${new Date().getTime()}`, + secretId: `secret-key${Math.ceil(100 + Math.random() * 900)}`, + isReadOnly: false, + contentType: secretReferenceContentType + }; + + if ( + !process.env["AZURE_TENANT_ID"] || + !process.env["AZURE_CLIENT_ID"] || + !process.env["AZURE_CLIENT_SECRET"] || + !process.env["KEYVAULT_URI"] + ) { + console.log( + `At least one of the AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and KEYVAULT_URI variables is not present, + please add the missing ones in your environment and rerun the sample.` + ); + return; + } + // DefaultAzureCredential expects the following three environment variables: + // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory + // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant + // - AZURE_CLIENT_SECRET: The client secret for the registered application + const credential = new DefaultAzureCredential(); + const url = process.env["KEYVAULT_URI"] || ""; + + const secretClient = new SecretClient(url, credential); + // Create a secret + console.log( + `Create a keyvault secret with key: ${secretReference.secretId} and value: "MySecretValue"` + ); + await secretClient.setSecret(secretReference.secretId, "MySecretValue"); + + // Set the following environment variable or edit the value on the following line. + const connectionString = process.env["APPCONFIG_CONNECTION_STRING"] || ""; + const appConfigClient = new AppConfigurationClient(connectionString); + + await cleanupSampleValues([secretReference.key], appConfigClient); + + console.log( + `Add a new secretReference with key: ${secretReference.key} and secretId: ${secretReference.secretId}` + ); + await appConfigClient.addConfigurationSetting(secretReference); + + console.log(`Get the added secretReference from App Config with key: ${secretReference.key}`); + const getResponse = await appConfigClient.getConfigurationSetting({ + key: secretReference.key + }); + + if (isSecretReference(getResponse)) { + // isSecretReference() check for type inference to narrow down the configuration setting as a SecretReference + // setting is a `SecretReference` + // Read the secret we created + const secret = await secretClient.getSecret(getResponse.secretId); + console.log(`Get the secret from keyvault key: ${secret.name}, value: ${secret.value}`); + + console.log(`Deleting the secret from keyvault`); + await secretClient.beginDeleteSecret(getResponse.secretId); + } + + await cleanupSampleValues([secretReference.key], appConfigClient); +} + +async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) { + const settingsIterator = client.listConfigurationSettings({ + keyFilter: keys.join(",") + }); + + for await (const setting of settingsIterator) { + await client.deleteConfigurationSetting({ key: setting.key, label: setting.label }); + } +} + +/** + * Returns the environment variable, throws an error if not defined. + * + * @export + * @param {string} name + */ +export function getEnvVar(name: string) { + const val = process.env[name]; + if (!val) { + throw `Environment variable ${name} is not defined.`; + } + return val; +} + +main().catch((err) => { + console.error("Failed to run sample:", err); + process.exit(1); +}); diff --git a/sdk/appconfiguration/app-configuration/samples/v1/typescript/tsconfig.json b/sdk/appconfiguration/app-configuration/samples/v1/typescript/tsconfig.json index 4e7be7e1a850..27b4e24c6a0f 100644 --- a/sdk/appconfiguration/app-configuration/samples/v1/typescript/tsconfig.json +++ b/sdk/appconfiguration/app-configuration/samples/v1/typescript/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES6", + "target": "ES2018", "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, diff --git a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts index b66392178f95..515daf84d8d7 100644 --- a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts +++ b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts @@ -50,7 +50,8 @@ import { transformKeyValueResponseWithStatusCode, transformKeyValue, formatAcceptDateTime, - formatFieldsForSelect + formatFieldsForSelect, + serializeAsConfigurationSettingParam } from "./internal/helpers"; import { tracingPolicy } from "@azure/core-http"; import { trace as traceFromTracingHelpers } from "./internal/tracingHelpers"; @@ -67,7 +68,7 @@ const packageName = "azsdk-js-app-configuration"; * User - Agent header. There's a unit test that makes sure it always stays in sync. * @internal */ -export const packageVersion = "1.1.1"; +export const packageVersion = "1.2.0-beta.1"; const apiVersion = "1.0"; const ConnectionStringRegex = /Endpoint=(.*);Id=(.*);Secret=(.*)/; const deserializationContentTypes = { @@ -191,10 +192,11 @@ export class AppConfigurationClient { options: AddConfigurationSettingOptions = {} ): Promise { return this._trace("addConfigurationSetting", options, async (newOptions) => { + const keyValue = serializeAsConfigurationSettingParam(configurationSetting); const originalResponse = await this.client.putKeyValue(configurationSetting.key, { ifNoneMatch: "*", label: configurationSetting.label, - entity: configurationSetting, + entity: keyValue, ...newOptions }); @@ -457,10 +459,11 @@ export class AppConfigurationClient { options: SetConfigurationSettingOptions = {} ): Promise { return this._trace("setConfigurationSetting", options, async (newOptions) => { + const keyValue = serializeAsConfigurationSettingParam(configurationSetting); const response = await this.client.putKeyValue(configurationSetting.key, { ...newOptions, label: configurationSetting.label, - entity: configurationSetting, + entity: keyValue, ...checkAndFormatIfAndIfNoneMatch(configurationSetting, options) }); diff --git a/sdk/appconfiguration/app-configuration/src/featureFlag.ts b/sdk/appconfiguration/app-configuration/src/featureFlag.ts new file mode 100644 index 000000000000..02c42bfcdac0 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/featureFlag.ts @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + isJsonFeatureFlagPercentageClientFilter, + isJsonFeatureFlagTargetingClientFilter, + isJsonFeatureFlagTimeWindowClientFilter, + JsonFeatureFlag, + JsonFeatureFlagPercentageClientFilter, + JsonFeatureFlagTargetingClientFilter, + JsonFeatureFlagTimeWindowClientFilter +} from "./internal/jsonModels"; +import { isObjectWithProperties } from "./internal/typeguards"; +import { ConfigurationSetting, ConfigurationSettingParam } from "./models"; + +/** + * The prefix for feature flags. + */ +export const featureFlagPrefix = ".appconfig.featureflag/"; + +/** + * The content type for a FeatureFlag + */ +export const featureFlagContentType = "application/vnd.microsoft.appconfig.ff+json;charset=utf-8"; + +/** + * Necessary fields for updating or creating a new feature flag. + */ +export interface FeatureFlagParam extends ConfigurationSettingParam { + /** + * A Feature filter consistently evaluates the state of a feature flag. + * Our feature management library supports three types of built-in filters: Targeting, TimeWindow, and Percentage. + * Custom filters can also be created based on different factors, such as device used, browser types, geographic location, etc. + * + * [More Info](https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-feature-filters-aspnet-core) + */ + conditions: { + clientFilters: ( + | FeatureFlagTargetingClientFilter + | FeatureFlagTimeWindowClientFilter + | FeatureFlagPercentageClientFilter + | Record + )[]; + }; + /** + * Description of the feature. + */ + description?: string; + /** + * Boolean flag to say if the feature flag is enabled. + */ + enabled: boolean; +} + +/** + * FeatureFlag represents a configuration setting that stores a feature flag value. + * + * [More Info](https://docs.microsoft.com/azure/azure-app-configuration/concept-feature-management) + */ +export interface FeatureFlag extends FeatureFlagParam, ConfigurationSetting {} + +/** + * Targeting Client filter for the feature flag configuration setting. + * + * [More Info](https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-feature-filters-aspnet-core) + */ +export interface FeatureFlagTargetingClientFilter { + /** + * The name of the feature filter. + */ + name: "Microsoft.Targeting"; + /** + * Parameters that can be passed in the filter. + */ + parameters: { + audience: { + users: string[]; + groups: { + name: string; + rolloutPercentage: number; + }[]; + defaultRolloutPercentage: number; + }; + }; +} + +/** + * Time window Client filter for the feature flag configuration setting. + * + * [More Info](https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-feature-filters-aspnet-core) + */ +export interface FeatureFlagTimeWindowClientFilter { + /** + * The name of the feature filter. + */ + name: "Microsoft.TimeWindow"; + /** + * Parameters that can be passed in the filter. + */ + parameters: { + /** + * Start time of the time window. + * Expected UTCString - Example: "Wed, 01 May 2021 13:59:59 GMT" + * + * UTCString can be obtained from `new Date().toUTCString()`. + * Use `Date.parse()` to parse the UTCString as Date. + */ + start: string; + /** + * End time of the time window. + * Expected UTCString - Example: "Wed, 05 May 2021 13:59:59 GMT" + * + * UTCString can be obtained from `new Date().toUTCString()`. + * Use `Date.parse()` to parse the UTCString as Date. + */ + end: string; + }; +} + +/** + * Percentage Client filter for the feature flag configuration setting. + * + * [More Info](https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-feature-filters-aspnet-core) + */ +export interface FeatureFlagPercentageClientFilter { + /** + * The name of the feature filter. + */ + name: "Microsoft.Percentage"; + /** + * Parameters that can be passed in the filter. + * Value is expected to be from 0 to 100. SDK doesn't validate the value. + */ + parameters: { + value: number; + }; +} + +/** + * FeatureFlag represents a configuration setting that stores a feature flag value. + * [More Info](https://docs.microsoft.com/azure/azure-app-configuration/concept-feature-management) + * + * This helper method tells you if the given setting is a feature flag. + */ +export function isFeatureFlag(setting: ConfigurationSetting | FeatureFlag): setting is FeatureFlag { + return setting.contentType === featureFlagContentType; +} + +/** + * This helper method tells you if the given client filter has the name "Microsoft.Targeting". + */ +function isFeatureFlagTargetingClientFilter( + clientFilter: unknown +): clientFilter is FeatureFlagTargetingClientFilter { + return ( + isObjectWithProperties(clientFilter, ["name"]) && clientFilter.name === "Microsoft.Targeting" + ); +} + +/** + * This helper method tells you if the given client filter has the name "Microsoft.TimeWindow". + */ +function isFeatureFlagTimeWindowClientFilter( + clientFilter: unknown +): clientFilter is FeatureFlagTimeWindowClientFilter { + return ( + isObjectWithProperties(clientFilter, ["name"]) && clientFilter.name === "Microsoft.TimeWindow" + ); +} + +/** + * This helper method tells you if the given client filter has the name "Microsoft.Percentage". + */ +function isFeatureFlagPercentageClientFilter( + clientFilter: unknown +): clientFilter is FeatureFlagPercentageClientFilter { + return ( + isObjectWithProperties(clientFilter, ["name"]) && clientFilter.name === "Microsoft.Percentage" + ); +} + +/** + * Type of FeatureFlag based on the client filters. + */ +export type FeatureFlagType< + T extends "targeting" | "timeWindow" | "percentage" +> = T extends "targeting" + ? FeatureFlagTargetingClientFilter + : T extends "timeWindow" + ? FeatureFlagTimeWindowClientFilter + : T extends "percentage" + ? FeatureFlagPercentageClientFilter + : never; + +/** + * This helper method tells you if the given client filter is a FeatureFlagClientFilter. + */ +export function isFeatureFlagClientFilter( + type: T, + obj: unknown +): obj is FeatureFlagType { + switch (type) { + case "targeting": + return isFeatureFlagTargetingClientFilter(obj); + case "timeWindow": + return isFeatureFlagTimeWindowClientFilter(obj); + case "percentage": + return isFeatureFlagPercentageClientFilter(obj); + default: + return false; + } +} + +/** + * @internal + */ +export function deserializeFeatureFlag(setting: ConfigurationSetting): FeatureFlag | undefined { + if (!isFeatureFlag(setting) || !setting.value) { + return undefined; + } + + let jsonFeatureFlag: JsonFeatureFlag; + + try { + jsonFeatureFlag = JSON.parse(setting.value) as JsonFeatureFlag; + } catch (err) { + // best effort - if it doesn't deserialize properly we'll just let it "degrade" + // to be treated as a ConfigurationSetting. + return undefined; + } + + // update (in-place) the feature flag specific properties + const featureFlag = setting as Omit; + featureFlag.conditions = convertJsonConditions(jsonFeatureFlag.conditions); + featureFlag.enabled = Boolean(jsonFeatureFlag.enabled); + featureFlag.description = jsonFeatureFlag.description; + + return setting; +} + +/** + * @internal + */ +export function serializeFeatureFlagParam(setting: FeatureFlagParam): ConfigurationSettingParam { + const value: JsonFeatureFlag & { id: string } = { + id: setting.key.replace(featureFlagPrefix, ""), + description: setting.description, + enabled: setting.enabled, + conditions: convertToJsonConditions(setting.conditions) + }; + const configurationSetting: ConfigurationSettingParam = { + key: setting.key, + label: setting.label, + contentType: setting.contentType, + etag: setting.etag, + tags: setting.tags, + value: JSON.stringify(value) + }; + return configurationSetting; +} + +/** + * @internal + */ +export function convertJsonConditions( + conditions: JsonFeatureFlag["conditions"] +): FeatureFlag["conditions"] { + const clientFilters = !conditions.client_filters + ? [] + : conditions.client_filters.map((jsonFilter) => { + if (isJsonFeatureFlagTargetingClientFilter(jsonFilter)) { + const filter: FeatureFlagTargetingClientFilter = { + name: jsonFilter.name, + parameters: { + audience: { + groups: + jsonFilter.parameters.Audience.Groups.map((grp) => ({ + name: grp.Name, + rolloutPercentage: grp.RolloutPercentage + })) || [], + users: jsonFilter.parameters.Audience.Users || [], + defaultRolloutPercentage: jsonFilter.parameters.Audience.DefaultRolloutPercentage + } + } + }; + + return filter; + } else if (isJsonFeatureFlagTimeWindowClientFilter(jsonFilter)) { + const filter: FeatureFlagTimeWindowClientFilter = { + name: jsonFilter.name, + parameters: { + start: jsonFilter.parameters.Start, + end: jsonFilter.parameters.End + } + }; + + return filter; + } else if (isJsonFeatureFlagPercentageClientFilter(jsonFilter)) { + const filter: FeatureFlagPercentageClientFilter = { + name: jsonFilter.name, + parameters: { + value: jsonFilter.parameters.Value + } + }; + return filter; + } else { + return jsonFilter; + } + }); + + return { + clientFilters + }; +} + +/** + * @internal + */ +export function convertToJsonConditions( + conditions: FeatureFlag["conditions"] +): JsonFeatureFlag["conditions"] { + const client_filters = conditions.clientFilters.map((filter) => { + if (isFeatureFlagTargetingClientFilter(filter)) { + const jsonFilter: JsonFeatureFlagTargetingClientFilter = { + name: filter.name, + parameters: { + Audience: { + Groups: + filter.parameters.audience?.groups.map((grp) => ({ + Name: grp.name, + RolloutPercentage: grp.rolloutPercentage + })) || [], + Users: filter.parameters.audience?.users || [], + DefaultRolloutPercentage: filter.parameters.audience.defaultRolloutPercentage + } + } + }; + + return jsonFilter; + } else if (isFeatureFlagTimeWindowClientFilter(filter)) { + const jsonFilter: JsonFeatureFlagTimeWindowClientFilter = { + name: filter.name, + parameters: { + Start: filter.parameters.start, + End: filter.parameters.end + } + }; + + return jsonFilter; + } else if (isFeatureFlagPercentageClientFilter(filter)) { + const jsonFilter: JsonFeatureFlagPercentageClientFilter = { + name: filter.name, + parameters: { + Value: filter.parameters.value + } + }; + return jsonFilter; + } else { + return filter; + } + }); + + return { + client_filters + }; +} diff --git a/sdk/appconfiguration/app-configuration/src/generated/src/appConfigurationContext.ts b/sdk/appconfiguration/app-configuration/src/generated/src/appConfigurationContext.ts index 2dcac2c5c22d..aa81e115b834 100644 --- a/sdk/appconfiguration/app-configuration/src/generated/src/appConfigurationContext.ts +++ b/sdk/appconfiguration/app-configuration/src/generated/src/appConfigurationContext.ts @@ -10,7 +10,7 @@ import * as coreHttp from "@azure/core-http"; import { ApiVersion10, AppConfigurationOptionalParams } from "./models"; const packageName = "app-configuration"; -const packageVersion = "1.1.1"; +const packageVersion = "1.2.0-beta.1"; /** @internal */ export class AppConfigurationContext extends coreHttp.ServiceClient { diff --git a/sdk/appconfiguration/app-configuration/src/index.ts b/sdk/appconfiguration/app-configuration/src/index.ts index f865b791cd6c..86044a0a51a2 100644 --- a/sdk/appconfiguration/app-configuration/src/index.ts +++ b/sdk/appconfiguration/app-configuration/src/index.ts @@ -1,5 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +export { AppConfigurationClient, AppConfigurationClientOptions } from "./appConfigurationClient"; +export { + FeatureFlag, + featureFlagContentType, + FeatureFlagParam, + FeatureFlagPercentageClientFilter, + featureFlagPrefix, + FeatureFlagTargetingClientFilter, + FeatureFlagTimeWindowClientFilter, + FeatureFlagType, + isFeatureFlag, + isFeatureFlagClientFilter +} from "./featureFlag"; +export { + isSecretReference, + SecretReference, + secretReferenceContentType, + SecretReferenceParam +} from "./keyvaultReference"; export * from "./models"; -export { AppConfigurationClientOptions, AppConfigurationClient } from "./appConfigurationClient"; diff --git a/sdk/appconfiguration/app-configuration/src/internal/helpers.ts b/sdk/appconfiguration/app-configuration/src/internal/helpers.ts index 9c7f1f1fe575..7a79c18a461c 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/helpers.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/helpers.ts @@ -10,9 +10,24 @@ import { HttpResponseField, HttpResponseFields, HttpOnlyIfChangedField, - HttpOnlyIfUnchangedField + HttpOnlyIfUnchangedField, + ConfigurationSettingParam } from "../models"; import { AppConfigurationGetKeyValuesOptionalParams, KeyValue } from "../generated/src/models"; +import { + deserializeFeatureFlag, + FeatureFlag, + featureFlagContentType, + FeatureFlagParam, + serializeFeatureFlagParam +} from "../featureFlag"; +import { + deserializeSecretReference, + SecretReference, + secretReferenceContentType, + SecretReferenceParam, + serializeSecretReferenceParam +} from "../keyvaultReference"; /** * Formats the etag so it can be used with a If-Match/If-None-Match header @@ -150,13 +165,41 @@ export function makeConfigurationSettingEmpty( * @internal */ export function transformKeyValue(kvp: KeyValue): ConfigurationSetting { - const obj: ConfigurationSetting & KeyValue = { + const setting: ConfigurationSetting & KeyValue = { ...kvp, isReadOnly: !!kvp.locked }; - delete obj.locked; - return obj; + delete setting.locked; + + switch (setting.contentType) { + case featureFlagContentType: { + return deserializeFeatureFlag(setting) ?? setting; + } + case secretReferenceContentType: { + return deserializeSecretReference(setting) ?? setting; + } + default: + return setting; + } +} + +/** + * @internal + */ +export function serializeAsConfigurationSettingParam( + setting: FeatureFlagParam | SecretReferenceParam | ConfigurationSettingParam +): ConfigurationSettingParam { + switch (setting.contentType) { + case featureFlagContentType: { + return serializeFeatureFlagParam(setting as FeatureFlag); + } + case secretReferenceContentType: { + return serializeSecretReferenceParam(setting as SecretReference); + } + default: + return setting; + } } /** diff --git a/sdk/appconfiguration/app-configuration/src/internal/jsonModels.ts b/sdk/appconfiguration/app-configuration/src/internal/jsonModels.ts new file mode 100644 index 000000000000..4360c0833827 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/internal/jsonModels.ts @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * These interfaces map to the actual JSON representation that's needed when serializing to/from the string .value of a ConfigurationSetting. + */ + +/** + * @internal + */ +export type JsonFeatureFlag = { + conditions: { + client_filters: ( + | JsonFeatureFlagTargetingClientFilter + | JsonFeatureFlagTimeWindowClientFilter + | JsonFeatureFlagPercentageClientFilter + | Record + )[]; + }; + description?: string; + enabled: boolean; +}; + +/** + * @internal + */ +export interface JsonFeatureFlagTargetingClientFilter { + name: "Microsoft.Targeting"; + parameters: { + Audience: { + Users: string[]; + Groups: { + Name: string; + RolloutPercentage: number; + }[]; + DefaultRolloutPercentage: number; + }; + [key: string]: any; + }; +} + +/** + * @internal + */ +export interface JsonFeatureFlagTimeWindowClientFilter { + name: "Microsoft.TimeWindow"; + parameters: { + Start: string; + End: string; + [key: string]: any; + }; +} + +/** + * @internal + */ +export interface JsonFeatureFlagPercentageClientFilter { + name: "Microsoft.Percentage"; + parameters: { + Value: number; + [key: string]: any; + }; +} + +/** + * @internal + */ +export function isJsonFeatureFlagTargetingClientFilter( + clientFilter: any +): clientFilter is JsonFeatureFlagTargetingClientFilter { + return clientFilter.name === "Microsoft.Targeting"; +} + +/** + * @internal + */ +export function isJsonFeatureFlagTimeWindowClientFilter( + clientFilter: any +): clientFilter is JsonFeatureFlagTimeWindowClientFilter { + return clientFilter.name === "Microsoft.TimeWindow"; +} + +/** + * @internal + */ +export function isJsonFeatureFlagPercentageClientFilter( + clientFilter: any +): clientFilter is JsonFeatureFlagPercentageClientFilter { + return clientFilter.name === "Microsoft.Percentage"; +} + +// keyvault reference + +/** + * @internal + */ +export interface JsonKeyVaultReference { + uri: string; +} diff --git a/sdk/appconfiguration/app-configuration/src/internal/typeguards.ts b/sdk/appconfiguration/app-configuration/src/internal/typeguards.ts new file mode 100644 index 000000000000..f14b6c20d1f2 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/internal/typeguards.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * Helper TypeGuard that checks if something is defined or not. + * @param thing - Anything + * @internal + */ +export function isDefined(thing: T | undefined | null): thing is T { + return typeof thing !== "undefined" && thing !== null; +} + +/** + * Helper TypeGuard that checks if the input is an object with the specified properties. + * @param thing - Anything. + * @param properties - The name of the properties that should appear in the object. + * @internal + */ +export function isObjectWithProperties( + thing: Thing, + properties: PropertyName[] +): thing is Thing & Record { + if (!isDefined(thing) || typeof thing !== "object") { + return false; + } + + for (const property of properties) { + if (!objectHasProperty(thing, property)) { + return false; + } + } + + return true; +} + +/** + * Helper TypeGuard that checks if the input is an object with the specified property. + * @param thing - Any object. + * @param property - The name of the property that should appear in the object. + * @internal + */ +function objectHasProperty( + thing: Thing, + property: PropertyName +): thing is Thing & Record { + return typeof thing === "object" && property in (thing as Record); +} diff --git a/sdk/appconfiguration/app-configuration/src/keyvaultReference.ts b/sdk/appconfiguration/app-configuration/src/keyvaultReference.ts new file mode 100644 index 000000000000..a5a6f41e8f66 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/keyvaultReference.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { JsonKeyVaultReference } from "./internal/jsonModels"; +import { ConfigurationSetting, ConfigurationSettingParam } from "./models"; + +/** + * content-type for the secret reference. + */ +export const secretReferenceContentType = + "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8"; + +/** + * Necessary fields for updating or creating a new secret reference. + */ +export interface SecretReferenceParam extends ConfigurationSettingParam { + /** + * Id for the secret reference. + */ + secretId: string; +} + +/** + * SecretReference represents a configuration setting that references as KeyVault secret. + * + * Secret references have "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8" content-type. + */ +export interface SecretReference extends SecretReferenceParam, ConfigurationSetting {} + +/** + * This helper method tells you if the given setting is a SecretReference configuration setting. + */ +export function isSecretReference(setting: ConfigurationSetting): setting is SecretReference { + return setting.contentType === secretReferenceContentType; +} + +/** + * @internal + */ +export function deserializeSecretReference( + setting: ConfigurationSetting +): SecretReference | undefined { + if (!setting.value) { + return undefined; + } + + try { + const jsonKeyVaultRef = JSON.parse(setting.value) as JsonKeyVaultReference; + const keyVaultRef: SecretReference = { + ...setting, + secretId: jsonKeyVaultRef.uri + }; + + return keyVaultRef; + } catch (err) { + return undefined; + } +} + +/** + * @internal + */ +export function serializeSecretReferenceParam( + setting: SecretReferenceParam +): ConfigurationSettingParam { + const configurationSetting: ConfigurationSettingParam = { + key: setting.key, + label: setting.label, + contentType: setting.contentType, + etag: setting.etag, + tags: setting.tags, + value: JSON.stringify({ uri: setting.secretId }) + }; + return configurationSetting; +} diff --git a/sdk/appconfiguration/app-configuration/src/models.ts b/sdk/appconfiguration/app-configuration/src/models.ts index 3649b02a33be..b016e92ac577 100644 --- a/sdk/appconfiguration/app-configuration/src/models.ts +++ b/sdk/appconfiguration/app-configuration/src/models.ts @@ -8,7 +8,8 @@ import { OperationOptions, HttpResponse } from "@azure/core-http"; */ export interface ConfigurationSettingId { /** - * The key for this setting + * The key for this setting. + * Feature flags must be prefixed with `.appconfig.featureflag/`. */ key: string; diff --git a/sdk/appconfiguration/app-configuration/test/public/featureFlag.spec.ts b/sdk/appconfiguration/app-configuration/test/public/featureFlag.spec.ts new file mode 100644 index 000000000000..437695270533 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/test/public/featureFlag.spec.ts @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { assert } from "chai"; +import { createAppConfigurationClientForTests, startRecorder } from "./utils/testHelpers"; +import { + AddConfigurationSettingResponse, + AppConfigurationClient, + FeatureFlag, + featureFlagContentType, + FeatureFlagPercentageClientFilter, + featureFlagPrefix, + FeatureFlagTargetingClientFilter, + FeatureFlagTimeWindowClientFilter, + isFeatureFlag +} from "../../src"; +import { Recorder } from "@azure/test-utils-recorder"; +import { Context } from "mocha"; + +describe("AppConfigurationClient - FeatureFlag", () => { + let client: AppConfigurationClient; + let recorder: Recorder; + + beforeEach(function(this: Context) { + recorder = startRecorder(this); + client = createAppConfigurationClientForTests() || this.skip(); + }); + + afterEach(async function(this: Context) { + await recorder.stop(); + }); + + describe("FeatureFlag configuration setting", () => { + const clientFilters: ( + | Record + | FeatureFlagTargetingClientFilter + | FeatureFlagTimeWindowClientFilter + | FeatureFlagPercentageClientFilter + )[] = [ + { + name: "Microsoft.TimeWindow", + parameters: { + start: "Wed, 01 May 2019 13:59:59 GMT", + end: "Mon, 01 July 2019 00:00:00 GMT" + } + }, + { name: "FilterX" }, + { + name: "Microsoft.Targeting", + parameters: { + audience: { + groups: [ + { name: "group-1", rolloutPercentage: 25 }, + { name: "group-2", rolloutPercentage: 45 } + ], + users: ["userA", "userB"], + defaultRolloutPercentage: 40 + } + } + }, + { name: "Microsoft.Percentage", parameters: { value: 25 } } + ]; + + let baseSetting: FeatureFlag; + let addResponse: AddConfigurationSettingResponse; + + beforeEach(async () => { + baseSetting = { + conditions: { + clientFilters + }, + enabled: false, + isReadOnly: false, + key: `${featureFlagPrefix + recorder.getUniqueName("name-1")}`, + contentType: featureFlagContentType, + description: "I'm a description", + label: "label-1" + }; + addResponse = await client.addConfigurationSetting(baseSetting); + }); + + afterEach(async () => { + await client.deleteConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + }); + + function assertFeatureFlagProps( + actual: Omit, + expected: FeatureFlag + ) { + assert.equal(isFeatureFlag(actual), true, "Expected to get the feature flag"); + if (isFeatureFlag(actual)) { + assert.equal( + actual.key, + expected.key, + "Key from the response from get request is not as expected" + ); + assert.deepEqual( + actual.conditions, + expected.conditions, + "conditions from the response from get request is not as expected" + ); + assert.equal(actual.description, expected.description); + assert.equal(actual.enabled, expected.enabled); + assert.equal(actual.isReadOnly, expected.isReadOnly); + assert.equal(actual.label, expected.label); + assert.equal(actual.contentType, expected.contentType); + } + } + + it("can add and get FeatureFlag", async () => { + assertFeatureFlagProps(addResponse, baseSetting); + const getResponse = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + assertFeatureFlagProps(getResponse, baseSetting); + }); + + it("can add and update FeatureFlag", async () => { + const getResponse = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + assertFeatureFlagProps(getResponse, baseSetting); + if (isFeatureFlag(getResponse)) { + getResponse.enabled = !baseSetting.enabled; + } + + const setResponse = await client.setConfigurationSetting(getResponse); + assertFeatureFlagProps(setResponse, { + ...baseSetting, + enabled: !baseSetting.enabled + }); + + const getResponseAfterUpdate = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + assertFeatureFlagProps(getResponseAfterUpdate, { + ...baseSetting, + enabled: !baseSetting.enabled + }); + }); + + it("can add, list and update multiple FeatureFlags", async () => { + const secondSetting = { + ...baseSetting, + key: `${baseSetting.key}-2` + }; + await client.addConfigurationSetting(secondSetting); + + let numberOFFeatureFlagsReceived = 0; + for await (const setting of client.listConfigurationSettings({ + keyFilter: `${baseSetting.key}*` + })) { + numberOFFeatureFlagsReceived++; + if (setting.key === baseSetting.key) { + assertFeatureFlagProps(setting, baseSetting); + await client.setConfigurationSetting({ + ...baseSetting, + enabled: !baseSetting.enabled + } as FeatureFlag); + } else { + assertFeatureFlagProps(setting, secondSetting); + await client.setConfigurationSetting({ + ...setting, + description: "I'm new description" + } as FeatureFlag); + } + } + assert.equal(numberOFFeatureFlagsReceived, 2, "Unexpected number of FeatureFlags seen"); + + for await (const setting of client.listConfigurationSettings({ + keyFilter: `${baseSetting.key}*` + })) { + numberOFFeatureFlagsReceived--; + if (setting.key === baseSetting.key) { + assertFeatureFlagProps(setting, { ...baseSetting, enabled: !baseSetting.enabled }); + } else { + assertFeatureFlagProps(setting, { ...secondSetting, description: "I'm new description" }); + } + } + + assert.equal( + numberOFFeatureFlagsReceived, + 0, + "Unexpected number of FeatureFlags seen after updating" + ); + await client.deleteConfigurationSetting({ key: secondSetting.key }); + }); + }); +}); diff --git a/sdk/appconfiguration/app-configuration/test/public/secretReference.spec.ts b/sdk/appconfiguration/app-configuration/test/public/secretReference.spec.ts new file mode 100644 index 000000000000..c5e3e12b5223 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/test/public/secretReference.spec.ts @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { assert } from "chai"; +import { createAppConfigurationClientForTests, startRecorder } from "./utils/testHelpers"; +import { + AddConfigurationSettingResponse, + AppConfigurationClient, + isSecretReference, + SecretReference, + secretReferenceContentType +} from "../../src"; +import { Recorder } from "@azure/test-utils-recorder"; +import { Context } from "mocha"; + +describe("AppConfigurationClient - SecretReference", () => { + let client: AppConfigurationClient; + let recorder: Recorder; + + beforeEach(function(this: Context) { + recorder = startRecorder(this); + client = createAppConfigurationClientForTests() || this.skip(); + }); + + afterEach(async function(this: Context) { + await recorder.stop(); + }); + + describe("SecretReference configuration setting", () => { + const getBaseSetting = (): SecretReference => { + return { + secretId: `https://vault_name.vault.azure.net/secrets/${recorder.getUniqueName("name-2")}`, // TODO: It's a URL in .NET, should we leave it as a string input? + isReadOnly: false, + key: recorder.getUniqueName("name-3"), + label: "label-s", + contentType: secretReferenceContentType + }; + }; + + function assertSecretReferenceProps( + actual: Omit, + expected: SecretReference + ) { + assert.equal(isSecretReference(actual), true, "Expected to get the SecretReference"); + if (isSecretReference(actual)) { + assert.equal( + actual.key, + expected.key, + "Key from the response from get request is not as expected" + ); + assert.equal(actual.secretId, expected.secretId); + assert.equal(actual.isReadOnly, expected.isReadOnly); + assert.equal(actual.label, expected.label); + assert.equal(actual.contentType, expected.contentType); + } + } + + let addResponse: AddConfigurationSettingResponse; + let baseSetting: SecretReference; + beforeEach(async () => { + baseSetting = getBaseSetting(); + addResponse = await client.addConfigurationSetting(baseSetting); + }); + + afterEach(async () => { + await client.deleteConfigurationSetting({ + key: baseSetting.key + }); + }); + + it("can add and get SecretReference", async () => { + assertSecretReferenceProps(addResponse, baseSetting); + const getResponse = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + assertSecretReferenceProps(getResponse, baseSetting); + }); + + it("can add and update SecretReference", async () => { + const getResponse = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + const newSecretId = `https://vault_name.vault.azure.net/secrets/${recorder.getUniqueName( + "name-4" + )}`; + + assertSecretReferenceProps(getResponse, baseSetting); + if (isSecretReference(getResponse)) { + getResponse.secretId = newSecretId; + } + + const setResponse = await client.setConfigurationSetting(getResponse); + assertSecretReferenceProps(setResponse, { + ...baseSetting, + secretId: newSecretId + }); + + const getResponseAfterUpdate = await client.getConfigurationSetting({ + key: baseSetting.key, + label: baseSetting.label + }); + assertSecretReferenceProps(getResponseAfterUpdate, { + ...baseSetting, + secretId: newSecretId + }); + }); + + it("can add, list and update multiple SecretReferences", async () => { + const secondSetting = { + ...baseSetting, + key: `${baseSetting.key}-2` + }; + const newSecretId = `https://vault_name.vault.azure.net/secrets/${recorder.getUniqueName( + "name-5" + )}`; + await client.addConfigurationSetting(secondSetting); + + let numberOFSecretReferencesReceived = 0; + for await (const setting of client.listConfigurationSettings({ + keyFilter: `${baseSetting.key}*` + })) { + numberOFSecretReferencesReceived++; + if (setting.key === baseSetting.key) { + assertSecretReferenceProps(setting, baseSetting); + await client.setConfigurationSetting({ + ...baseSetting, + secretId: newSecretId + } as SecretReference); + } else { + assertSecretReferenceProps(setting, secondSetting); + await client.setReadOnly( + { key: setting.key, label: setting.label }, + !secondSetting.isReadOnly + ); + } + } + assert.equal(numberOFSecretReferencesReceived, 2, "Unexpected number of FeatureFlags seen"); + for await (const setting of client.listConfigurationSettings({ + keyFilter: `${baseSetting.key}*` + })) { + numberOFSecretReferencesReceived--; + if (setting.key === baseSetting.key) { + assertSecretReferenceProps(setting, { ...baseSetting, secretId: newSecretId }); + } else { + assertSecretReferenceProps(setting, { + ...secondSetting, + isReadOnly: !secondSetting.isReadOnly + }); + } + } + + assert.equal( + numberOFSecretReferencesReceived, + 0, + "Unexpected number of SecretReferences seen after updating" + ); + await client.deleteConfigurationSetting({ key: secondSetting.key }); + }); + }); +});