Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 Add full CI/CD pipeline #922

Merged
merged 6 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions .azure/bicep/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
@description('The location into which your Azure resources should be deployed.')
param location string = resourceGroup().location

@description('Select the type of environment you want to provision. Allowed values are Production and Test.')
@allowed([
'Production'
'Test'
])
param environmentType string

@description('A unique suffix to add to resource names that need to be globally unique.')
@maxLength(13)
param resourceNameSuffix string = uniqueString(resourceGroup().id)

@description('The administrator login username for the SQL server.')
param sqlServerAdministratorLogin string

@secure()
@description('The administrator login password for the SQL server.')
param sqlServerAdministratorLoginPassword string

@description('The name of the project.')
param projectName string = 'CleanArchitecture'

// Define the environment configuration map.
var environmentConfigurationMap = {
Production: {
environmentAbbreviation: 'prd'
appServicePlan: {
sku: {
name: 'S1'
capacity: 1
}
}
storageAccount: {
sku: {
name: 'Standard_LRS'
}
}
sqlDatabase: {
sku: {
name: 'Standard'
tier: 'Standard'
}
}
}
Test: {
environmentAbbreviation: 'tst'
appServicePlan: {
sku: {
name: 'B1'
}
}
sqlDatabase: {
sku: {
name: 'Standard'
tier: 'Standard'
}
}
}
}

// Define the names for resources.
var environmentAbbreviation = environmentConfigurationMap[environmentType].environmentAbbreviation
var keyVaultName = 'kv-${projectName}-${environmentAbbreviation}'
var appServiceAppName = 'as-${projectName}-${resourceNameSuffix}-${environmentAbbreviation}'
var appServicePlanName = 'plan-${projectName}-${environmentAbbreviation}'
var logAnalyticsWorkspaceName = 'log-${projectName}-${environmentAbbreviation}'
var applicationInsightsName = 'appi-${projectName}-${environmentAbbreviation}'
var sqlServerName = 'sql-${projectName}-${resourceNameSuffix}-${environmentAbbreviation}'
var sqlDatabaseName = '${projectName}-${environmentAbbreviation}'

// Define the connection string to access Azure SQL.
var sqlDatabaseConnectionString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabase.name};Persist Security Info=False;User ID=${sqlServerAdministratorLogin};Password=${sqlServerAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'

// Define the SKUs for each component based on the environment type.
var appServicePlanSku = environmentConfigurationMap[environmentType].appServicePlan.sku
var sqlDatabaseSku = environmentConfigurationMap[environmentType].sqlDatabase.sku

resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: location
properties: {
enabledForTemplateDeployment: true
tenantId: subscription().tenantId
accessPolicies: [
{
tenantId: appServiceApp.identity.tenantId
objectId: appServiceApp.identity.principalId
permissions: {
keys: [
'Get'
]
secrets: [
'List'
'Get'
]
certificates: [
'List'
'Get'
]
}
}
]
sku: {
name: 'standard'
family: 'A'
}
}
}

resource connectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
parent: keyVault
name: 'ConnectionStrings--DefaultConnection'
properties: {
value: sqlDatabaseConnectionString
}
}

resource appServicePlan 'Microsoft.Web/serverfarms@2021-01-15' = {
name: appServicePlanName
location: location
sku: appServicePlanSku
}

resource appServiceApp 'Microsoft.Web/sites@2021-01-15' = {
name: appServiceAppName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
netFrameworkVersion: 'v7.0'
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
{
name: 'KeyVaultName'
value: keyVaultName
}
]
}
}
}

resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-10-01' = {
name: logAnalyticsWorkspaceName
location: location
properties: {
sku: {
name: 'PerGB2018'
}
}
}

resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: applicationInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}

resource sqlServer 'Microsoft.Sql/servers@2021-02-01-preview' = {
name: sqlServerName
location: location
properties: {
administratorLogin: sqlServerAdministratorLogin
administratorLoginPassword: sqlServerAdministratorLoginPassword
}
}

resource sqlServerFirewallRule 'Microsoft.Sql/servers/firewallRules@2021-02-01-preview' = {
parent: sqlServer
name: 'AllowAllWindowsAzureIps'
properties: {
endIpAddress: '0.0.0.0'
startIpAddress: '0.0.0.0'
}
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2021-02-01-preview' = {
parent: sqlServer
name: sqlDatabaseName
location: location
sku: sqlDatabaseSku
}

output appServiceAppName string = appServiceApp.name
output appServiceAppHostName string = appServiceApp.properties.defaultHostName
output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
output sqlDatabaseName string = sqlDatabase.name
110 changes: 110 additions & 0 deletions .azure/setup.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
Param(
[Parameter(Mandatory)]
[String]$GitHubOrganisationName,

[Parameter(Mandatory)]
[String]$GitHubRepositoryName,

[Parameter(Mandatory)]
[String]$AzureLocation,

[ValidateNotNullOrEmpty()]
[ValidateLength(4, 17)]
[String]$ProjectName = $GitHubRepositoryName
)

