diff --git a/.github/workflows/main_build_and_deploy_all.yml b/.github/workflows/main_build_and_deploy_all.yml new file mode 100644 index 0000000..67e1ad9 --- /dev/null +++ b/.github/workflows/main_build_and_deploy_all.yml @@ -0,0 +1,51 @@ +name: "Main: build and deploy all" + +on: + workflow_dispatch: + push: + branches: + - master + +env: + RESOURCE_GROUP_NAME: rg-policy-initiative-builder + BICEP_TEMPLATE: bicep/deploy.bicep + BICEP_DEPLOY_MODE: Incremental + ACR_SERVER: containerregistryexpecho + ACR_IMAGE: policyinitiativebuilder + DOCKER_FILE: ./src/PolicyInitiativeBuilder/Dockerfile + DOCKER_WORKING_DIRECTORY: ./src/PolicyInitiativeBuilder/ + +permissions: + contents: read # Allow repo checkout + checks: write # Allow write check results (test reporter) + id-token: write # Allow requesting OIDC JWT + +jobs: + build-and-push-app: + name: Build Web App Docker image and push it to registry + uses: ./.github/workflows/reusable_docker_build_and_push_to_acr.yml + with: + docker-file: ${{ env.DOCKER_FILE }} + working-directory: ${{ env.DOCKER_WORKING_DIRECTORY }} + acr-name: ${{ env.ACR_SERVER }} + container-tags: | + ${{ env.ACR_SERVER }}.azurecr.io/${{ env.IMAGE }}:latest + ${{ env.ACR_SERVER }}.azurecr.io/${{ env.IMAGE }}:build-${{ github.run_number }} + secrets: + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + + deploy-infra: + needs : build-and-push-app + name: Validate and deploy infrastructure + uses: ./.github/workflows/reusable_deploy_bicep_if_valid.yml + with: + resourceGroupName: ${{ env.RESOURCE_GROUP_NAME }} + template: ${{ env.BICEP_TEMPLATE }} + parameters: cappImageName=${{ env.ACR_SERVER }}.azurecr.io/${{ env.IMAGE }}:build-${{ github.run_number }} + mode: ${{ env.BICEP_DEPLOY_MODE }} + secrets: + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} \ No newline at end of file diff --git a/.github/workflows/policyinitiativebuilder.yml b/.github/workflows/policyinitiativebuilder.yml deleted file mode 100644 index 5e22b7b..0000000 --- a/.github/workflows/policyinitiativebuilder.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build and deploy .NET application to container app policyinitiativebuilder -on: - push: - branches: - - master -env: - CONTAINER_APP_CONTAINER_NAME: policyinitiativebuilder - CONTAINER_APP_NAME: policyinitiativebuilder - CONTAINER_APP_RESOURCE_GROUP_NAME: rg-policy-initiative-builder - CONTAINER_REGISTRY_LOGIN_SERVER: policyinitiativebuilder.azurecr.io - DOTNET_CORE_VERSION: 9.0.x - PROJECT_NAME_FOR_DOCKER: policyinitiativebuilder -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout to the branch - uses: actions/checkout@v4 - - name: Setup .NET SDK - uses: actions/setup-dotnet@v1.8.0 - with: - include-prerelease: True - dotnet-version: ${{ env.DOTNET_CORE_VERSION }} - - name: Log in to container registry - uses: azure/docker-login@v2 - with: - login-server: ${{ env.CONTAINER_REGISTRY_LOGIN_SERVER }} - username: ${{ secrets.PolicyInitiativeBuilder_USERNAME_31DD }} - password: ${{ secrets.PolicyInitiativeBuilder_PASSWORD_31DD }} - - name: Build and push container image to registry - run: dotnet publish -c Release -r linux-x64 -p:PublishProfile=DefaultContainer -p:ContainerImageTag=${{ github.sha }} --no-self-contained -p:ContainerRegistry=${{ env.CONTAINER_REGISTRY_LOGIN_SERVER }} -bl - - name: Upload binlog for investigation - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - name: binlog - path: msbuild.binlog - deploy: - runs-on: ubuntu-latest - needs: build - steps: - - name: Azure Login - uses: azure/login@v2 - with: - creds: ${{ secrets.policyinitiativebuilder_SPN }} - - name: Deploy to containerapp - uses: azure/CLI@v2 - with: - inlineScript: > - az config set extension.use_dynamic_install=yes_without_prompt - - az containerapp registry set --name ${{ env.CONTAINER_APP_NAME }} --resource-group ${{ env.CONTAINER_APP_RESOURCE_GROUP_NAME }} --server ${{ env.CONTAINER_REGISTRY_LOGIN_SERVER }} --username ${{ secrets.PolicyInitiativeBuilder_USERNAME_31DD }} --password ${{ secrets.PolicyInitiativeBuilder_PASSWORD_31DD }} - - az containerapp update --name ${{ env.CONTAINER_APP_NAME }} --container-name ${{ env.CONTAINER_APP_CONTAINER_NAME }} --resource-group ${{ env.CONTAINER_APP_RESOURCE_GROUP_NAME }} --image ${{ env.CONTAINER_REGISTRY_LOGIN_SERVER }}/${{ env.PROJECT_NAME_FOR_DOCKER }}:${{ github.sha }} - - name: logout - run: > - az logout diff --git a/.github/workflows/reusable_deploy_bicep_if_valid.yml b/.github/workflows/reusable_deploy_bicep_if_valid.yml new file mode 100644 index 0000000..4227f1e --- /dev/null +++ b/.github/workflows/reusable_deploy_bicep_if_valid.yml @@ -0,0 +1,75 @@ +name: "Deploy: infrastructure" + +on: + workflow_call: + inputs: + resourceGroupName: + description: "Name of the resource group" + required: true + type: string + location: + description: "Location of the resource group" + required: false + type: string + default: "West Europe" + tags: + description: "Space-separated tags for the resource group: key[=value] [key[=value] ...]" + required: true + type: string + template: + description: "Path to template file" + required: true + type: string + parameters: + description: "Path to parameters file" + required: true + type: string + mode: + description: "The deployment mode, accepted values: Complete, Incremental" + required: false + type: string + default: "Incremental" + + secrets: + AZURE_SUBSCRIPTION_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_CLIENT_ID: + required: true + +jobs: + validate-infrastructure: + name: "Az deployment what-if " + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@v3 + - name: "Az CLI login" + uses: azure/login@v1 + with: + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + - name: "Az CLI what-if deployment" + run: | + az group create --name ${{ inputs.resourceGroupName }} --location '${{ inputs.location }}' --tags ${{ inputs.tags }} + az deployment group what-if --resource-group ${{ inputs.resourceGroupName }} --template-file ${{ inputs.template }} --parameters ${{ inputs.parameters }} + + deploy-infrastructure-if: + name: "Az deployment" + needs: "validate-infrastructure" + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@v3 + - name: "Az CLI login" + uses: azure/login@v1 + with: + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + - name: "Deploy to Azure" + run: | + az group create --name ${{ inputs.resourceGroupName }} --location '${{ inputs.location }}' --tags ${{inputs.tags}} + az deployment group create --resource-group ${{ inputs.resourceGroupName }} --template-file ${{ inputs.template }} --parameters ${{ inputs.parameters }} --mode ${{ inputs.mode }} diff --git a/.github/workflows/reusable_docker_build_and_push_to_acr.yml b/.github/workflows/reusable_docker_build_and_push_to_acr.yml new file mode 100644 index 0000000..9539e13 --- /dev/null +++ b/.github/workflows/reusable_docker_build_and_push_to_acr.yml @@ -0,0 +1,60 @@ +name: "Docker: build container" + +on: + workflow_call: + inputs: + docker-file: + description: "Path to the docker file" + type: string + required: true + working-directory: + description: "Build context" + type: string + required: true + acr-name: + description: "Container registry name" + type: string + required: true + container-tags: + description: "Container tags" + type: string + required: true + + secrets: + AZURE_SUBSCRIPTION_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_CLIENT_ID: + required: true + +jobs: + build-and-push-container: + name: Docker build and push + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Az CLI login + uses: azure/login@v1 + with: + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + + - name: Login to container registry + run: | + az acr login --name ${{ inputs.acr-name }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + file: ${{ inputs.docker-file }} + context: ${{ inputs.working-directory }} + push: true + tags: ${{ inputs.container-tags }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/reusable_dotnet_build.yml b/.github/workflows/reusable_dotnet_build.yml new file mode 100644 index 0000000..56ca7ea --- /dev/null +++ b/.github/workflows/reusable_dotnet_build.yml @@ -0,0 +1,49 @@ +name: "Build: dotnet" + +on: + workflow_call: + inputs: + dotnet-version: + description: ".NET cli version" + required: false + default: 9.0.x + type: string + project-folder: + description: "Path to the project" + required: true + type: string + publish-args: + description: "Arguments for dotnet publish, excluding --output" + required: false + default: -c Release --nologo + type: string + artifact-name: + description: "Name for the build artifact" + required: true + type: string + +env: + PUBLISH_FOLDER_NAME: pubDir + +jobs: + build: + name: "Build solution" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.project-folder }} + steps: + - uses: actions/checkout@v3 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ inputs.dotnet-version }} + - name: Publish + run: dotnet publish -o ${{ env.PUBLISH_FOLDER_NAME }} ${{ inputs.publish-args }} + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.project-folder }}/${{ env.PUBLISH_FOLDER_NAME }} + retention-days: 30 + if-no-files-found: error diff --git a/bicep/deploy.bicep b/bicep/deploy.bicep index 033ef9e..73269ed 100644 --- a/bicep/deploy.bicep +++ b/bicep/deploy.bicep @@ -1,28 +1,125 @@ -@allowed([ 'Free', 'Standard' ]) -param sku string = 'Free' -param name string param location string = resourceGroup().location -param repositoryToken string -param repositoryUrl string = 'https://github.com/Expecho/Policy-Initiative-Bicep-Builder' -param branch string = 'master' -param appLocation string = './PolicyInitiativeEditor/Client' +param workloadProfileType string = 'consumption' +param workloadProfileMinimumCount int = 0 +param workloadProfileMaximumCount int = 1 +param workloadProfileName string = 'container' -resource staticwebapp 'Microsoft.Web/staticSites@2023-01-01' = { - name: name +param cappName string = 'policyinitativebuilder' +param cappConsumptionCpu string = '0.5' +param cappConsumptionMemory string = '1' +param cappImageName string = 'expechocontainerregistry.azurecr.io/policyinitativebuilder:latest' +param cappImageServer string = 'expechocontainerregistry.azurecr.io' + +param vnnetName string = 'vnet-policyinitativebuilder' +param subnetName string = 'subnet-policyinitativebuilder' + +param appInsightsName string = 'policyinitativebuilder-insights' +param laWorkspaceName string = 'policyinitativebuilderlogwsexpecho' + +resource vnet 'Microsoft.Network/virtualNetworks@2022-07-01' = { + name: vnnetName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.0.0.0/16' + ] + } + subnets: [ + { + name: subnetName + properties: { + addressPrefix: '10.0.0.0/23' + } + } + ] + } +} + +resource containerAppEnv 'Microsoft.App/managedEnvironments@2024-08-02-preview' = { + name: 'PolicyInitativeBuilder' + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: laWorkspace.properties.customerId + sharedKey: laWorkspace.listKeys().primarySharedKey + } + } + vnetConfiguration: { + infrastructureSubnetId: vnet.properties.subnets[0].id + } + workloadProfiles: [ + { + name: workloadProfileName + workloadProfileType: workloadProfileType + minimumCount: workloadProfileMinimumCount + maximumCount: workloadProfileMaximumCount + } + ] + } +} + +resource appInsights 'Microsoft.Insights/components@2020-02-02' = { + kind: 'web' location: location - sku: { - name: sku + name: appInsightsName + properties:{ + Application_Type: 'web' + WorkspaceResourceId: laWorkspace.id + IngestionMode: 'LogAnalytics' + } +} + +resource laWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' ={ + location: location + name: laWorkspaceName + properties:{ + retentionInDays: 90 + sku: { + name: 'PerGB2018' + } + } +} + +resource containerAppInConsumptionWorkloadProfile 'Microsoft.App/containerApps@2024-08-02-preview' = { + name: cappName + location: location + identity: { + type: 'SystemAssigned' } properties: { - repositoryUrl: repositoryUrl - stagingEnvironmentPolicy: 'Disabled' - branch: branch - buildProperties: { - appLocation: appLocation - appArtifactLocation: './PolicyInitiativeEditor/wwwroot' - outputLocation: + configuration: { + activeRevisionsMode: 'single' + ingress: { + allowInsecure: false + external: true + targetPort: 80 + } + registries: [ + { + identity: 'system' + server: cappImageServer + } + ] + } + managedEnvironmentId: containerAppEnv.id + template: { + containers: [ + { + image: cappImageName + resources: { + cpu: json('${cappConsumptionCpu}') + memory: '${cappConsumptionMemory}Gi' + } + } + ] + scale: { + minReplicas: 1 + } } - repositoryToken: repositoryToken + workloadProfileName: 'Consumption' } } diff --git a/src/PolicyInitiativeBuilder/DOCKERFILE b/src/PolicyInitiativeBuilder/DOCKERFILE new file mode 100644 index 0000000..40bf923 --- /dev/null +++ b/src/PolicyInitiativeBuilder/DOCKERFILE @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src +COPY ["PolicyInitiativeBuilder.csproj", "."] +RUN dotnet restore "./PolicyInitiativeBuilder.csproj" +COPY . . +WORKDIR "/src/." +RUN dotnet build "PolicyInitiativeBuilder.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "PolicyInitiativeBuilder.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "PolicyInitiativeBuilder.dll"] \ No newline at end of file