Skip to content

Commit

Permalink
Synchronize repo from Repoman
Browse files Browse the repository at this point in the history
  • Loading branch information
azure-sdk committed Jan 11, 2023
1 parent 58c4b94 commit ee35d1f
Show file tree
Hide file tree
Showing 19 changed files with 12,571 additions and 8,484 deletions.
3 changes: 3 additions & 0 deletions .azdo/pipelines/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pool:
container: mcr.microsoft.com/azure-dev-cli-apps:latest

steps:
- pwsh: |
azd config set auth.useAzCliAuth "true"
displayName: Configure AZD to Use AZ CLI Authentication.
- task: AzureCLI@2
displayName: Azure Dev Provision
inputs:
Expand Down
5 changes: 4 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
ARG VARIANT=bullseye
FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
FROM --platform=amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update && apt-get install -y xdg-utils \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://aka.ms/install-azd.sh | bash
6 changes: 2 additions & 4 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
}
},
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "2.38"
},
"ghcr.io/devcontainers/features/docker-from-docker:1": {
"version": "20.10"
},
Expand All @@ -30,7 +27,8 @@
"ms-azuretools.vscode-docker",
"ms-vscode.vscode-node-azure-pack",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime"
"ms-dotnettools.vscode-dotnet-runtime",
"ms-azuretools.vscode-azurefunctions"
],
"forwardPorts": [
3000,
Expand Down
40 changes: 32 additions & 8 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,48 @@ on:
branches:
- main
- master
pull_request:
branches:
- main
- master

# https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication
permissions:
id-token: write
contents: read

jobs:
build:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/azure-dev-cli-apps:latest
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Log in with Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Log in with Azure (Federated Credentials)
if: ${{ env.AZURE_CLIENT_ID != '' }}
run: |
azd login `
--client-id "$Env:AZURE_CLIENT_ID" `
--federated-credential-provider "github" `
--tenant-id "$Env:AZURE_TENANT_ID"
shell: pwsh

- name: Log in with Azure (Client Credentials)
if: ${{ env.AZURE_CREDENTIALS != '' }}
run: |
$info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable;
Write-Host "::add-mask::$($info.clientSecret)"
azd login `
--client-id "$($info.clientId)" `
--client-secret "$($info.clientSecret)" `
--tenant-id "$($info.tenantId)"
shell: pwsh
env:
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}

