diff --git a/.docsettings.yml b/.docsettings.yml
index 8c9790f94e47..d5509adf2ccb 100644
--- a/.docsettings.yml
+++ b/.docsettings.yml
@@ -19,7 +19,8 @@ required_readme_sections:
known_content_issues:
- ["README.md", "#1583"]
- ["sdk/template/template/README.md", "#1583"]
- - ["sdk/applicationinsights/applicationinsights-query/README.md", "#1583"]
+ - ["sdk/appconfiguration/app-configuration/README.md", "#1583"]
+ - ["sdk/applicationinsights/applicationinsights-query/README.md", "#1583"]
- ["sdk/batch/batch/README.md", "#1583"]
- [
"sdk/cognitiveservices/cognitiveservices-anomalydetector/README.md",
diff --git a/sdk/appconfiguration/app-configuration/README.md b/sdk/appconfiguration/app-configuration/README.md
index 0a4e89bfd7c4..bbc96dccd247 100644
--- a/sdk/appconfiguration/app-configuration/README.md
+++ b/sdk/appconfiguration/app-configuration/README.md
@@ -1,115 +1,98 @@
# Azure App Configuration client library for JS
-This package contains an isomorphic SDK for ConfigurationClient.
+Azure App Configuration is a managed service that helps developers centralize their application configurations simply and securely.
+
+Modern programs, especially programs running in a cloud, generally have many components that are distributed in nature. Spreading configuration settings across these components can lead to hard-to-troubleshoot errors during an application deployment. Use App Configuration to securely store all the settings for your application in one place.
+
+Use the client library for App Configuration to:
+
+* Create centrally stored application configuration settings
+* Retrieve settings
+* Update settings
+* Delete settings
+
+[NPM](https://www.npmjs.com/package/@azure/app-configuration) | [Product documentation](https://docs.microsoft.com/en-us/azure/azure-app-configuration/)
## Getting started
### Currently supported environments
-- Node.js version 6.x.x or higher
-- Browser JavaScript
+- Node.js version 8.x.x or higher
### How to Install
```bash
-npm install @azure/app-config
+npm install @azure/app-configuration
```
+
## Key concepts
-### How to use
+### Configuration Setting
-#### nodejs - Authentication, client creation and listConfigurationSettings as an example written in TypeScript.
+A Configuration Setting is the fundamental resource within a Configuration Store.
+In its simplest form, it is a key and a value. However, there are additional properties such as
+the modifiable content type and tags fields that allows the value to be interpreted or associated
+in different ways.
-##### Install @azure/ms-rest-nodeauth
+The `label` property of a Configuration Setting provides a way to separate configuration settings
+into different dimensions. These dimensions are user defined and can take any form. Some common
+examples of dimensions to use for a label include regions, semantic versions, or environments.
+Many applications have a required set of configuration keys that have varying values as the
+application exists across different dimensions.
-```bash
-npm install @azure/ms-rest-nodeauth
-```
+For example, MaxRequests may be 100 in "NorthAmerica", and 200 in "WestEurope". By creating a
+Configuration Setting named MaxRequests with a label of "NorthAmerica" and another, only with
+a different value, in the "WestEurope" label, an application can seamlessly retrieve
+Configuration Settings as it runs in these two dimensions.
## Examples
+#### nodejs - Authentication, client creation and listConfigurationSettings as an example written in TypeScript.
+
##### Sample code
```typescript
-import * as coreHttp from "@azure/core-http";
-import * as coreArm from "@azure/core-arm";
-import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
-import { ConfigurationClient, ConfigurationModels, ConfigurationMappers } from "@azure/app-config";
-const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
-
-msRestNodeAuth.interactiveLogin().then((creds) => {
- const client = new ConfigurationClient(creds, subscriptionId);
- const label = ["testlabel"];
- const key = ["testkey"];
- const acceptDateTime = new Date().toISOString();
- const fields = ["etag"];
- client.listConfigurationSettings(label, key, acceptDateTime, fields).then((result) => {
- console.log("The result is:");
- console.log(result);
- });
-}).catch((err) => {
- console.error(err);
-});
-```
+import { AppConfigurationClient } from "@azure/app-configuration";
-#### browser - Authentication, client creation and listConfigurationSettings as an example written in JavaScript.
+const connectionString = process.env["AZ_CONFIG_CONNECTION"]!;
+const client = new AppConfigurationClient(connectionString);
-##### Install @azure/ms-rest-browserauth
+let configurationSetting = await client.getConfigurationSetting("testkey");
-```bash
-npm install @azure/ms-rest-browserauth
+console.log("The result is:");
+console.log(configurationSetting.value);
```
-##### Sample code
+More examples can be found in the samples folder on [github](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples)
-See https://github.com/Azure/ms-rest-browserauth to learn how to authenticate to Azure in the browser.
-
-- index.html
-```html
-
-
-
- @azure/app-configuration sample
-
-
-
-
-
-
-
-
-```
+## Next steps
-## Troubleshooting
+Explore the samples to understand how to work with Azure App Configuration.
-## Next steps
+* [`helloworld.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/helloworld.ts) - getting, setting and deleting configuration values
+* [`helloworldWithLabels.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/helloworldWithLabels.ts) - using labels to add additional dimensions to your settings
+* [`helloworldWithETag.ts`](https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/appconfiguration/app-configuration/samples/helloworldWithETag.ts) - setting values using etags to prevent accidental overwrites
## Contributing
+This project welcomes contributions and suggestions. Most contributions require you to agree to a
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
+the rights to use your contribution. For details, visit
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
+a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
+provided by the bot. You will only need to do this once across all repos using our CLA.
+
+If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/master/CONTRIBUTING.md) to learn more about how to build and test the code.
+
+This module's tests are live tests, which require you to have an Azure App Configuration instance. To execute the tests
+you'll need to run:
+1. `rush update`
+2. `rush build`
+3. `npm run test`.
+
+View our tests ([index.spec.ts](https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/appconfiguration/app-configuration/test/index.spec.ts)) for more details.
+
## Related projects
- [Microsoft Azure SDK for Javascript](https://github.com/Azure/azure-sdk-for-js)
diff --git a/sdk/appconfiguration/app-configuration/samples/helloworld.ts b/sdk/appconfiguration/app-configuration/samples/helloworld.ts
new file mode 100644
index 000000000000..971673ba37d6
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/samples/helloworld.ts
@@ -0,0 +1,50 @@
+// NOTE: replace with import { AppConfigurationClient } from "@azure/app-configuration"
+// in a standalone project
+import { AppConfigurationClient } from "../src"
+
+export async function run() {
+ console.log("Running helloworld sample");
+
+ // You will need to set this environment variable
+ const connectionString = process.env["AZ_CONFIG_CONNECTION"]!;
+ const client = new AppConfigurationClient(connectionString);
+
+ const greetingKey = "Samples:Greeting";
+
+ await cleanupSampleValues([greetingKey], client);
+
+ // creating a new setting
+ console.log(`Adding in new setting ${greetingKey}`);
+ await client.addConfigurationSetting(greetingKey, { value: "Hello!" });
+
+ const newSetting = await client.getConfigurationSetting(greetingKey);
+ console.log(`${greetingKey} has been set to ${newSetting.value}`);
+
+ // changing the value of a setting
+ await client.setConfigurationSetting(greetingKey, { value: "Goodbye!" });
+
+ const updatedSetting = await client.getConfigurationSetting(greetingKey);
+ console.log(`${greetingKey} has been set to ${updatedSetting.value}`);
+
+ // removing the setting
+ await client.deleteConfigurationSetting(greetingKey, {});
+ console.log(`${greetingKey} has been deleted`);
+
+ await cleanupSampleValues([greetingKey], client);
+}
+
+async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) {
+ const existingSettings = await client.listConfigurationSettings({
+ key: keys
+ });
+
+ for (const setting of existingSettings) {
+ await client.deleteConfigurationSetting(setting.key!, { label: setting.label });
+ }
+}
+
+// If you want to run this sample from a console
+// uncomment these lines so run() will get called
+// run().catch(err => {
+// console.log(`ERROR: ${err}`);
+// });
\ No newline at end of file
diff --git a/sdk/appconfiguration/app-configuration/samples/helloworldWithETag.ts b/sdk/appconfiguration/app-configuration/samples/helloworldWithETag.ts
new file mode 100644
index 000000000000..2ab39158dc33
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/samples/helloworldWithETag.ts
@@ -0,0 +1,88 @@
+// NOTE: replace with import { AppConfigurationClient } from "@azure/app-configuration"
+// in a standalone project
+import { AppConfigurationClient } from "../src"
+
+export async function run() {
+ console.log("Running helloworld sample using etags");
+
+ // You will need to set this environment variable
+ const connectionString = process.env["AZ_CONFIG_CONNECTION"]!;
+ const client = new AppConfigurationClient(connectionString);
+
+ const greetingKey = "Samples:Greeting";
+
+ await cleanupSampleValues([greetingKey], client);
+
+ // create a new setting as Client Alpha
+ console.log(`Client Alpha: adding in new setting ${greetingKey}`);
+ let initialSettingFromClientA = await client.addConfigurationSetting(greetingKey, { value: "Created for Client Alpha" });
+ console.log(`Client Alpha: ${greetingKey} has been set to '${initialSettingFromClientA.value}' with etag of ${initialSettingFromClientA.etag}`);
+
+ // Each setting, when added, will come back with an etag (https://en.wikipedia.org/wiki/HTTP_ETag)
+ // This allows you to update a value but only if it hasn't changed from the last time you read it.
+ //
+ // Let's simulate two processes attempting to update the value
+
+ // if you don't specify an etag the update is unconditional
+ let updateFromClientBeta = await client.setConfigurationSetting(greetingKey, {
+ value: "Update from Client Beta"
+ });
+
+ console.log(`Client Beta: updated the value of ${greetingKey} without specifying an etag (unconditional update).`);
+ console.log(` Client Beta's etag is ${updateFromClientBeta.etag}`);
+ console.log(` Client Alpha's etag from the initial creation is ${initialSettingFromClientA.etag}`);
+
+ // at this point we've got this sequence of events
+ //
+ // 1. Client Alpha created the setting (and stored off its etag)
+ // 2. Client Beta updated the setting, ignoring the etag
+
+ // Now Client Alpha wants to update the value _but_ Client Alpha will pay attention to the
+ // etag and only update the value if the value currently stored is the same as when we
+ // initially updated the setting.
+ //
+ // This allows us to prevent unintentional overwrites and allows you to implement
+ // optimistic concurrency (https://en.wikipedia.org/wiki/Optimistic_concurrency_control)
+ // within your application.
+
+ console.log("Client Alpha: attempting update that doesn't include an etag");
+ await client.setConfigurationSetting(greetingKey, {
+ value: "Update from Client Alpha that should only get set if the value has not changed from the last time we loaded it",
+ etag: initialSettingFromClientA.etag
+ }).catch(err => {
+ console.log(" Update failed - etag didn't match");
+ });
+
+ // if we want to update then we need to retrieve the new setting and determine if our update makes sense
+ let actualStoredSetting = await client.getConfigurationSetting(greetingKey);
+
+ console.log("Client Alpha: getting current value and merging/updating based on it")
+ // now we can figure out if we want to merge our value, overwrite with our value, etc...
+ // in this case we'll just update the value to what we want (again, specifying the etag to
+ // prevent unintended overwrite)
+ await client.updateConfigurationSetting(greetingKey, {
+ value: "Theoretical update from Client Alpha that takes Client Beta's changes into account",
+ etag: actualStoredSetting.etag
+ });
+
+ let currentSetting = await client.getConfigurationSetting(greetingKey);
+ console.log(`The value is now updated to '${currentSetting.value}'`);
+
+ await cleanupSampleValues([greetingKey], client);
+}
+
+async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) {
+ const existingSettings = await client.listConfigurationSettings({
+ key: keys
+ });
+
+ for (const setting of existingSettings) {
+ await client.deleteConfigurationSetting(setting.key!, { label: setting.label });
+ }
+}
+
+// If you want to run this sample from a console
+// uncomment these lines so run() will get called
+// run().catch(err => {
+// console.log("ERROR", err);
+// });
\ No newline at end of file
diff --git a/sdk/appconfiguration/app-configuration/samples/helloworldWithLabels.ts b/sdk/appconfiguration/app-configuration/samples/helloworldWithLabels.ts
new file mode 100644
index 000000000000..359dc7c37e05
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/samples/helloworldWithLabels.ts
@@ -0,0 +1,45 @@
+// NOTE: replace with import { AppConfigurationClient } from "@azure/app-configuration"
+// in a standalone project
+import { AppConfigurationClient } from "../src"
+
+export async function run() {
+ console.log("Running helloworldWithLabels sample");
+
+ // You will need to set this environment variable
+ const connectionString = process.env["AZ_CONFIG_CONNECTION"]!;
+ const client = new AppConfigurationClient(connectionString);
+
+ const urlKey = "Samples:Endpoint:Url";
+
+ await cleanupSampleValues([urlKey], client);
+
+ // labels allow you to use the same key with different values for separate environments
+ // or clients
+ console.log("Adding in endpoint with two labels - beta and production");
+ await client.addConfigurationSetting(urlKey, { label: "beta", value: "https://beta.example.com" });
+ await client.addConfigurationSetting(urlKey, { label: "production", value: "https://example.com" });
+
+ const betaEndpoint = await client.getConfigurationSetting(urlKey, { label: "beta" });
+ console.log(`Endpoint with beta label: ${betaEndpoint.value}`);
+
+ const productionEndpoint = await client.getConfigurationSetting(urlKey, { label: "production" });
+ console.log(`Endpoint with production label: ${productionEndpoint.value}`);
+
+ await cleanupSampleValues([urlKey], client);
+}
+
+async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) {
+ const existingSettings = await client.listConfigurationSettings({
+ key: keys
+ });
+
+ for (const setting of existingSettings) {
+ await client.deleteConfigurationSetting(setting.key!, { label: setting.label });
+ }
+}
+
+// If you want to run this sample from a console
+// uncomment these lines so run() will get called
+// run().catch(err => {
+// console.log(`ERROR: ${err}`);
+// });
\ No newline at end of file
diff --git a/sdk/appconfiguration/app-configuration/samples/index.ts b/sdk/appconfiguration/app-configuration/samples/index.ts
new file mode 100644
index 000000000000..d768fc0d0d6b
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/samples/index.ts
@@ -0,0 +1,7 @@
+import * as helloworld from "./helloworld";
+import * as helloworldWithLabels from "./helloworldWithLabels";
+
+export async function runAll() {
+ await helloworld.run();
+ await helloworldWithLabels.run();
+}
\ No newline at end of file
diff --git a/sdk/appconfiguration/app-configuration/src/index.ts b/sdk/appconfiguration/app-configuration/src/index.ts
index f1aa1b606567..bd815dcaa3ab 100644
--- a/sdk/appconfiguration/app-configuration/src/index.ts
+++ b/sdk/appconfiguration/app-configuration/src/index.ts
@@ -102,6 +102,12 @@ export class AppConfigurationClient {
*/
constructor(uri: string, credential: TokenCredential);
constructor(uriOrConnectionString: string, credential?: TokenCredential) {
+ if (uriOrConnectionString == null) {
+ throw new Error(
+ "You must provide a connection string or the URL for your AppConfiguration instance"
+ );
+ }
+
const regexMatch = uriOrConnectionString.match(ConnectionStringRegex);
if (regexMatch) {
const credential = new AppConfigCredential(regexMatch[2], regexMatch[3]);
diff --git a/sdk/appconfiguration/app-configuration/test/index.spec.ts b/sdk/appconfiguration/app-configuration/test/index.spec.ts
index aa80f2f00770..98ce524118ad 100644
--- a/sdk/appconfiguration/app-configuration/test/index.spec.ts
+++ b/sdk/appconfiguration/app-configuration/test/index.spec.ts
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
import * as assert from "assert";
+import { getConnectionStringFromEnvironment } from "./testHelpers";
import * as dotenv from "dotenv";
import { AppConfigurationClient } from "../src";
@@ -9,14 +10,11 @@ dotenv.config();
describe("AppConfigurationClient", () => {
const settings: Array<{ key: string; label?: string }> = [];
- const connectionString: string = process.env["APPCONFIG_CONNECTION_STRING"]!;
let client: AppConfigurationClient;
before("validate environment variables", () => {
- if (!connectionString) {
- throw new Error("APPCONFIG_CONNECTION_STRING not defined.");
- }
+ let connectionString = getConnectionStringFromEnvironment();
client = new AppConfigurationClient(connectionString);
});
@@ -28,6 +26,7 @@ describe("AppConfigurationClient", () => {
describe("constructor", () => {
it("supports connection string", async () => {
+ const connectionString = getConnectionStringFromEnvironment();
const client = new AppConfigurationClient(connectionString);
// make sure a service call succeeds
await client.listConfigurationSettings();
diff --git a/sdk/appconfiguration/app-configuration/test/samples.spec.ts b/sdk/appconfiguration/app-configuration/test/samples.spec.ts
new file mode 100644
index 000000000000..c594b699408a
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/test/samples.spec.ts
@@ -0,0 +1,7 @@
+import { runAll } from "../samples";
+
+describe("AppConfiguration samples", () => {
+ it("Make sure all the samples build and run", async () => {
+ await runAll();
+ });
+});
\ No newline at end of file
diff --git a/sdk/appconfiguration/app-configuration/test/testHelpers.ts b/sdk/appconfiguration/app-configuration/test/testHelpers.ts
new file mode 100644
index 000000000000..3838664fe319
--- /dev/null
+++ b/sdk/appconfiguration/app-configuration/test/testHelpers.ts
@@ -0,0 +1,26 @@
+import { AppConfigurationClient } from "../src"
+
+// allow loading from a .env file as an alternative to defining the variable
+// in the environment
+import * as dotenv from "dotenv";
+dotenv.config();
+
+export function getConnectionStringFromEnvironment() : string {
+ const connectionString = process.env["AZ_CONFIG_CONNECTION"]!;
+
+ if (connectionString == null) {
+ throw Error(`No connection string in environment - set AZ_CONFIG_CONNECTION with a connection string for your AppConfiguration instance.`);
+ }
+
+ return connectionString;
+}
+
+export async function cleanupSampleValues(keys: string[], client: AppConfigurationClient) {
+ const existingSettings = await client.listConfigurationSettings({
+ key: keys
+ });
+
+ for (const setting of existingSettings) {
+ await client.deleteConfigurationSetting(setting.key!, { label: setting.label });
+ }
+}
\ No newline at end of file