Skip to content

Commit

Permalink
[app-configuration] Adding in sample code and readme (#5091)
Browse files Browse the repository at this point in the history
* Adding in sample code for AppConfiguration

* Remove test:watch command until I get it working reliably

* * Updating samples and adding one that demonstrates etags
* Removing the browser examples since we're not yet supporting browsers.

* Updating readme and expanding out the summary

* Fix the sample code so it works

* Remove the wrapper

* Fixed sample code and also added in link to additional samples

* Updated readme with appropriate text

* * Flattening out samples so they aren't referencing any separate files
* Updating readme with links to the samples and instructions on how to build and test it for contributions
* Removing troubleshooting section until we add in logging

* * Ignore missing sections in readme until we get troubleshooting steps
* Add in a section for examples

* Update path

* Update .docsettings.yml to the right path

* Update to a simpler function for the example code

* * Added back in the connection string test
* Fixed an issue where I wasn't awaiting on the result of the clean, causing the samples to be flaky when they ran
  • Loading branch information
richardpark-msft authored Sep 12, 2019
1 parent 3133eb2 commit 386cc43
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 83 deletions.
3 changes: 2 additions & 1 deletion .docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
139 changes: 61 additions & 78 deletions sdk/appconfiguration/app-configuration/README.md
Original file line number Diff line number Diff line change
@@ -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
<!DOCTYPE html>
<html lang="en">
<head>
<title>@azure/app-configuration sample</title>
<script src="node_modules/@azure/core-http/dist/coreHttp.browser.js"></script>
<script src="node_modules/@azure/core-arm/dist/coreArm.js"></script>
<script src="node_modules/@azure/ms-rest-browserauth/dist/msAuth.js"></script>
<script src="node_modules/@azure/app-config/dist/app-config.js"></script>
<script type="text/javascript">
const subscriptionId = "<Subscription_Id>";
const authManager = new msAuth.AuthManager({
clientId: "<client id for your Azure AD app>",
tenant: "<optional tenant for your organization>"
});
authManager.finalizeLogin().then((res) => {
if (!res.isLoggedIn) {
// may cause redirects
authManager.login();
}
const client = new Azure.AppConfig.ConfigurationClient(res.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.log("An error occurred:");
console.error(err);
});
});
</script>
</head>
<body></body>
</html>
```
## 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 <https://cla.microsoft.com.>

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)
Expand Down
50 changes: 50 additions & 0 deletions sdk/appconfiguration/app-configuration/samples/helloworld.ts
Original file line number Diff line number Diff line change
@@ -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}`);
// });
Original file line number Diff line number Diff line change
@@ -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);
// });
Original file line number Diff line number Diff line change
@@ -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}`);
// });
7 changes: 7 additions & 0 deletions sdk/appconfiguration/app-configuration/samples/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as helloworld from "./helloworld";
import * as helloworldWithLabels from "./helloworldWithLabels";

export async function runAll() {
await helloworld.run();
await helloworldWithLabels.run();
}
6 changes: 6 additions & 0 deletions sdk/appconfiguration/app-configuration/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
Loading

0 comments on commit 386cc43

Please sign in to comment.