diff --git a/deployment/MyHrApp.zip b/deployment/MyHrApp.zip new file mode 100644 index 00000000..781b816c Binary files /dev/null and b/deployment/MyHrApp.zip differ diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 00000000..252f68d2 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,61 @@ +## Introduction + + +CoreEx leverages [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/) to provision resources in Azure. Following are the steps to be performed when deploying CoreEx sample app to Azure. + +## Prequisites + + - [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli) + + - [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) + + - [.NET CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/) + + - [Powershell](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/powershell) + +## Deploy Azure resources + - Open the source code in VSCode. Start a terminal and navigate to the ***deployment*** folder. + + - Sign in to your Azure account from the Visual Studio Code terminal. +~~~script + az login +~~~ + + - Set the default subscription +~~~script + az account set --subscription {your-subscription-ID} +~~~ + - Create resource group +~~~script + az group create --name {resource-group-name} --location {location-name} +~~~ + - Set default resource group +~~~script + az configure --defaults group={resource-group-name} +~~~ + - Deploy bicep template +~~~script +az deployment group create --template-file main.bicep +~~~ + - Verify deployment +~~~script +az deployment group list --output table +~~~ + +***NOTE:*** You will be prompted for SQL Admin Login and **complex** Password. If you run into any error, delete the resource-group and start again. + +## Deploy App Code + + - Create Publish artifacts +~~~script +Remove-Item -Recurse -Force "\artifacts" -ErrorAction SilentlyContinue + +dotnet publish "..\samples\My.Hr\My.Hr.Api\My.Hr.Api.csproj" -c Release --output "\artifacts" + +compress-Archive -Path "\artifacts\*" -DestinationPath "MyHrApp.zip" -Force +~~~ + + - Publish Web API +~~~script +az webapp deployment source config-zip --name {App-Service-Name} --src "MyHrApp.zip" +~~~ \ No newline at end of file diff --git a/deployment/main.bicep b/deployment/main.bicep new file mode 100644 index 00000000..940dc90d --- /dev/null +++ b/deployment/main.bicep @@ -0,0 +1,39 @@ +@description('Location for all resources.') +param location string = resourceGroup().location + +@description('The admin user of the SQL Server') +param sqlAdministratorLogin string + +@description('The password of the admin user of the SQL Server') +@secure() +param sqlAdministratorLoginPassword string + +module sql 'modules/sqlServer.bicep' = { + name: 'sql' + params: { + sqlAdministratorLogin: sqlAdministratorLogin + sqlAdministratorLoginPassword: sqlAdministratorLoginPassword + location: location + } +} + +module bus 'modules/serviceBus.bicep' = { + name: 'serviceBus' + params: { + serviceBusQueueName: 'myHr' + location: location + + } +} + +module webApp 'modules/appService.bicep' = { + name: 'webApp' + params: { + sqlAdministratorLogin: sqlAdministratorLogin + sqlAdministratorLoginPassword: sqlAdministratorLoginPassword + sqlServerDatabaseName: sql.outputs.sqlServerDatabaseName + sqlServerFullyQualifiedDomainName: sql.outputs.sqlServerFullyQualifiedName + location: location + servicebusName: bus.name + } +} diff --git a/deployment/modules/appService.bicep b/deployment/modules/appService.bicep new file mode 100644 index 00000000..34774ca2 --- /dev/null +++ b/deployment/modules/appService.bicep @@ -0,0 +1,282 @@ +@description('Location for all resources.') +param location string = resourceGroup().location + +@description('Describes plan\'s pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/') +@allowed([ + 'F1' + 'D1' + 'B1' + 'B2' + 'B3' + 'S1' + 'S2' + 'S3' + 'P1' + 'P2' + 'P3' + 'P4' +]) +param skuName string = 'S1' + +@description('Describes plan\'s instance count') +@minValue(1) +@maxValue(3) +param skuCapacity int = 1 + +@description('The admin user of the SQL Server') +param sqlAdministratorLogin string + +@description('The password of the admin user of the SQL Server') +@secure() +param sqlAdministratorLoginPassword string + +@description('The fully qualified domain name for Sql Server') +param sqlServerFullyQualifiedDomainName string + +@description('The database name for the app') +param sqlServerDatabaseName string + +@description('Service bus resource name') +param servicebusName string + +var hostingPlanName = 'myHrPlan-${uniqueString(resourceGroup().id)}' +var websiteName = 'myHrApp${uniqueString(resourceGroup().id)}' + +resource appInsights 'Microsoft.Insights/components@2020-02-02' = { + name: 'AppInsights${websiteName}' + location: location + tags: { + 'hidden-link:${websiteName}': 'Resource' + displayName: 'AppInsightsComponent' + } + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspace.id + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' = { + name: 'LogAnalytics${websiteName}' + location: location + tags: { + displayName: 'Log Analytics' + ProjectName: websiteName + } + properties: { + sku: { + name: 'PerGB2018' + } + retentionInDays: 120 + features: { + searchVersion: 1 + legacy: 0 + enableLogAccessUsingOnlyResourcePermissions: true + } + } +} + +resource hostingPlan 'Microsoft.Web/serverfarms@2020-12-01' = { + name: hostingPlanName + location: location + tags: { + displayName: 'HostingPlan' + } + sku: { + name: skuName + capacity: skuCapacity + } + kind: 'Linux' + properties: { + reserved: true + } +} + +resource website 'Microsoft.Web/sites@2022-03-01' = { + name: websiteName + location: location + kind: 'app,linux' + identity: { + type: 'SystemAssigned' + } + tags: { + 'hidden-related:${hostingPlan.id}': 'empty' + displayName: 'Website' + } + properties: { + enabled: true + hostNameSslStates: [ + { + name: '${websiteName}.azurewebsites.net' + sslState: 'Disabled' + hostType: 'Standard' + } + { + name: '${websiteName}.scm.azurewebsites.net' + sslState: 'Disabled' + hostType: 'Repository' + } + ] + serverFarmId: hostingPlan.id + reserved: true + isXenon: false + hyperV: false + vnetRouteAllEnabled: false + vnetImagePullEnabled: false + vnetContentShareEnabled: false + scmSiteAlsoStopped: false + clientAffinityEnabled: true + clientCertEnabled: false + clientCertMode: 'Required' + hostNamesDisabled: false + customDomainVerificationId: '4C8CB3E0A6C623305A3DFAA9AE1EB0D67EC65588273F58DD2912DF02E3BE774B' + containerSize: 0 + dailyMemoryTimeQuota: 0 + httpsOnly: true + redundancyMode: 'None' + storageAccountRequired: false + keyVaultReferenceIdentity: 'SystemAssigned' + siteConfig:{ + numberOfWorkers: 1 + linuxFxVersion: 'DOTNETCORE|6.0' + acrUseManagedIdentityCreds: false + alwaysOn: true + http20Enabled: true + functionAppScaleLimit: 0 + minimumElasticInstanceCount: 0 + appSettings: [ + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~4' + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: 'InstrumentationKey=${appInsights.properties.InstrumentationKey}' + } + { + name: 'ServiceBusConnection__fullyQualifiedNamespace' + value: '${servicebusName}.servicebus.windows.net"' + } + ] + } + } +} + +resource webSiteFtp'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-03-01' = { + parent: website + name: 'ftp' + location: location + properties: { + allow: true + } + tags: { + displayName: 'Website' + 'hidden-related:/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Web/serverfarms/${hostingPlan.name}': 'empty' + } +} + +resource webSiteScm 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-03-01' = { + parent: website + name: 'scm' + location: location + properties: { + allow: true + } + tags: { + displayName: 'Website' + 'hidden-related:/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Web/serverfarms/${hostingPlan.name}': 'empty' + } +} + +resource websiteConfig 'Microsoft.Web/sites/config@2022-03-01' = { + parent : website + name: 'web' + location: location + tags: { + displayName: 'Website' + 'hidden-related:/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Web/serverfarms/${hostingPlan.name}': 'empty' + } + properties: { + numberOfWorkers: 1 + defaultDocuments: [ + 'Default.htm' + 'Default.html' + 'Default.asp' + 'index.htm' + 'index.html' + 'iisstart.htm' + 'default.aspx' + 'index.php' + 'hostingstart.html' + ] + netFrameworkVersion: 'v4.0' + linuxFxVersion: 'DOTNETCORE|6.0' + requestTracingEnabled: false + remoteDebuggingEnabled: false + httpLoggingEnabled: false + acrUseManagedIdentityCreds: false + logsDirectorySizeLimit: 35 + detailedErrorLoggingEnabled: false + appCommandLine: 'dotnet My.Hr.Api.dll' + scmType: 'None' + use32BitWorkerProcess: true + webSocketsEnabled: false + alwaysOn: true + managedPipelineMode: 'Integrated' + virtualApplications: [ + { + virtualPath: '/' + physicalPath: 'site\\wwwroot' + preloadEnabled: true + } + ] + loadBalancing: 'LeastRequests' + experiments: { + rampUpRules: [] + } + autoHealEnabled: false + vnetRouteAllEnabled: false + vnetPrivatePortsCount: 0 + publicNetworkAccess: 'Enabled' + localMySqlEnabled: false + ipSecurityRestrictions: [ + { + ipAddress: 'Any' + action: 'Allow' + priority: 2147483647 + name: 'Allow all' + description: 'Allow all access' + } + ] + scmIpSecurityRestrictions: [ + { + ipAddress: 'Any' + action: 'Allow' + priority: 2147483647 + name: 'Allow all' + description: 'Allow all access' + } + ] + scmIpSecurityRestrictionsUseMain: false + http20Enabled: true + minTlsVersion: '1.2' + scmMinTlsVersion: '1.2' + ftpsState: 'AllAllowed' + preWarmedInstanceCount: 0 + functionsRuntimeScaleMonitoringEnabled: false + minimumElasticInstanceCount: 0 + azureStorageAccounts: { + } + } +} + +resource webSiteConnectionStrings 'Microsoft.Web/sites/config@2020-12-01' = { + parent: website + name: 'connectionstrings' + properties: { + DefaultConnection: { + value: 'Data Source=tcp:${sqlServerFullyQualifiedDomainName},1433;Initial Catalog=${sqlServerDatabaseName};User Id=${sqlAdministratorLogin}@${sqlServerFullyQualifiedDomainName};Password=${sqlAdministratorLoginPassword};' + type: 'SQLAzure' + } + } +} diff --git a/deployment/modules/serviceBus.bicep b/deployment/modules/serviceBus.bicep new file mode 100644 index 00000000..c97e4234 --- /dev/null +++ b/deployment/modules/serviceBus.bicep @@ -0,0 +1,37 @@ +@description('Name of the Service Bus namespace') +param serviceBusNamespaceName string = 'myHrBus${uniqueString(resourceGroup().id)}' + +@description('Name of the Queue') +param serviceBusQueueName string + +@description('Location for all resources.') +param location string = resourceGroup().location + +resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2022-01-01-preview' = { + name: serviceBusNamespaceName + location: location + sku: { + name: 'Standard' + } + properties: {} +} + +resource serviceBusQueue 'Microsoft.ServiceBus/namespaces/queues@2022-01-01-preview' = { + parent: serviceBusNamespace + name: serviceBusQueueName + properties: { + lockDuration: 'PT5M' + maxSizeInMegabytes: 1024 + requiresDuplicateDetection: false + requiresSession: false + defaultMessageTimeToLive: 'P10675199DT2H48M5.4775807S' + deadLetteringOnMessageExpiration: false + duplicateDetectionHistoryTimeWindow: 'PT10M' + maxDeliveryCount: 10 + autoDeleteOnIdle: 'P10675199DT2H48M5.4775807S' + enablePartitioning: false + enableExpress: false + } +} + +output serviceBusName string = serviceBusNamespace.name diff --git a/deployment/modules/sqlServer.bicep b/deployment/modules/sqlServer.bicep new file mode 100644 index 00000000..35d93f53 --- /dev/null +++ b/deployment/modules/sqlServer.bicep @@ -0,0 +1,54 @@ +@description('The admin user of the SQL Server') +param sqlAdministratorLogin string + +@description('The password of the admin user of the SQL Server') +@secure() +param sqlAdministratorLoginPassword string + +@description('Location for all resources.') +param location string = resourceGroup().location + +var sqlserverName = 'myHrSql${uniqueString(resourceGroup().id)}' + +var databaseName = 'myHr' + +resource sqlServer 'Microsoft.Sql/servers@2021-02-01-preview' = { + name: sqlserverName + location: location + tags: { + displayName: 'SQL Server' + } + properties: { + administratorLogin: sqlAdministratorLogin + administratorLoginPassword: sqlAdministratorLoginPassword + version: '12.0' + } +} + +resource sqlDatabase 'Microsoft.Sql/servers/databases@2021-02-01-preview' = { + parent: sqlServer + name: databaseName + location: location + tags: { + displayName: 'Database' + } + sku: { + name: 'Basic' + } + properties: { + collation: 'SQL_Latin1_General_CP1_CI_AS' + maxSizeBytes: 1073741824 + } +} + +resource allowAllWindowsAzureIps 'Microsoft.Sql/servers/firewallRules@2021-02-01-preview' = { + parent: sqlServer + name: 'AllowAllWindowsAzureIps' + properties: { + endIpAddress: '0.0.0.0' + startIpAddress: '0.0.0.0' + } +} + +output sqlServerFullyQualifiedName string = sqlServer.properties.fullyQualifiedDomainName +output sqlServerDatabaseName string = databaseName