$testEnvironmentName = "Test"
$productionEnvironmentName = "Production"

function CreateWorkloadIdentity {
param (
$environmentName
)

Write-Host "🧱 Creating Azure Workload Identity for $ProjectName$environmentName"

# Create Azure AD Application Registration
$applicationRegistrationDetails=$(az ad app create --display-name "$ProjectName$environmentName") | ConvertFrom-Json

# Create federated credentials
$credential = @{
name="$ProjectName$environmentName";
issuer="https://token.actions.githubusercontent.com";
subject="repo:${GitHubOrganisationName}/${GitHubRepositoryName}:environment:$environmentName";
audiences=@("api://AzureADTokenExchange")
} | ConvertTo-Json

$credential | az ad app federated-credential create --id $applicationRegistrationDetails.id --parameters "@-" | Out-Null

$credential = @{
name="$ProjectName";
issuer="https://token.actions.githubusercontent.com";
subject="repo:${GitHubOrganisationName}/${GitHubRepositoryName}:ref:refs/heads/main";
audiences=@("api://AzureADTokenExchange")
} | ConvertTo-Json

$credential | az ad app federated-credential create --id $applicationRegistrationDetails.id --parameters "@-" | Out-Null

return $applicationRegistrationDetails.appId
}

function CreateResourceGroup {
param (
$environmentName,
$appId
)

Write-Host "🧱 Creating Azure Resource Group for $ProjectName$environmentName"

$resourceGroupId=$(az group create --name "$ProjectName$environmentName" --location $AzureLocation --query id --output tsv)
az ad sp create --id $appId
az role assignment create --assignee $appId --role Contributor --scope $resourceGroupId
}

function CreateEnvironments {
Write-Host "🧱 Creating GitHub Environments"

$token = gh auth token
$header = @{"Authorization" = "token $token"}
$contentType = "application/json"

# Test
$uri = "https://api.github.com/repos/$GitHubOrganisationName/$GitHubRepositoryName/environments/$testEnvironmentName"
Invoke-WebRequest -Method PUT -Header $header -ContentType $contentType -Uri $uri

#Production
$uri = "https://api.github.com/repos/$GitHubOrganisationName/$GitHubRepositoryName/environments/$productionEnvironmentName"
Invoke-WebRequest -Method PUT -Header $header -ContentType $contentType -Uri $uri
}

function SetSecrets {
param(
$testAppId,
$prodAppId
)

Write-Host "🧱 Setting GitHub Secrets"

$repo = "https://github.com/$GitHubOrganisationName/$GitHubRepositoryName"

gh secret set "AZURE_CLIENT_ID_TEST" --repo $repo --body $testAppId
gh secret set "AZURE_CLIENT_ID_PRODUCTION" --repo $repo --body $prodAppId
gh secret set "AZURE_TENANT_ID" --repo $repo --body $(az account show --query tenantId --output tsv)
gh secret set "AZURE_SUBSCRIPTION_ID" --repo $repo --body $(az account show --query id --output tsv)

Write-Host "Specify the Test SQL Server Administrator Login Password:"
gh secret set "SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST" --repo $repo

Write-Host "Specify the Production SQL Server Administrator Login Password:"
gh secret set "SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION" --repo $repo
}

# Azure Initialisation
$testAppId = CreateWorkloadIdentity $testEnvironmentName
CreateResourceGroup $testEnvironmentName $testAppId
$productionAppId = CreateWorkloadIdentity $productionEnvironmentName
CreateResourceGroup $productionEnvironmentName $productionAppId

# GitHub Initialisation
CreateEnvironments
SetSecrets $testAppId $productionAppId
41 changes: 41 additions & 0 deletions .github/workflows/build+deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Build+Deploy

on:
push:
branches: [ main ]

jobs:

# Build the website
build:
uses: ./.github/workflows/build.yml
with:
build-artifacts: true

# Deploy to the test environment.
deploy-test:
uses: ./.github/workflows/deploy.yml
needs: [ build ]
with:
environmentType: Test
resourceGroupName: CleanArchitectureTest
sqlServerAdministratorLogin: SqlAdmin
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_TEST }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST }}

# Deploy to the production environment.
deploy-prod:
uses: ./.github/workflows/deploy.yml
needs: [ deploy-test ]
with:
environmentType: Production
resourceGroupName: CleanArchitectureProduction
sqlServerAdministratorLogin: SqlAdmin
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION }}
Loading