- name: Azure Dev Provision
run: azd provision --no-prompt
Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,33 @@ When you are done, you can delete all the Azure resources created with this temp
azd down
```

### Enable Additional Features

#### Enable [Azure API Management](https://learn.microsoft.com/azure/api-management/)

This template is prepared to use Azure API Management (aka APIM) for backend API protection and observability. APIM supports the complete API lifecycle and abstract backend complexity from API consumers.

To use APIM on this template you just need to set the environment variable with the following command:

```bash
azd env set USE_APIM true
```
And then execute `azd up` to provision and deploy. No worries if you already did `azd up`! You can set the `USE_APIM` environment variable at anytime and then just repeat the `azd up` command to run the incremental deployment.

Here's the high level architecture diagram when APIM is used:

<img src="assets/resources-with-apim.png" width="60%" alt="Application architecture diagram with APIM"/>

The frontend will be configured to make API requests through APIM instead of calling the backend directly, so that the following flow gets executed:

1. APIM receives the frontend request, applies the configured policy to enable CORS, validates content and limits concurrency. Follow this [guide](https://learn.microsoft.com/azure/api-management/api-management-howto-policies) to understand how to customize the policy.
1. If there are no errors, the request is forwarded to the backend and then the backend response is sent back to the frontend.
1. APIM emits logs, metrics, and traces for monitoring, reporting, and troubleshooting on every execution. Follow this [guide](https://learn.microsoft.com/azure/api-management/api-management-howto-use-azure-monitor) to visualize, query, and take actions on the metrics or logs coming from APIM.

> NOTE:
>
> By default, this template uses the Consumption tier that is a lightweight and serverless version of API Management service, billed per execution. Please check the [pricing page](https://azure.microsoft.com/pricing/details/api-management/) for more details.
### Additional azd commands

The Azure Developer CLI includes many other commands to help with your Azure development experience. You can view these commands at the terminal by running `azd help`. You can also view the full list of commands on our [Azure Developer CLI command](https://aka.ms/azure-dev/ref) page.
Expand All @@ -173,7 +200,7 @@ Sometimes, things go awry. If you happen to run into issues, then please review

### Roles

This template creates a [managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) for your app inside your Azure Active Directory tenant, and it is used to authenticate your app with Azure and other services that support Azure AD authentication like Key Vault via access policies. You will see principalId referenced in the infrastructure as code files, that refers to the id of the currently logged in Azure CLI user, which will be granted access policies and permissions to run the application locally. To view your managed identity in the Azure Portal, follow these [steps](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-view-managed-identity-service-principal-portal).
This template creates a [managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) for your app inside your Azure Active Directory tenant, and it is used to authenticate your app with Azure and other services that support Azure AD authentication like Key Vault via access policies. You will see principalId referenced in the infrastructure as code files, that refers to the id of the currently logged in Azure Developer CLI user, which will be granted access policies and permissions to run the application locally. To view your managed identity in the Azure Portal, follow these [steps](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-view-managed-identity-service-principal-portal).

### Key Vault

Expand Down
Binary file added assets/resources-with-apim.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/resources.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/urls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions infra/app/apim-api-policy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!-- Policy configuration for the API. Explore other sample policies at https://learn.microsoft.com/en-us/azure/api-management/policies/ -->
<policies>
<inbound>
<base />
<!-- This policy is needed to handle preflight requests using the OPTIONS method. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-cross-domain-policies -->
<cors allow-credentials="false">
<allowed-origins>
<origin>{origin}</origin>
</allowed-origins>
<allowed-methods>
<method>PUT</method>
<method>GET</method>
<method>POST</method>
<method>DELETE</method>
<method>PATCH</method>
</allowed-methods>
<allowed-headers>
<header>*</header>
</allowed-headers>
<expose-headers>
<header>*</header>
</expose-headers>
</cors>
<!-- Optional policy to validate the request content. Learn more at https://learn.microsoft.com/en-us/azure/api-management/validation-policies#validate-content -->
<validate-content unspecified-content-type-action="ignore" max-size="1024" size-exceeded-action="detect" errors-variable-name="requestBodyValidation">
<content type="application/json" validate-as="json" action="detect" />
</validate-content>
<!-- Optional policy to send custom trace telemetry to Application Insights. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#Trace -->
<trace source="@(context.Api.Name)" severity="verbose">
<message>Call to the @(context.Api.Name)</message>
<metadata name="User-Agent" value="@(context.Request.Headers.GetValueOrDefault("User-Agent",""))" />
<metadata name="Operation Method" value="@(context.Request.Method)" />
<metadata name="Host" value="@(context.Request.Url.Host)" />
<metadata name="Path" value="@(context.Request.Url.Path)" />
</trace>
</inbound>
<backend>
<limit-concurrency key="@(context.Request.IpAddress)" max-count="3">
<forward-request timeout="120" />
</limit-concurrency>
</backend>
<outbound>
<base />
<!-- Optional policy to validate the response headers. Learn more at https://learn.microsoft.com/en-us/azure/api-management/validation-policies#validate-headers -->
<validate-headers specified-header-action="ignore" unspecified-header-action="ignore" errors-variable-name="responseHeadersValidation" />
<!-- Optional policy to to send custom metrics to Application Insights. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#emit-metrics -->
<choose>
<when condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300)">
<emit-metric name="Successful requests" value="1" namespace="apim-metrics">
<dimension name="API" value="@(context.Api.Name)" />
<dimension name="Client IP" value="@(context.Request.IpAddress)" />
<dimension name="Status Code" value="@((String)context.Response.StatusCode.ToString())" />
<dimension name="Status Reason" value="@(context.Response.StatusReason)" />
</emit-metric>
</when>
<when condition="@(context.Response.StatusCode >= 400 && context.Response.StatusCode < 600)">
<emit-metric name="Failed requests" value="1" namespace="apim-metrics">
<dimension name="API" value="@(context.Api.Name)" />
<dimension name="Client IP" value="@(context.Request.IpAddress)" />
<dimension name="Status Code" value="@(context.Response.StatusCode.ToString())" />
<dimension name="Status Reason" value="@(context.Response.StatusReason)" />
<dimension name="Error Source" value="backend" />
</emit-metric>
</when>
</choose>
</outbound>
<on-error>
<base />
<!-- Optional policy to handle errors. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-error-handling-policies -->
<trace source="@(context.Api.Name)" severity="error">
<message>Failed to process the @(context.Api.Name)</message>
<metadata name="User-Agent" value="@(context.Request.Headers.GetValueOrDefault("User-Agent",""))" />
<metadata name="Operation Method" value="@(context.Request.Method)" />
<metadata name="Host" value="@(context.Request.Url.Host)" />
<metadata name="Path" value="@(context.Request.Url.Path)" />
<metadata name="Error Reason" value="@(context.LastError.Reason)" />
<metadata name="Error Message" value="@(context.LastError.Message)" />
</trace>
<emit-metric name="Failed requests" value="1" namespace="apim-metrics">
<dimension name="API" value="@(context.Api.Name)" />
<dimension name="Client IP" value="@(context.Request.IpAddress)" />
<dimension name="Status Code" value="500" />
<dimension name="Status Reason" value="@(context.LastError.Reason)" />
<dimension name="Error Source" value="gateway" />
</emit-metric>
<!-- Optional policy to hide error details and provide a custom generic message. Learn more at https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#ReturnResponse -->
<return-response>
<set-status code="500" reason="Internal Server Error" />
<set-body>An unexpected error has occurred.</set-body>
</return-response>
</on-error>
</policies>
103 changes: 103 additions & 0 deletions infra/app/apim-api.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
param name string

@description('Resouce name to uniquely dentify this API within the API Management service instance')
@minLength(1)
param apiName string

@description('The Display Name of the API')
@minLength(1)
@maxLength(300)
param apiDisplayName string

@description('Description of the API. May include HTML formatting tags.')
@minLength(1)
param apiDescription string

@description('Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.')
@minLength(1)
param apiPath string

@description('Absolute URL of the web frontend')
param webFrontendUrl string

@description('Absolute URL of the backend service implementing this API.')
param apiBackendUrl string

var apiPolicyContent = replace(loadTextContent('./apim-api-policy.xml'), '{origin}', webFrontendUrl)

resource restApi 'Microsoft.ApiManagement/service/apis@2021-12-01-preview' = {
name: apiName
parent: apimService
properties: {
description: apiDescription
displayName: apiDisplayName
path: apiPath
protocols: [ 'https' ]
subscriptionRequired: false
type: 'http'
format: 'openapi'
serviceUrl: apiBackendUrl
value: loadTextContent('../../src/api/wwwroot/openapi.yaml')
}
}

resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-preview' = {
name: 'policy'
parent: restApi
properties: {
format: 'rawxml'
value: apiPolicyContent
}
}

resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = {
name: 'applicationinsights'
parent: restApi
properties: {
alwaysLog: 'allErrors'
backend: {
request: {
body: {
bytes: 1024
}
}
response: {
body: {
bytes: 1024
}
}
}
frontend: {
request: {
body: {
bytes: 1024
}
}
response: {
body: {
bytes: 1024
}
}
}
httpCorrelationProtocol: 'W3C'
logClientIp: true
loggerId: apimLogger.id
metrics: true
sampling: {
percentage: 100
samplingType: 'fixed'
}
verbosity: 'verbose'
}
}

resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = {
name: name
}

resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' existing = {
name: 'app-insights-logger'
parent: apimService
}

output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}'
61 changes: 61 additions & 0 deletions infra/core/gateway/apim.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
param name string
param location string = resourceGroup().location
param tags object = {}

@description('The email address of the owner of the service')
@minLength(1)
param publisherEmail string = '[email protected]'

@description('The name of the owner of the service')
@minLength(1)
param publisherName string = 'n/a'

@description('The pricing tier of this API Management service')
@allowed([
'Consumption'
'Developer'
'Standard'
'Premium'
])
param sku string = 'Consumption'

@description('The instance size of this API Management service.')
@allowed([ 0, 1, 2 ])
param skuCount int = 0

@description('Azure Application Insights Name')
param applicationInsightsName string

resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = {
name: name
location: location
tags: union(tags, { 'azd-service-name': name })
sku: {
name: sku
capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount)
}
properties: {
publisherEmail: publisherEmail
publisherName: publisherName
}
}

resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) {
name: 'app-insights-logger'
parent: apimService
properties: {
credentials: {
instrumentationKey: applicationInsights.properties.InstrumentationKey
}
description: 'Logger to Azure Application Insights'
isBuffered: false
loggerType: 'applicationInsights'
resourceId: applicationInsights.id
}
}

resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {
name: applicationInsightsName
}

output apimServiceName string = apimService.name
Loading

0 comments on commit ee35d1f

Please sign in to comment.