From 695c86ae0bfdc15c0e443aa547d1b517a096ae18 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Thu, 8 Aug 2024 00:02:32 -0400 Subject: [PATCH 1/8] updated to azd format --- .gitignore | 1 + Azure/container_app.bicep | 67 - Azure/environment.bicep | 44 - Azure/main.bicep | 83 -- Store.InventoryApi/Store.InventoryApi.csproj | 3 +- Store.ProductApi/Store.ProductApi.csproj | 3 +- Store/Store.csproj | 3 +- azure.yaml | 24 + infra/abbreviations.json | 135 ++ infra/app/Store.InventoryApi.bicep | 113 ++ infra/app/Store.ProductApi.bicep | 113 ++ infra/app/Store.bicep | 113 ++ infra/main.bicep | 135 ++ infra/main.parameters.json | 12 + infra/modules/fetch-container-image.bicep | 8 + infra/shared/apps-env.bicep | 33 + infra/shared/dashboard-web.bicep | 1231 ++++++++++++++++++ infra/shared/keyvault.bicep | 31 + infra/shared/monitoring.bicep | 34 + infra/shared/registry.bicep | 36 + next-steps.md | 95 ++ 21 files changed, 2120 insertions(+), 197 deletions(-) delete mode 100644 Azure/container_app.bicep delete mode 100644 Azure/environment.bicep delete mode 100644 Azure/main.bicep create mode 100644 azure.yaml create mode 100644 infra/abbreviations.json create mode 100644 infra/app/Store.InventoryApi.bicep create mode 100644 infra/app/Store.ProductApi.bicep create mode 100644 infra/app/Store.bicep create mode 100644 infra/main.bicep create mode 100644 infra/main.parameters.json create mode 100644 infra/modules/fetch-container-image.bicep create mode 100644 infra/shared/apps-env.bicep create mode 100644 infra/shared/dashboard-web.bicep create mode 100644 infra/shared/keyvault.bicep create mode 100644 infra/shared/monitoring.bicep create mode 100644 infra/shared/registry.bicep create mode 100644 next-steps.md diff --git a/.gitignore b/.gitignore index dfcfd56..51dfcaf 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ +.azure diff --git a/Azure/container_app.bicep b/Azure/container_app.bicep deleted file mode 100644 index c1c892d..0000000 --- a/Azure/container_app.bicep +++ /dev/null @@ -1,67 +0,0 @@ -param name string -param location string = resourceGroup().location -param containerAppEnvironmentId string -param repositoryImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' -param envVars array = [] -param registry string -param minReplicas int = 1 -param maxReplicas int = 1 -param port int = 80 -param externalIngress bool = false -param allowInsecure bool = true -param transport string = 'http' -param appProtocol string = 'http' -param registryUsername string -@secure() -param registryPassword string - -resource containerApp 'Microsoft.App/containerApps@2022-01-01-preview' ={ - name: name - location: location - properties:{ - managedEnvironmentId: containerAppEnvironmentId - configuration: { - dapr: { - enabled: true - appId: name - appPort: port - appProtocol: appProtocol - } - activeRevisionsMode: 'single' - secrets: [ - { - name: 'container-registry-password' - value: registryPassword - } - ] - registries: [ - { - server: registry - username: registryUsername - passwordSecretRef: 'container-registry-password' - } - ] - ingress: { - external: externalIngress - targetPort: port - transport: transport - allowInsecure: allowInsecure - } - } - template: { - containers: [ - { - image: repositoryImage - name: name - env: envVars - } - ] - scale: { - minReplicas: minReplicas - maxReplicas: maxReplicas - } - } - } -} - -output fqdn string = containerApp.properties.configuration.ingress.fqdn diff --git a/Azure/environment.bicep b/Azure/environment.bicep deleted file mode 100644 index 07e50be..0000000 --- a/Azure/environment.bicep +++ /dev/null @@ -1,44 +0,0 @@ -param baseName string = resourceGroup().name -param location string = resourceGroup().location - -resource logs 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { - name: '${baseName}logs' - location: location - properties: any({ - retentionInDays: 30 - features: { - searchVersion: 1 - } - sku: { - name: 'PerGB2018' - } - }) -} - -resource appInsights 'Microsoft.Insights/components@2020-02-02' = { - name: '${baseName}ai' - location: location - kind: 'web' - properties: { - Application_Type: 'web' - WorkspaceResourceId: logs.id - } -} - -resource env 'Microsoft.App/managedEnvironments@2022-01-01-preview' = { - name: '${baseName}env' - location: location - properties: { - appLogsConfiguration: { - destination: 'log-analytics' - logAnalyticsConfiguration: { - customerId: logs.properties.customerId - sharedKey: logs.listKeys().primarySharedKey - } - } - } -} - -output id string = env.id -output appInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey -output appInsightsConnectionString string = appInsights.properties.ConnectionString diff --git a/Azure/main.bicep b/Azure/main.bicep deleted file mode 100644 index 36b88a1..0000000 --- a/Azure/main.bicep +++ /dev/null @@ -1,83 +0,0 @@ -param location string = resourceGroup().location - -// create the azure container registry -resource acr 'Microsoft.ContainerRegistry/registries@2021-09-01' = { - name: toLower('${resourceGroup().name}acr') - location: location - sku: { - name: 'Basic' - } - properties: { - adminUserEnabled: true - } -} - -// create the aca environment -module env 'environment.bicep' = { - name: 'containerAppEnvironment' - params: { - location: location - } -} - -// create the various config pairs -var shared_config = [ - { - name: 'ASPNETCORE_ENVIRONMENT' - value: 'Development' - } - { - name: 'APPINSIGHTS_INSTRUMENTATIONKEY' - value: env.outputs.appInsightsInstrumentationKey - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: env.outputs.appInsightsConnectionString - } -] - -// create the products api container app -module products 'container_app.bicep' = { - name: 'products' - params: { - name: 'products' - location: location - registryPassword: acr.listCredentials().passwords[0].value - registryUsername: acr.listCredentials().username - containerAppEnvironmentId: env.outputs.id - registry: acr.name - envVars: shared_config - externalIngress: false - } -} - -// create the inventory api container app -module inventory 'container_app.bicep' = { - name: 'inventory' - params: { - name: 'inventory' - location: location - registryPassword: acr.listCredentials().passwords[0].value - registryUsername: acr.listCredentials().username - containerAppEnvironmentId: env.outputs.id - registry: acr.name - envVars: shared_config - externalIngress: false - } -} - -// create the store api container app -module store 'container_app.bicep' = { - name: 'store' - params: { - name: 'store' - location: location - registryPassword: acr.listCredentials().passwords[0].value - registryUsername: acr.listCredentials().username - containerAppEnvironmentId: env.outputs.id - registry: acr.name - envVars: shared_config - externalIngress: true - } -} - diff --git a/Store.InventoryApi/Store.InventoryApi.csproj b/Store.InventoryApi/Store.InventoryApi.csproj index 55ac2b9..4f65a86 100644 --- a/Store.InventoryApi/Store.InventoryApi.csproj +++ b/Store.InventoryApi/Store.InventoryApi.csproj @@ -1,10 +1,11 @@ - + net6.0 enable enable Linux + c489c9dd-1029-463e-976f-6b49e3056a28 diff --git a/Store.ProductApi/Store.ProductApi.csproj b/Store.ProductApi/Store.ProductApi.csproj index 1c2bc55..9ce4fb9 100644 --- a/Store.ProductApi/Store.ProductApi.csproj +++ b/Store.ProductApi/Store.ProductApi.csproj @@ -1,10 +1,11 @@ - + net6.0 enable enable Linux + bd7c3927-71d5-4ff7-841c-d85d6d1b32b5 diff --git a/Store/Store.csproj b/Store/Store.csproj index fdca0e5..1e7aac6 100644 --- a/Store/Store.csproj +++ b/Store/Store.csproj @@ -1,10 +1,11 @@ - + net6.0 enable enable Linux + 75057a84-8850-481a-ba6a-ea34429aa056 diff --git a/azure.yaml b/azure.yaml new file mode 100644 index 0000000..6a78abc --- /dev/null +++ b/azure.yaml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +name: dotNET-FrontEnd-to-BackEnd-with-DAPR-on-Azure-Container-Apps +metadata: + template: azd-init@1.9.5 +services: + Store: + project: Store + host: containerapp + language: dotnet + docker: + path: Dockerfile + Store.InventoryApi: + project: Store.InventoryApi + host: containerapp + language: dotnet + docker: + path: Dockerfile + Store.ProductApi: + project: Store.ProductApi + host: containerapp + language: dotnet + docker: + path: Dockerfile diff --git a/infra/abbreviations.json b/infra/abbreviations.json new file mode 100644 index 0000000..dc62141 --- /dev/null +++ b/infra/abbreviations.json @@ -0,0 +1,135 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} diff --git a/infra/app/Store.InventoryApi.bicep b/infra/app/Store.InventoryApi.bicep new file mode 100644 index 0000000..78dcbe6 --- /dev/null +++ b/infra/app/Store.InventoryApi.bicep @@ -0,0 +1,113 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param containerRegistryName string +param containerAppsEnvironmentName string +param applicationInsightsName string +param exists bool + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = { + name: containerRegistryName +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = { + name: containerAppsEnvironmentName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry + name: guid(subscription().id, resourceGroup().id, identity.id, 'acrPullRole') + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' + ) + principalType: 'ServicePrincipal' + principalId: identity.properties.principalId + } +} + +module fetchLatestImage '../modules/fetch-container-image.bicep' = { + name: '${name}-fetch-image' + params: { + exists: exists + name: name + } +} + +resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'Store.InventoryApi' }) + dependsOn: [acrPullRole] + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${identity.id}': {} } + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + ingress: { + external: false + targetPort: 80 + transport: 'auto' + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: identity.id + } + ] + } + template: { + containers: [ + { + image: fetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'main' + env: [ + { + name: 'ASPNETCORE_ENVIRONMENT' + value: 'Development' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: applicationInsights.properties.InstrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'PORT' + value: '80' + } + ] + resources: { + cpu: json('1.0') + memory: '2.0Gi' + } + } + ] + scale: { + minReplicas: 1 + maxReplicas: 10 + } + } + } +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output name string = app.name +output uri string = 'https://${app.properties.configuration.ingress.fqdn}' +output id string = app.id diff --git a/infra/app/Store.ProductApi.bicep b/infra/app/Store.ProductApi.bicep new file mode 100644 index 0000000..5aa2d90 --- /dev/null +++ b/infra/app/Store.ProductApi.bicep @@ -0,0 +1,113 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param containerRegistryName string +param containerAppsEnvironmentName string +param applicationInsightsName string +param exists bool + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = { + name: containerRegistryName +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = { + name: containerAppsEnvironmentName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry + name: guid(subscription().id, resourceGroup().id, identity.id, 'acrPullRole') + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' + ) + principalType: 'ServicePrincipal' + principalId: identity.properties.principalId + } +} + +module fetchLatestImage '../modules/fetch-container-image.bicep' = { + name: '${name}-fetch-image' + params: { + exists: exists + name: name + } +} + +resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'Store.ProductApi' }) + dependsOn: [acrPullRole] + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${identity.id}': {} } + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + ingress: { + external: false + targetPort: 80 + transport: 'auto' + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: identity.id + } + ] + } + template: { + containers: [ + { + image: fetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'main' + env: [ + { + name: 'ASPNETCORE_ENVIRONMENT' + value: 'Development' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: applicationInsights.properties.InstrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'PORT' + value: '80' + } + ] + resources: { + cpu: json('1.0') + memory: '2.0Gi' + } + } + ] + scale: { + minReplicas: 1 + maxReplicas: 10 + } + } + } +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output name string = app.name +output uri string = 'https://${app.properties.configuration.ingress.fqdn}' +output id string = app.id diff --git a/infra/app/Store.bicep b/infra/app/Store.bicep new file mode 100644 index 0000000..d59db2f --- /dev/null +++ b/infra/app/Store.bicep @@ -0,0 +1,113 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param containerRegistryName string +param containerAppsEnvironmentName string +param applicationInsightsName string +param exists bool + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = { + name: containerRegistryName +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = { + name: containerAppsEnvironmentName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry + name: guid(subscription().id, resourceGroup().id, identity.id, 'acrPullRole') + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' + ) + principalType: 'ServicePrincipal' + principalId: identity.properties.principalId + } +} + +module fetchLatestImage '../modules/fetch-container-image.bicep' = { + name: '${name}-fetch-image' + params: { + exists: exists + name: name + } +} + +resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'Store' }) + dependsOn: [acrPullRole] + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${identity.id}': {} } + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + transport: 'auto' + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: identity.id + } + ] + } + template: { + containers: [ + { + image: fetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'main' + env: [ + { + name: 'ASPNETCORE_ENVIRONMENT' + value: 'Development' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: applicationInsights.properties.InstrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'PORT' + value: '80' + } + ] + resources: { + cpu: json('1.0') + memory: '2.0Gi' + } + } + ] + scale: { + minReplicas: 1 + maxReplicas: 10 + } + } + } +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output name string = app.name +output uri string = 'https://${app.properties.configuration.ingress.fqdn}' +output id string = app.id diff --git a/infra/main.bicep b/infra/main.bicep new file mode 100644 index 0000000..0cb2c8c --- /dev/null +++ b/infra/main.bicep @@ -0,0 +1,135 @@ +targetScope = 'subscription' + +@minLength(1) +@maxLength(64) +@description('Name of the environment that can be used as part of naming resource convention') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string + +param storeExists bool = false +param storeInventoryApiExists bool = false +param storeProductApiExists bool = false + +// Tags that should be applied to all resources. +// +// Note that 'azd-service-name' tags should be applied separately to service host resources. +// Example usage: +// tags: union(tags, { 'azd-service-name': }) +var tags = { + 'azd-env-name': environmentName +} + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) + +resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: 'rg-${environmentName}' + location: location + tags: tags +} + +module monitoring './shared/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: '${abbrs.insightsComponents}${resourceToken}' + } + scope: rg +} + +module dashboard './shared/dashboard-web.bicep' = { + name: 'dashboard' + params: { + name: '${abbrs.portalDashboards}${resourceToken}' + applicationInsightsName: monitoring.outputs.applicationInsightsName + location: location + tags: tags + } + scope: rg +} + +module registry './shared/registry.bicep' = { + name: 'registry' + params: { + location: location + tags: tags + name: '${abbrs.containerRegistryRegistries}${resourceToken}' + } + scope: rg +} + +module keyVault './shared/keyvault.bicep' = { + name: 'keyvault' + params: { + location: location + tags: tags + name: '${abbrs.keyVaultVaults}${resourceToken}' + } + scope: rg +} + +module appsEnv './shared/apps-env.bicep' = { + name: 'apps-env' + params: { + name: '${abbrs.appManagedEnvironments}${resourceToken}' + location: location + tags: tags + applicationInsightsName: monitoring.outputs.applicationInsightsName + logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + } + scope: rg +} + +module store './app/Store.bicep' = { + name: 'Store' + params: { + name: '${abbrs.appContainerApps}store-${resourceToken}' + location: location + tags: tags + identityName: '${abbrs.managedIdentityUserAssignedIdentities}store-${resourceToken}' + applicationInsightsName: monitoring.outputs.applicationInsightsName + containerAppsEnvironmentName: appsEnv.outputs.name + containerRegistryName: registry.outputs.name + exists: storeExists + } + scope: rg +} + +module storeInventoryApi './app/Store.InventoryApi.bicep' = { + name: 'Store.InventoryApi' + params: { + name: '${abbrs.appContainerApps}storeinvent-${resourceToken}' + location: location + tags: tags + identityName: '${abbrs.managedIdentityUserAssignedIdentities}storeinvent-${resourceToken}' + applicationInsightsName: monitoring.outputs.applicationInsightsName + containerAppsEnvironmentName: appsEnv.outputs.name + containerRegistryName: registry.outputs.name + exists: storeInventoryApiExists + } + scope: rg +} + +module storeProductApi './app/Store.ProductApi.bicep' = { + name: 'Store.ProductApi' + params: { + name: '${abbrs.appContainerApps}storeproduc-${resourceToken}' + location: location + tags: tags + identityName: '${abbrs.managedIdentityUserAssignedIdentities}storeproduc-${resourceToken}' + applicationInsightsName: monitoring.outputs.applicationInsightsName + containerAppsEnvironmentName: appsEnv.outputs.name + containerRegistryName: registry.outputs.name + exists: storeProductApiExists + } + scope: rg +} + +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = registry.outputs.loginServer +output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name +output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint diff --git a/infra/main.parameters.json b/infra/main.parameters.json new file mode 100644 index 0000000..8f7787b --- /dev/null +++ b/infra/main.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + } + } +} \ No newline at end of file diff --git a/infra/modules/fetch-container-image.bicep b/infra/modules/fetch-container-image.bicep new file mode 100644 index 0000000..78d1e7e --- /dev/null +++ b/infra/modules/fetch-container-image.bicep @@ -0,0 +1,8 @@ +param exists bool +param name string + +resource existingApp 'Microsoft.App/containerApps@2023-05-02-preview' existing = if (exists) { + name: name +} + +output containers array = exists ? existingApp.properties.template.containers : [] diff --git a/infra/shared/apps-env.bicep b/infra/shared/apps-env.bicep new file mode 100644 index 0000000..030b823 --- /dev/null +++ b/infra/shared/apps-env.bicep @@ -0,0 +1,33 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-10-01' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIConnectionString: applicationInsights.properties.ConnectionString + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +output name string = containerAppsEnvironment.name +output domain string = containerAppsEnvironment.properties.defaultDomain diff --git a/infra/shared/dashboard-web.bicep b/infra/shared/dashboard-web.bicep new file mode 100644 index 0000000..eccce0d --- /dev/null +++ b/infra/shared/dashboard-web.bicep @@ -0,0 +1,1231 @@ +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsightsName}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} diff --git a/infra/shared/keyvault.bicep b/infra/shared/keyvault.bicep new file mode 100644 index 0000000..f84f750 --- /dev/null +++ b/infra/shared/keyvault.bicep @@ -0,0 +1,31 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Service principal that should be granted read access to the KeyVault. If unset, no service principal is granted access by default') +param principalId string = '' + +var defaultAccessPolicies = !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } +] : [] + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + enabledForTemplateDeployment: true + accessPolicies: union(defaultAccessPolicies, [ + // define access policies here + ]) + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/infra/shared/monitoring.bicep b/infra/shared/monitoring.bicep new file mode 100644 index 0000000..4ae9796 --- /dev/null +++ b/infra/shared/monitoring.bicep @@ -0,0 +1,34 @@ +param logAnalyticsName string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: logAnalyticsName + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: applicationInsightsName + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalytics.id + } +} + +output applicationInsightsName string = applicationInsights.name +output logAnalyticsWorkspaceId string = logAnalytics.id +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/infra/shared/registry.bicep b/infra/shared/registry.bicep new file mode 100644 index 0000000..d6629f8 --- /dev/null +++ b/infra/shared/registry.bicep @@ -0,0 +1,36 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param adminUserEnabled bool = true +param anonymousPullEnabled bool = false +param dataEndpointEnabled bool = false +param encryption object = { + status: 'disabled' +} +param networkRuleBypassOptions string = 'AzureServices' +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'Standard' +} +param zoneRedundancy string = 'Disabled' + +// 2023-01-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/next-steps.md b/next-steps.md new file mode 100644 index 0000000..9820cde --- /dev/null +++ b/next-steps.md @@ -0,0 +1,95 @@ +# Next Steps after `azd init` + +## Table of Contents + +1. [Next Steps](#next-steps) +2. [What was added](#what-was-added) +3. [Billing](#billing) +4. [Troubleshooting](#troubleshooting) + +## Next Steps + +### Provision infrastructure and deploy application code + +Run `azd up` to provision your infrastructure and deploy to Azure (or run `azd provision` then `azd deploy` to accomplish the tasks separately). Visit the service endpoints listed to see your application up-and-running! + +To troubleshoot any issues, see [troubleshooting](#troubleshooting). + +### Configure environment variables for running services + +Configure environment variables for running services by updating `settings` in [main.parameters.json](./infra/main.parameters.json). + +### Configure CI/CD pipeline + +1. Create a workflow pipeline file locally. The following starters are available: + - [Deploy with GitHub Actions](https://github.com/Azure-Samples/azd-starter-bicep/blob/main/.github/workflows/azure-dev.yml) + - [Deploy with Azure Pipelines](https://github.com/Azure-Samples/azd-starter-bicep/blob/main/.azdo/pipelines/azure-dev.yml) +2. Run `azd pipeline config` to configure the deployment pipeline to connect securely to Azure. + +## What was added + +### Infrastructure configuration + +To describe the infrastructure and application, `azure.yaml` along with Infrastructure as Code files using Bicep were added with the following directory structure: + +```yaml +- azure.yaml # azd project configuration +- infra/ # Infrastructure as Code (bicep) files + - main.bicep # main deployment module + - app/ # Application resource modules + - shared/ # Shared resource modules + - modules/ # Library modules +``` + +Each bicep file declares resources to be provisioned. The resources are provisioned when running `azd up` or `azd provision`. + +- [app/Store.bicep](./infra/app/Store.bicep) - Azure Container Apps resources to host the 'Store' service. +- [app/Store.InventoryApi.bicep](./infra/app/Store.InventoryApi.bicep) - Azure Container Apps resources to host the 'Store.InventoryApi' service. +- [app/Store.ProductApi.bicep](./infra/app/Store.ProductApi.bicep) - Azure Container Apps resources to host the 'Store.ProductApi' service. +- [shared/keyvault.bicep](./infra/shared/keyvault.bicep) - Azure KeyVault to store secrets. +- [shared/monitoring.bicep](./infra/shared/monitoring.bicep) - Azure Log Analytics workspace and Application Insights to log and store instrumentation logs. +- [shared/registry.bicep](./infra/shared/registry.bicep) - Azure Container Registry to store docker images. + +More information about [Bicep](https://aka.ms/bicep) language. + +### Build from source (no Dockerfile) + +#### Build with Buildpacks using Oryx + +If your project does not contain a Dockerfile, we will use [Buildpacks](https://buildpacks.io/) using [Oryx](https://github.com/microsoft/Oryx/blob/main/doc/README.md) to create an image for the services in `azure.yaml` and get your containerized app onto Azure. + +To produce and run the docker image locally: + +1. Run `azd package` to build the image. +2. Copy the *Image Tag* shown. +3. Run `docker run -it ` to run the image locally. + +#### Exposed port + +Oryx will automatically set `PORT` to a default value of `80` (port `8080` for Java). Additionally, it will auto-configure supported web servers such as `gunicorn` and `ASP .NET Core` to listen to the target `PORT`. If your application already listens to the port specified by the `PORT` variable, the application will work out-of-the-box. Otherwise, you may need to perform one of the steps below: + +1. Update your application code or configuration to listen to the port specified by the `PORT` variable +1. (Alternatively) Search for `targetPort` in a .bicep file under the `infra/app` folder, and update the variable to match the port used by the application. + +## Billing + +Visit the *Cost Management + Billing* page in Azure Portal to track current spend. For more information about how you're billed, and how you can monitor the costs incurred in your Azure subscriptions, visit [billing overview](https://learn.microsoft.com/azure/developer/intro/azure-developer-billing). + +## Troubleshooting + +Q: I visited the service endpoint listed, and I'm seeing a blank page, a generic welcome page, or an error page. + +A: Your service may have failed to start, or it may be missing some configuration settings. To investigate further: + +1. Run `azd show`. Click on the link under "View in Azure Portal" to open the resource group in Azure Portal. +2. Navigate to the specific Container App service that is failing to deploy. +3. Click on the failing revision under "Revisions with Issues". +4. Review "Status details" for more information about the type of failure. +5. Observe the log outputs from Console log stream and System log stream to identify any errors. +6. If logs are written to disk, use *Console* in the navigation to connect to a shell within the running container. + +For more troubleshooting information, visit [Container Apps troubleshooting](https://learn.microsoft.com/azure/container-apps/troubleshooting). + +### Additional information + +For additional information about setting up your `azd` project, visit our official [docs](https://learn.microsoft.com/azure/developer/azure-developer-cli/make-azd-compatible?pivots=azd-convert). From c6ce844b75a2e697246ed7e6b359fcce3623c2da Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Thu, 8 Aug 2024 10:25:46 -0400 Subject: [PATCH 2/8] folder structure maintenance --- azure.yaml | 6 +++--- .../ApplicationMapNodeNameInitializer.cs | 0 {Monitoring => src/Monitoring}/Monitoring.csproj | 0 .../Store.InventoryApi}/Dockerfile | 0 .../Store.InventoryApi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../Store.InventoryApi}/Store.InventoryApi.csproj | 0 .../appsettings.Development.json | 0 .../Store.InventoryApi}/appsettings.json | 0 .../Store.ProductApi}/Dockerfile | 0 .../Store.ProductApi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../Store.ProductApi}/Store.ProductApi.csproj | 0 .../Store.ProductApi}/appsettings.Development.json | 0 .../Store.ProductApi}/appsettings.json | 0 Store.sln => src/Store.sln | 0 {Store => src/Store}/App.razor | 0 {Store => src/Store}/Dockerfile | 4 ++-- {Store => src/Store}/Pages/Error.cshtml | 0 {Store => src/Store}/Pages/Error.cshtml.cs | 0 {Store => src/Store}/Pages/Index.razor | 0 {Store => src/Store}/Pages/_Host.cshtml | 0 {Store => src/Store}/Pages/_Layout.cshtml | 0 {Store => src/Store}/Program.cs | 0 {Store => src/Store}/Properties/launchSettings.json | 0 {Store => src/Store}/Shared/MainLayout.razor | 0 {Store => src/Store}/Shared/MainLayout.razor.css | 0 {Store => src/Store}/Shared/SurveyPrompt.razor | 0 {Store => src/Store}/Store.csproj | 0 {Store => src/Store}/_Imports.razor | 0 {Store => src/Store}/appsettings.Development.json | 0 {Store => src/Store}/appsettings.json | 0 .../Store}/wwwroot/css/bootstrap/bootstrap.min.css | 0 .../wwwroot/css/bootstrap/bootstrap.min.css.map | 0 .../Store}/wwwroot/css/open-iconic/FONT-LICENSE | 0 .../Store}/wwwroot/css/open-iconic/ICON-LICENSE | 0 .../Store}/wwwroot/css/open-iconic/README.md | 0 .../font/css/open-iconic-bootstrap.min.css | 0 .../css/open-iconic/font/fonts/open-iconic.eot | Bin .../css/open-iconic/font/fonts/open-iconic.otf | Bin .../css/open-iconic/font/fonts/open-iconic.svg | 0 .../css/open-iconic/font/fonts/open-iconic.ttf | Bin .../css/open-iconic/font/fonts/open-iconic.woff | Bin {Store => src/Store}/wwwroot/css/site.css | 0 {Store => src/Store}/wwwroot/favicon.ico | Bin 45 files changed, 5 insertions(+), 5 deletions(-) rename {Monitoring => src/Monitoring}/ApplicationMapNodeNameInitializer.cs (100%) rename {Monitoring => src/Monitoring}/Monitoring.csproj (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/Dockerfile (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/Program.cs (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/Properties/launchSettings.json (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/Store.InventoryApi.csproj (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/appsettings.Development.json (100%) rename {Store.InventoryApi => src/Store.InventoryApi}/appsettings.json (100%) rename {Store.ProductApi => src/Store.ProductApi}/Dockerfile (100%) rename {Store.ProductApi => src/Store.ProductApi}/Program.cs (100%) rename {Store.ProductApi => src/Store.ProductApi}/Properties/launchSettings.json (100%) rename {Store.ProductApi => src/Store.ProductApi}/Store.ProductApi.csproj (100%) rename {Store.ProductApi => src/Store.ProductApi}/appsettings.Development.json (100%) rename {Store.ProductApi => src/Store.ProductApi}/appsettings.json (100%) rename Store.sln => src/Store.sln (100%) rename {Store => src/Store}/App.razor (100%) rename {Store => src/Store}/Dockerfile (86%) rename {Store => src/Store}/Pages/Error.cshtml (100%) rename {Store => src/Store}/Pages/Error.cshtml.cs (100%) rename {Store => src/Store}/Pages/Index.razor (100%) rename {Store => src/Store}/Pages/_Host.cshtml (100%) rename {Store => src/Store}/Pages/_Layout.cshtml (100%) rename {Store => src/Store}/Program.cs (100%) rename {Store => src/Store}/Properties/launchSettings.json (100%) rename {Store => src/Store}/Shared/MainLayout.razor (100%) rename {Store => src/Store}/Shared/MainLayout.razor.css (100%) rename {Store => src/Store}/Shared/SurveyPrompt.razor (100%) rename {Store => src/Store}/Store.csproj (100%) rename {Store => src/Store}/_Imports.razor (100%) rename {Store => src/Store}/appsettings.Development.json (100%) rename {Store => src/Store}/appsettings.json (100%) rename {Store => src/Store}/wwwroot/css/bootstrap/bootstrap.min.css (100%) rename {Store => src/Store}/wwwroot/css/bootstrap/bootstrap.min.css.map (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/FONT-LICENSE (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/ICON-LICENSE (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/README.md (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/fonts/open-iconic.eot (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/fonts/open-iconic.otf (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/fonts/open-iconic.svg (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf (100%) rename {Store => src/Store}/wwwroot/css/open-iconic/font/fonts/open-iconic.woff (100%) rename {Store => src/Store}/wwwroot/css/site.css (100%) rename {Store => src/Store}/wwwroot/favicon.ico (100%) diff --git a/azure.yaml b/azure.yaml index 6a78abc..b2562c4 100644 --- a/azure.yaml +++ b/azure.yaml @@ -5,19 +5,19 @@ metadata: template: azd-init@1.9.5 services: Store: - project: Store + project: ./src/Store host: containerapp language: dotnet docker: path: Dockerfile Store.InventoryApi: - project: Store.InventoryApi + project: ./src/Store.InventoryApi host: containerapp language: dotnet docker: path: Dockerfile Store.ProductApi: - project: Store.ProductApi + project: ./src/Store.ProductApi host: containerapp language: dotnet docker: diff --git a/Monitoring/ApplicationMapNodeNameInitializer.cs b/src/Monitoring/ApplicationMapNodeNameInitializer.cs similarity index 100% rename from Monitoring/ApplicationMapNodeNameInitializer.cs rename to src/Monitoring/ApplicationMapNodeNameInitializer.cs diff --git a/Monitoring/Monitoring.csproj b/src/Monitoring/Monitoring.csproj similarity index 100% rename from Monitoring/Monitoring.csproj rename to src/Monitoring/Monitoring.csproj diff --git a/Store.InventoryApi/Dockerfile b/src/Store.InventoryApi/Dockerfile similarity index 100% rename from Store.InventoryApi/Dockerfile rename to src/Store.InventoryApi/Dockerfile diff --git a/Store.InventoryApi/Program.cs b/src/Store.InventoryApi/Program.cs similarity index 100% rename from Store.InventoryApi/Program.cs rename to src/Store.InventoryApi/Program.cs diff --git a/Store.InventoryApi/Properties/launchSettings.json b/src/Store.InventoryApi/Properties/launchSettings.json similarity index 100% rename from Store.InventoryApi/Properties/launchSettings.json rename to src/Store.InventoryApi/Properties/launchSettings.json diff --git a/Store.InventoryApi/Store.InventoryApi.csproj b/src/Store.InventoryApi/Store.InventoryApi.csproj similarity index 100% rename from Store.InventoryApi/Store.InventoryApi.csproj rename to src/Store.InventoryApi/Store.InventoryApi.csproj diff --git a/Store.InventoryApi/appsettings.Development.json b/src/Store.InventoryApi/appsettings.Development.json similarity index 100% rename from Store.InventoryApi/appsettings.Development.json rename to src/Store.InventoryApi/appsettings.Development.json diff --git a/Store.InventoryApi/appsettings.json b/src/Store.InventoryApi/appsettings.json similarity index 100% rename from Store.InventoryApi/appsettings.json rename to src/Store.InventoryApi/appsettings.json diff --git a/Store.ProductApi/Dockerfile b/src/Store.ProductApi/Dockerfile similarity index 100% rename from Store.ProductApi/Dockerfile rename to src/Store.ProductApi/Dockerfile diff --git a/Store.ProductApi/Program.cs b/src/Store.ProductApi/Program.cs similarity index 100% rename from Store.ProductApi/Program.cs rename to src/Store.ProductApi/Program.cs diff --git a/Store.ProductApi/Properties/launchSettings.json b/src/Store.ProductApi/Properties/launchSettings.json similarity index 100% rename from Store.ProductApi/Properties/launchSettings.json rename to src/Store.ProductApi/Properties/launchSettings.json diff --git a/Store.ProductApi/Store.ProductApi.csproj b/src/Store.ProductApi/Store.ProductApi.csproj similarity index 100% rename from Store.ProductApi/Store.ProductApi.csproj rename to src/Store.ProductApi/Store.ProductApi.csproj diff --git a/Store.ProductApi/appsettings.Development.json b/src/Store.ProductApi/appsettings.Development.json similarity index 100% rename from Store.ProductApi/appsettings.Development.json rename to src/Store.ProductApi/appsettings.Development.json diff --git a/Store.ProductApi/appsettings.json b/src/Store.ProductApi/appsettings.json similarity index 100% rename from Store.ProductApi/appsettings.json rename to src/Store.ProductApi/appsettings.json diff --git a/Store.sln b/src/Store.sln similarity index 100% rename from Store.sln rename to src/Store.sln diff --git a/Store/App.razor b/src/Store/App.razor similarity index 100% rename from Store/App.razor rename to src/Store/App.razor diff --git a/Store/Dockerfile b/src/Store/Dockerfile similarity index 86% rename from Store/Dockerfile rename to src/Store/Dockerfile index 9ae95d4..afdbefb 100644 --- a/Store/Dockerfile +++ b/src/Store/Dockerfile @@ -6,8 +6,8 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src -COPY ["Store/Store.csproj", "Store/"] -COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] +COPY ["Store.csproj", "Store/"] +COPY ["Monitoring.csproj", "Monitoring/"] RUN dotnet restore "Store/Store.csproj" COPY . . WORKDIR "/src/Store" diff --git a/Store/Pages/Error.cshtml b/src/Store/Pages/Error.cshtml similarity index 100% rename from Store/Pages/Error.cshtml rename to src/Store/Pages/Error.cshtml diff --git a/Store/Pages/Error.cshtml.cs b/src/Store/Pages/Error.cshtml.cs similarity index 100% rename from Store/Pages/Error.cshtml.cs rename to src/Store/Pages/Error.cshtml.cs diff --git a/Store/Pages/Index.razor b/src/Store/Pages/Index.razor similarity index 100% rename from Store/Pages/Index.razor rename to src/Store/Pages/Index.razor diff --git a/Store/Pages/_Host.cshtml b/src/Store/Pages/_Host.cshtml similarity index 100% rename from Store/Pages/_Host.cshtml rename to src/Store/Pages/_Host.cshtml diff --git a/Store/Pages/_Layout.cshtml b/src/Store/Pages/_Layout.cshtml similarity index 100% rename from Store/Pages/_Layout.cshtml rename to src/Store/Pages/_Layout.cshtml diff --git a/Store/Program.cs b/src/Store/Program.cs similarity index 100% rename from Store/Program.cs rename to src/Store/Program.cs diff --git a/Store/Properties/launchSettings.json b/src/Store/Properties/launchSettings.json similarity index 100% rename from Store/Properties/launchSettings.json rename to src/Store/Properties/launchSettings.json diff --git a/Store/Shared/MainLayout.razor b/src/Store/Shared/MainLayout.razor similarity index 100% rename from Store/Shared/MainLayout.razor rename to src/Store/Shared/MainLayout.razor diff --git a/Store/Shared/MainLayout.razor.css b/src/Store/Shared/MainLayout.razor.css similarity index 100% rename from Store/Shared/MainLayout.razor.css rename to src/Store/Shared/MainLayout.razor.css diff --git a/Store/Shared/SurveyPrompt.razor b/src/Store/Shared/SurveyPrompt.razor similarity index 100% rename from Store/Shared/SurveyPrompt.razor rename to src/Store/Shared/SurveyPrompt.razor diff --git a/Store/Store.csproj b/src/Store/Store.csproj similarity index 100% rename from Store/Store.csproj rename to src/Store/Store.csproj diff --git a/Store/_Imports.razor b/src/Store/_Imports.razor similarity index 100% rename from Store/_Imports.razor rename to src/Store/_Imports.razor diff --git a/Store/appsettings.Development.json b/src/Store/appsettings.Development.json similarity index 100% rename from Store/appsettings.Development.json rename to src/Store/appsettings.Development.json diff --git a/Store/appsettings.json b/src/Store/appsettings.json similarity index 100% rename from Store/appsettings.json rename to src/Store/appsettings.json diff --git a/Store/wwwroot/css/bootstrap/bootstrap.min.css b/src/Store/wwwroot/css/bootstrap/bootstrap.min.css similarity index 100% rename from Store/wwwroot/css/bootstrap/bootstrap.min.css rename to src/Store/wwwroot/css/bootstrap/bootstrap.min.css diff --git a/Store/wwwroot/css/bootstrap/bootstrap.min.css.map b/src/Store/wwwroot/css/bootstrap/bootstrap.min.css.map similarity index 100% rename from Store/wwwroot/css/bootstrap/bootstrap.min.css.map rename to src/Store/wwwroot/css/bootstrap/bootstrap.min.css.map diff --git a/Store/wwwroot/css/open-iconic/FONT-LICENSE b/src/Store/wwwroot/css/open-iconic/FONT-LICENSE similarity index 100% rename from Store/wwwroot/css/open-iconic/FONT-LICENSE rename to src/Store/wwwroot/css/open-iconic/FONT-LICENSE diff --git a/Store/wwwroot/css/open-iconic/ICON-LICENSE b/src/Store/wwwroot/css/open-iconic/ICON-LICENSE similarity index 100% rename from Store/wwwroot/css/open-iconic/ICON-LICENSE rename to src/Store/wwwroot/css/open-iconic/ICON-LICENSE diff --git a/Store/wwwroot/css/open-iconic/README.md b/src/Store/wwwroot/css/open-iconic/README.md similarity index 100% rename from Store/wwwroot/css/open-iconic/README.md rename to src/Store/wwwroot/css/open-iconic/README.md diff --git a/Store/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/src/Store/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css similarity index 100% rename from Store/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css rename to src/Store/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css diff --git a/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.eot similarity index 100% rename from Store/wwwroot/css/open-iconic/font/fonts/open-iconic.eot rename to src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.eot diff --git a/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.otf similarity index 100% rename from Store/wwwroot/css/open-iconic/font/fonts/open-iconic.otf rename to src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.otf diff --git a/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.svg similarity index 100% rename from Store/wwwroot/css/open-iconic/font/fonts/open-iconic.svg rename to src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.svg diff --git a/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf similarity index 100% rename from Store/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf rename to src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf diff --git a/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.woff similarity index 100% rename from Store/wwwroot/css/open-iconic/font/fonts/open-iconic.woff rename to src/Store/wwwroot/css/open-iconic/font/fonts/open-iconic.woff diff --git a/Store/wwwroot/css/site.css b/src/Store/wwwroot/css/site.css similarity index 100% rename from Store/wwwroot/css/site.css rename to src/Store/wwwroot/css/site.css diff --git a/Store/wwwroot/favicon.ico b/src/Store/wwwroot/favicon.ico similarity index 100% rename from Store/wwwroot/favicon.ico rename to src/Store/wwwroot/favicon.ico From 1a3025203abed1633e49533425fa7dc5cf2f3ead Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Thu, 8 Aug 2024 21:58:16 -0400 Subject: [PATCH 3/8] updated bicep and docker --- azure.yaml | 20 +++++++++++++------- infra/app/Store.InventoryApi.bicep | 12 ++++++++++-- infra/app/Store.ProductApi.bicep | 12 ++++++++++-- infra/app/Store.bicep | 12 ++++++++++-- src/Store.InventoryApi/Dockerfile | 2 +- src/Store.ProductApi/Dockerfile | 2 +- src/Store/Dockerfile | 12 +++++++----- 7 files changed, 52 insertions(+), 20 deletions(-) diff --git a/azure.yaml b/azure.yaml index b2562c4..6001190 100644 --- a/azure.yaml +++ b/azure.yaml @@ -2,23 +2,29 @@ name: dotNET-FrontEnd-to-BackEnd-with-DAPR-on-Azure-Container-Apps metadata: - template: azd-init@1.9.5 + template: frontend-backend-dapr-aca@1.0.0 services: Store: - project: ./src/Store + project: src/Store host: containerapp language: dotnet docker: - path: Dockerfile + # These paths are relative to the project directory + path: ./Dockerfile + context: ./../ Store.InventoryApi: - project: ./src/Store.InventoryApi + project: src/Store.InventoryApi host: containerapp language: dotnet docker: - path: Dockerfile + # These paths are relative to the project directory + path: ./Dockerfile + context: ./../ Store.ProductApi: - project: ./src/Store.ProductApi + project: src/Store.ProductApi host: containerapp language: dotnet docker: - path: Dockerfile + # These paths are relative to the project directory + path: ./Dockerfile + context: ./../ diff --git a/infra/app/Store.InventoryApi.bicep b/infra/app/Store.InventoryApi.bicep index 78dcbe6..34e0d79 100644 --- a/infra/app/Store.InventoryApi.bicep +++ b/infra/app/Store.InventoryApi.bicep @@ -58,10 +58,18 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { properties: { managedEnvironmentId: containerAppsEnvironment.id configuration: { + dapr: { + enabled: true + appId: name + appPort: 80 + appProtocol: 'http' + } + activeRevisionsMode: 'single' ingress: { - external: false + external: true targetPort: 80 - transport: 'auto' + transport: 'http' + allowInsecure: true } registries: [ { diff --git a/infra/app/Store.ProductApi.bicep b/infra/app/Store.ProductApi.bicep index 5aa2d90..d402dd7 100644 --- a/infra/app/Store.ProductApi.bicep +++ b/infra/app/Store.ProductApi.bicep @@ -58,10 +58,18 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { properties: { managedEnvironmentId: containerAppsEnvironment.id configuration: { + dapr: { + enabled: true + appId: name + appPort: 80 + appProtocol: 'http' + } + activeRevisionsMode: 'single' ingress: { - external: false + external: true targetPort: 80 - transport: 'auto' + transport: 'http' + allowInsecure: true } registries: [ { diff --git a/infra/app/Store.bicep b/infra/app/Store.bicep index d59db2f..9dbce58 100644 --- a/infra/app/Store.bicep +++ b/infra/app/Store.bicep @@ -58,10 +58,18 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { properties: { managedEnvironmentId: containerAppsEnvironment.id configuration: { + dapr: { + enabled: true + appId: name + appPort: 80 + appProtocol: 'http' + } + activeRevisionsMode: 'single' ingress: { external: true targetPort: 80 - transport: 'auto' + transport: 'http' + allowInsecure: true } registries: [ { @@ -74,7 +82,7 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { containers: [ { image: fetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' - name: 'main' + name: name env: [ { name: 'ASPNETCORE_ENVIRONMENT' diff --git a/src/Store.InventoryApi/Dockerfile b/src/Store.InventoryApi/Dockerfile index 575f57c..16bb1f0 100644 --- a/src/Store.InventoryApi/Dockerfile +++ b/src/Store.InventoryApi/Dockerfile @@ -7,7 +7,7 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Store.InventoryApi/Store.InventoryApi.csproj", "Store.InventoryApi/"] -COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] +#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] RUN dotnet restore "Store.InventoryApi/Store.InventoryApi.csproj" COPY . . WORKDIR "/src/Store.InventoryApi" diff --git a/src/Store.ProductApi/Dockerfile b/src/Store.ProductApi/Dockerfile index b5062f9..cc1d634 100644 --- a/src/Store.ProductApi/Dockerfile +++ b/src/Store.ProductApi/Dockerfile @@ -7,7 +7,7 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Store.ProductApi/Store.ProductApi.csproj", "Store.ProductApi/"] -COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] +#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] RUN dotnet restore "Store.ProductApi/Store.ProductApi.csproj" COPY . . WORKDIR "/src/Store.ProductApi" diff --git a/src/Store/Dockerfile b/src/Store/Dockerfile index afdbefb..393cf5c 100644 --- a/src/Store/Dockerfile +++ b/src/Store/Dockerfile @@ -6,15 +6,17 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src -COPY ["Store.csproj", "Store/"] -COPY ["Monitoring.csproj", "Monitoring/"] -RUN dotnet restore "Store/Store.csproj" +COPY ["Store/Store.csproj", "Store/"] +#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] +RUN dotnet restore "./Store/Store.csproj" + +# Build and publish COPY . . WORKDIR "/src/Store" -RUN dotnet build "Store.csproj" -c Release -o /app/build +RUN dotnet build "./Store.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "Store.csproj" -c Release -o /app/publish +RUN dotnet publish "./Store.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app From 826a190be77014b43499e1abeb3da23777a33758 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Fri, 9 Aug 2024 10:45:41 -0400 Subject: [PATCH 4/8] Configure Azure Developer Pipeline --- .github/workflows/azure-dev.yml | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/azure-dev.yml diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml new file mode 100644 index 0000000..6da1a44 --- /dev/null +++ b/.github/workflows/azure-dev.yml @@ -0,0 +1,64 @@ +on: + workflow_dispatch: + push: + # Run when commits are pushed to mainline branch (main or master) + # Set this to the mainline branch you are using + branches: + - main + - master + +# GitHub Actions workflow to deploy to Azure using azd +# To configure required secrets for connecting to Azure, simply run `azd pipeline config` + +# Set up permissions for deploying with secretless Azure federated credentials +# 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 + env: + AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} + AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} + AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install azd + uses: Azure/setup-azd@v1.0.0 + + - name: Log in with Azure (Federated Credentials) + if: ${{ env.AZURE_CLIENT_ID != '' }} + run: | + azd auth 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 auth login ` + --client-id "$($info.clientId)" ` + --client-secret "$($info.clientSecret)" ` + --tenant-id "$($info.tenantId)" + shell: pwsh + env: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Provision Infrastructure + run: azd provision --no-prompt + env: + AZD_INITIAL_ENVIRONMENT_CONFIG: ${{ secrets.AZD_INITIAL_ENVIRONMENT_CONFIG }} + + - name: Deploy Application + run: azd deploy --no-prompt \ No newline at end of file From 4b4d172cee7c5940f9525493c1f26a389ee44059 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Fri, 9 Aug 2024 12:12:48 -0400 Subject: [PATCH 5/8] updated solution to dotnet 8 --- infra/app/Store.InventoryApi.bicep | 2 +- infra/app/Store.ProductApi.bicep | 2 +- infra/app/Store.bicep | 2 +- src/Monitoring/Monitoring.csproj | 4 ++-- src/Store.InventoryApi/Store.InventoryApi.csproj | 6 +++--- src/Store.ProductApi/Store.ProductApi.csproj | 8 ++++---- src/Store/Store.csproj | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/infra/app/Store.InventoryApi.bicep b/infra/app/Store.InventoryApi.bicep index 34e0d79..e585ab9 100644 --- a/infra/app/Store.InventoryApi.bicep +++ b/infra/app/Store.InventoryApi.bicep @@ -60,7 +60,7 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { configuration: { dapr: { enabled: true - appId: name + appId: 'inventory' appPort: 80 appProtocol: 'http' } diff --git a/infra/app/Store.ProductApi.bicep b/infra/app/Store.ProductApi.bicep index d402dd7..7fd8cf3 100644 --- a/infra/app/Store.ProductApi.bicep +++ b/infra/app/Store.ProductApi.bicep @@ -60,7 +60,7 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { configuration: { dapr: { enabled: true - appId: name + appId: 'products' appPort: 80 appProtocol: 'http' } diff --git a/infra/app/Store.bicep b/infra/app/Store.bicep index 9dbce58..73fc2df 100644 --- a/infra/app/Store.bicep +++ b/infra/app/Store.bicep @@ -60,7 +60,7 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = { configuration: { dapr: { enabled: true - appId: name + appId: 'store' appPort: 80 appProtocol: 'http' } diff --git a/src/Monitoring/Monitoring.csproj b/src/Monitoring/Monitoring.csproj index 75fb3f0..193f415 100644 --- a/src/Monitoring/Monitoring.csproj +++ b/src/Monitoring/Monitoring.csproj @@ -1,13 +1,13 @@ - net6.0 + net8.0 enable enable - + diff --git a/src/Store.InventoryApi/Store.InventoryApi.csproj b/src/Store.InventoryApi/Store.InventoryApi.csproj index 4f65a86..55286b4 100644 --- a/src/Store.InventoryApi/Store.InventoryApi.csproj +++ b/src/Store.InventoryApi/Store.InventoryApi.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable Linux @@ -9,8 +9,8 @@ - - + + diff --git a/src/Store.ProductApi/Store.ProductApi.csproj b/src/Store.ProductApi/Store.ProductApi.csproj index 9ce4fb9..63de460 100644 --- a/src/Store.ProductApi/Store.ProductApi.csproj +++ b/src/Store.ProductApi/Store.ProductApi.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable Linux @@ -9,9 +9,9 @@ - - - + + + diff --git a/src/Store/Store.csproj b/src/Store/Store.csproj index 1e7aac6..dbd5010 100644 --- a/src/Store/Store.csproj +++ b/src/Store/Store.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable Linux @@ -9,8 +9,8 @@ - - + + From 9122aa03ce4e30711998265f700268f9e08f5650 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Fri, 9 Aug 2024 12:17:26 -0400 Subject: [PATCH 6/8] removed old workflow --- .github/workflows/deploy.yml | 148 ----------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 2c70762..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Build and deploy .NET application to Container App silo - -on: - push: - branches: - - deploy - -env: - - # alphanumeric string under 14 characters - RESOURCE_GROUP_NAME: store - - # specify your preferred region - REGION: eastus - - STORE_DOCKER: Store/Dockerfile - STORE_IMAGE: store - - INVENTORY_DOCKER: Store.InventoryApi/Dockerfile - INVENTORY_IMAGE: inventory - - PRODUCTS_DOCKER: Store.ProductApi/Dockerfile - PRODUCTS_IMAGE: products - -jobs: - provision: - runs-on: ubuntu-latest - - steps: - - - name: Checkout to the branch - uses: actions/checkout@v2 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AzureSPN }} - - - name: Create resource group - uses: azure/CLI@v1 - with: - inlineScript: > - echo "Creating resource group in Azure" - echo "Executing 'az group create -l ${{ env.REGION }} -n ${{ env.RESOURCE_GROUP_NAME }}'" - - az group create -l ${{ env.REGION }} -n ${{ env.RESOURCE_GROUP_NAME }} - - - name: Creating resources - uses: azure/CLI@v1 - with: - inlineScript: > - echo "Creating resources" - - az deployment group create --resource-group ${{ env.RESOURCE_GROUP_NAME }} --template-file '/github/workspace/Azure/main.bicep' --debug - - build: - runs-on: ubuntu-latest - needs: provision - - steps: - - - name: Checkout to the branch - uses: actions/checkout@v2 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AzureSPN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to ACR - run: | - set -euo pipefail - access_token=$(az account get-access-token --query accessToken -o tsv) - refresh_token=$(curl https://${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/oauth2/exchange -v -d "grant_type=access_token&service=${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io&access_token=$access_token" | jq -r .refresh_token) - docker login -u 00000000-0000-0000-0000-000000000000 --password-stdin ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io <<< "$refresh_token" - - - name: Build the products api image and push it to ACR - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.PRODUCTS_IMAGE }}:${{ github.sha }} - file: ${{ env.PRODUCTS_DOCKER }} - - - name: Build the inventory api image and push it to ACR - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.INVENTORY_IMAGE }}:${{ github.sha }} - file: ${{ env.INVENTORY_DOCKER }} - - - name: Build the frontend image and push it to ACR - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.STORE_IMAGE }}:${{ github.sha }} - file: ${{ env.STORE_DOCKER }} - - deploy: - runs-on: ubuntu-latest - needs: build - - steps: - - - name: Checkout to the branch - uses: actions/checkout@v2 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AzureSPN }} - - - name: Installing Container Apps extension - uses: azure/CLI@v1 - with: - inlineScript: > - az config set extension.use_dynamic_install=yes_without_prompt - - az extension add --name containerapp --yes - - - name: Login to ACR - run: | - set -euo pipefail - access_token=$(az account get-access-token --query accessToken -o tsv) - refresh_token=$(curl https://${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/oauth2/exchange -v -d "grant_type=access_token&service=${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io&access_token=$access_token" | jq -r .refresh_token) - docker login -u 00000000-0000-0000-0000-000000000000 --password-stdin ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io <<< "$refresh_token" - - - name: Deploy Container Apps - uses: azure/CLI@v1 - with: - inlineScript: > - az containerapp registry set -n products -g ${{ env.RESOURCE_GROUP_NAME }} --server ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io - - az containerapp update -n products -g ${{ env.RESOURCE_GROUP_NAME }} -i ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.PRODUCTS_IMAGE }}:${{ github.sha }} - - az containerapp registry set -n inventory -g ${{ env.RESOURCE_GROUP_NAME }} --server ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io - - az containerapp update -n inventory -g ${{ env.RESOURCE_GROUP_NAME }} -i ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.INVENTORY_IMAGE }}:${{ github.sha }} - - az containerapp registry set -n store -g ${{ env.RESOURCE_GROUP_NAME }} --server ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io - - az containerapp update -n store -g ${{ env.RESOURCE_GROUP_NAME }} -i ${{ env.RESOURCE_GROUP_NAME }}acr.azurecr.io/${{ env.STORE_IMAGE }}:${{ github.sha }} - - - name: logout - run: > - az logout From 256662d43d34fcb0d667deaacd3f025bf2e75666 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Fri, 9 Aug 2024 14:12:05 -0400 Subject: [PATCH 7/8] cleanup and revert --- infra/main.bicep | 12 ------------ src/Monitoring/Monitoring.csproj | 2 +- src/Store.InventoryApi/Dockerfile | 1 - src/Store.InventoryApi/Store.InventoryApi.csproj | 2 +- src/Store.ProductApi/Dockerfile | 1 - src/Store.ProductApi/Store.ProductApi.csproj | 2 +- src/Store/Dockerfile | 1 - src/Store/Store.csproj | 2 +- 8 files changed, 4 insertions(+), 19 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 0cb2c8c..7965338 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -63,16 +63,6 @@ module registry './shared/registry.bicep' = { scope: rg } -module keyVault './shared/keyvault.bicep' = { - name: 'keyvault' - params: { - location: location - tags: tags - name: '${abbrs.keyVaultVaults}${resourceToken}' - } - scope: rg -} - module appsEnv './shared/apps-env.bicep' = { name: 'apps-env' params: { @@ -131,5 +121,3 @@ module storeProductApi './app/Store.ProductApi.bicep' = { } output AZURE_CONTAINER_REGISTRY_ENDPOINT string = registry.outputs.loginServer -output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name -output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint diff --git a/src/Monitoring/Monitoring.csproj b/src/Monitoring/Monitoring.csproj index 193f415..13f878e 100644 --- a/src/Monitoring/Monitoring.csproj +++ b/src/Monitoring/Monitoring.csproj @@ -1,7 +1,7 @@ - net8.0 + net6.0 enable enable diff --git a/src/Store.InventoryApi/Dockerfile b/src/Store.InventoryApi/Dockerfile index 16bb1f0..183a798 100644 --- a/src/Store.InventoryApi/Dockerfile +++ b/src/Store.InventoryApi/Dockerfile @@ -7,7 +7,6 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Store.InventoryApi/Store.InventoryApi.csproj", "Store.InventoryApi/"] -#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] RUN dotnet restore "Store.InventoryApi/Store.InventoryApi.csproj" COPY . . WORKDIR "/src/Store.InventoryApi" diff --git a/src/Store.InventoryApi/Store.InventoryApi.csproj b/src/Store.InventoryApi/Store.InventoryApi.csproj index 55286b4..ccc9342 100644 --- a/src/Store.InventoryApi/Store.InventoryApi.csproj +++ b/src/Store.InventoryApi/Store.InventoryApi.csproj @@ -1,7 +1,7 @@  - net8.0 + net6.0 enable enable Linux diff --git a/src/Store.ProductApi/Dockerfile b/src/Store.ProductApi/Dockerfile index cc1d634..a130807 100644 --- a/src/Store.ProductApi/Dockerfile +++ b/src/Store.ProductApi/Dockerfile @@ -7,7 +7,6 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Store.ProductApi/Store.ProductApi.csproj", "Store.ProductApi/"] -#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] RUN dotnet restore "Store.ProductApi/Store.ProductApi.csproj" COPY . . WORKDIR "/src/Store.ProductApi" diff --git a/src/Store.ProductApi/Store.ProductApi.csproj b/src/Store.ProductApi/Store.ProductApi.csproj index 63de460..f9784a8 100644 --- a/src/Store.ProductApi/Store.ProductApi.csproj +++ b/src/Store.ProductApi/Store.ProductApi.csproj @@ -1,7 +1,7 @@  - net8.0 + net6.0 enable enable Linux diff --git a/src/Store/Dockerfile b/src/Store/Dockerfile index 393cf5c..e263c9f 100644 --- a/src/Store/Dockerfile +++ b/src/Store/Dockerfile @@ -7,7 +7,6 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Store/Store.csproj", "Store/"] -#COPY ["Monitoring/Monitoring.csproj", "Monitoring/"] RUN dotnet restore "./Store/Store.csproj" # Build and publish diff --git a/src/Store/Store.csproj b/src/Store/Store.csproj index dbd5010..1e944e0 100644 --- a/src/Store/Store.csproj +++ b/src/Store/Store.csproj @@ -1,7 +1,7 @@  - net8.0 + net6.0 enable enable Linux From a23499ec4e5c16ccbe43aa25465005df4d013b75 Mon Sep 17 00:00:00 2001 From: Kevin Kraus Date: Fri, 9 Aug 2024 14:42:50 -0400 Subject: [PATCH 8/8] updated readme --- README.md | 72 +++++++++++++------------------------------------------ 1 file changed, 16 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 5507e05..a148bdc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository contains a simple scenario built to demonstrate how ASP.NET Core * Store - A Blazor server project representing the frontend of an online store. The store's UI shows a list of all the products in the store, and their associated inventory status. * Products API - A simple API that generates fake product names using the open-source NuGet package [Bogus](https://github.com/bchavez/Bogus). * Inventory API - A simple API that provides a random number for a given product ID string. The values of each string/integer pair are stored in memory cache so they are consistent between API calls. -* Azure folder - contains Azure Bicep files used for creating and configuring all the Azure resources. +* Infra folder - contains Azure Bicep files used for creating and configuring all the Azure resources. * GitHub Actions workflow file used to deploy the app using CI/CD. ## What you'll learn @@ -23,7 +23,8 @@ You'll need an Azure subscription and a very small set of tools and skills to ge 1. An Azure subscription. Sign up [for free](https://azure.microsoft.com/free/). 2. A GitHub account, with access to GitHub Actions. -3. Either the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli) installed locally, or, access to [GitHub Codespaces](https://github.com/features/codespaces), which would enable you to do develop in your browser. +3. Either the [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-windows) installed locally, or, access to [GitHub Codespaces](https://github.com/features/codespaces), which would enable you to do develop in your browser. +4. Docker Desktop installed locally, or, access to a [Docker Desktop Codespace](https://docs.github.com/en/codespaces/getting-started/deep-dive) ## Topology diagram @@ -31,75 +32,34 @@ The resultant application is an Azure Container Environment-hosted set of contai ![Application topology](docs/media/topology.png) -Internet traffic should not be able to directly access either of the back-end APIs as each of these containers is marked as "internal ingress only" during the deployment phase. Internet traffic hitting the `store...azurecontainerapps.io` URL should be proxied to the `frontend` container, which in turn makes outbound calls to both the `products` and `inventory` APIs within the Azure Container Apps Environment. +Internet traffic should not be able to directly access either of the back-end APIs as each of these containers is marked as "internal ingress only" during the deployment phase. Internet traffic hitting the `...azurecontainerapps.io` URL should be proxied to the `frontend` container, which in turn makes outbound calls to both the `products` and `inventory` APIs within the Azure Container Apps Environment. ## Setup By the end of this section you'll have a 3-node app running in Azure. This setup process consists of two steps, and should take you around 15 minutes. -1. Use the Azure CLI to create an Azure Service Principal, then store that principal's JSON output to a GitHub secret so the GitHub Actions CI/CD process can log into your Azure subscription and deploy the code. -2. Edit the ` deploy.yml` workflow file and push the changes into a new `deploy` branch, triggering GitHub Actions to build the .NET projects into containers and push those containers into a new Azure Container Apps Environment. - -## Authenticate to Azure and configure the repository with a secret - 1. Fork this repository to your own GitHub organization. -2. Create an Azure Service Principal using the Azure CLI. - -```bash -$subscriptionId=$(az account show --query id --output tsv) -az ad sp create-for-rbac --sdk-auth --name WebAndApiSample --role contributor --scopes /subscriptions/$subscriptionId -``` -3. Copy the JSON written to the screen to your clipboard. - -```json -{ - "clientId": "", - "clientSecret": "", - "subscriptionId": "", - "tenantId": "", - "activeDirectoryEndpointUrl": "https://login.microsoftonline.com/", - "resourceManagerEndpointUrl": "https://brazilus.management.azure.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", - "galleryEndpointUrl": "https://gallery.azure.com", - "managementEndpointUrl": "https://management.core.windows.net" -} -``` - -4. Create a new GitHub secret in your fork of this repository named `AzureSPN`. Paste the JSON returned from the Azure CLI into this new secret. Once you've done this you'll see the secret in your fork of the repository. +## Deploy the code using `AZD` CLI - ![The AzureSPN secret in GitHub](docs/media/secrets.png) +The easiest way to deploy the code is to use the `AZD` CLI. This CLI is a wrapper around the Azure CLI that simplifies the process of deploying Azure resources. Run the following commands: -> Note: Never save the JSON to disk, for it will enable anyone who obtains this JSON code to create or edit resources in your Azure subscription. +```pwsh +azd auth login --tenant-id .onmicrosoft.com +``` ## Deploy the code using GitHub Actions -The easiest way to deploy the code is to make a commit directly to the `deploy` branch. Do this by navigating to the `deploy.yml` file in your browser and clicking the `Edit` button. - -![Edit the deployment workflow file.](docs/media/edit-the-deploy-file.png) +The `AZD` CLI is available as a GitHub Action, so you can use it in your CI/CD workflows. -Provide a custom resource group name for the app, and then commit the change to a new branch named `deploy`. +This repo contains a GitHub Actions workflow file that deploys the code to Azure Container Apps. The workflow file is located at `.github/workflows/azure-dev.yml`. -![Create the deploy branch.](docs/media/deploy.png) +Run the following command to configure the GitHub Actions workflow: -Once you click the `Propose changes` button, you'll be in "create a pull request" mode. Don't worry about creating the pull request yet, just click on the `Actions` tab, and you'll see that the deployment CI/CD process has already started. - -![Build started.](docs/media/deploy-started.png) - -When you click into the workflow, you'll see that there are 3 phases the CI/CD will run through: - -1. provision - the Azure resources will be created that eventually house your app. -2. build - the various .NET projects are build into containers and published into the Azure Container Registry instance created during provision. -3. deploy - once `build` completes, the images are in ACR, so the Azure Container Apps are updated to host the newly-published container images. - -![Deployment phases.](docs/media/cicd-phases.png) - -After a few minutes, all three steps in the workflow will be completed, and each box in the workflow diagram will reflect success. If anything fails, you can click into the individual process step to see the detailed log output. - -> Note: if you do see any failures or issues, please submit an Issue so we can update the sample. Likewise, if you have ideas that could make it better, feel free to submit a pull request. - -![Deployment success.](docs/media/success.png) +```pwsh +azd pipeline config +``` +The workflow file is triggered by a push to the `main` branch. With the projects deployed to Azure, you can now test the app to make sure it works.