Skip to content

Commit

Permalink
πŸ§‘β€πŸ’» DevOps - Add Intermediary Staging Slot (#1702)
Browse files Browse the repository at this point in the history
* Added swap command + intermediate staging environment

* Added Node LTS to Docker container

* Added correct tag for Docker deployment

* Added staging deployment to Bicep

* Removed deletion of staging slot

* Added new env variable to distinguish between PR and production builds

* Removed writefile

* Changed to LTS Alphine instead of 20.9

* un-renamed app service bicep

* Removed APP_ENV, replaced with decorator for middleware

* Removed erraneously added redirects

* noIndex - handle condition where url does not have www prefix

* noIndex - linting fixes

* Refactor noIndex middleware to improve readability

---------

Co-authored-by: Matt Wicks [SSW] <[email protected]>
Co-authored-by: Matt Wicks <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2023
1 parent a21f126 commit ed389a6
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 22 deletions.
6 changes: 6 additions & 0 deletions .github/actions/deploy/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ runs:
Write-Host '⏩ we know the production url already'
Return 0;
}
elseif ("${{ inputs.slot_name }}" -ieq "staging")
{
echo "webapp-url=https://${{ inputs.APP_SERVICE_NAME }}-staging.azurewebsites.net" >> $env:GITHUB_OUTPUT
Write-Host '⏩ we know the staging url already'
Return 0;
}
$url = az webapp config hostname list `
--resource-group ${{ inputs.AZURE_RESOURCE_GROUP }} `
Expand Down
34 changes: 28 additions & 6 deletions .github/workflows/main-build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:
name: Build and upload artifacts
uses: ./.github/workflows/template-build.yml
with:
tag: production
tag: staging
secrets: inherit

deploy-to-prod:
name: Deploy to Production
deploy-staging:
name: Deploy to staging slot
needs: build
runs-on: ubuntu-latest
environment:
Expand All @@ -46,7 +46,7 @@ jobs:
id: deploy
uses: ./.github/actions/deploy
with:
slot_name: production
slot_name: staging
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Expand All @@ -57,7 +57,29 @@ jobs:
AZURE_SERVICE_PRINCIPAL_OBJECT_ID: ${{ secrets.AZURE_SERVICE_PRINCIPAL_OBJECT_ID }}
test:
name: Run Playwright Tests
needs: deploy-to-prod
needs: deploy-staging
uses: ./.github/workflows/template-ui-tests.yml
with:
deploy_url: ${{ needs.deploy-to-prod.outputs.url || 'https://ssw.com.au' }}
deploy_url: ${{ needs.deploy-staging.outputs.url }}

swap-staging:
name: Swap staging with production
needs: test
runs-on: ubuntu-latest
steps:
- name: Azure CLI - Login
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Azure CLI Swap Deployments
uses: azure/CLI@v1
with:
azcliversion: 2.53.1
inlineScript: |
az webapp deployment slot swap \
--resource-group ${{ env.AZURE_RESOURCE_GROUP }} \
--name ${{ env.APP_SERVICE_NAME }} \
--slot staging \
--target-slot production
10 changes: 0 additions & 10 deletions .github/workflows/template-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ jobs:
path: ./.github
load-mode: strict

- uses: DamianReeves/write-file-action@master
name: Make site unindexable
if: github.event_name == 'pull_request'
with:
path: public/robots.txt
contents: |
User-agent: *
Disallow: /
write-mode: overwrite

- name: Get current date
id: date
run: |
Expand Down
Binary file not shown.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Install dependencies only when needed
FROM node:20-alpine AS deps
FROM node:lts-alpine AS deps
RUN corepack enable

# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
Expand All @@ -12,7 +12,7 @@ RUN yarn install


# Rebuild the source code only when needed
FROM node:20-alpine AS builder
FROM node:lts-alpine AS builder
WORKDIR /app

COPY . .
Expand Down Expand Up @@ -78,7 +78,7 @@ ENV SITE_URL $SITE_URL
RUN yarn build

# Production image, copy all the files and run next
FROM node:20-alpine AS runner
FROM node:lts-alpine AS runner
RUN corepack enable
WORKDIR /app

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ var appSettings = [
]


resource prodSlot 'Microsoft.Web/sites@2022-03-01' existing = {
resource prodSlot 'Microsoft.Web/sites@2022-03-01' existing = {
name: appServiceName
}

Expand Down
23 changes: 22 additions & 1 deletion infra/appService.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ var appSettings = [
}
]

var productionName = 'app-${projectName}-${entropy}'
var kind = 'app,linux,container'

resource appService 'Microsoft.Web/sites@2022-03-01' = {
name: 'app-${projectName}-${entropy}'
name: productionName
location: location
kind: 'app,linux,container'
identity: {
Expand All @@ -148,6 +151,24 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = {
}
}

resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
name: '${productionName}-staging'
location: location
kind: kind
identity: {
type: 'SystemAssigned'
}
tags: tags
properties: {
serverFarmId: plan.id
siteConfig: {
appSettings: appSettings
acrUseManagedIdentityCreds: true
}
clientAffinityEnabled: false
}
}

// Add AcrPull role so that the app service can pull images using managed identity

// This is the ACR Pull Role Definition Id: https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#acrpull
Expand Down
2 changes: 1 addition & 1 deletion infra/create-pr-environment.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module appInsight 'appInsight.bicep' = {
tags: dev
}
}
module appServiceSlot 'appSerivce-create-slot.bicep' = {
module appServiceSlot 'appService-create-pr-slot.bicep' = {
name:'${slotName}-create-slot-${now}'
params:{
location:location
Expand Down
10 changes: 10 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextRequest, NextResponse } from "next/server";
import { addNoIndexHeaders } from "./middleware/noIndex";

export function middleware(request: NextRequest) {
const response = NextResponse.next();

addNoIndexHeaders(request, response);

return response;
}
32 changes: 32 additions & 0 deletions middleware/noIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from "next/server";

export const addNoIndexHeaders = (
request: NextRequest,
response: NextResponse
) => {
try {
const siteUrl = new URL(process.env.SITE_URL || "https://www.ssw.com.au");

if (
sanitizeHostname(request.nextUrl.hostname) !==
sanitizeHostname(siteUrl.hostname)
) {
response.headers.set("X-Robots-Tag", "noindex");
}
} catch (err) {
// If TypeError is thrown from an invalid URL, fail gracefully
if (err instanceof TypeError) {
return;
}

throw err;
}
};

const sanitizeHostname = (hostname: string) => {
if (hostname.startsWith("www.")) {
return hostname.substring(4);
}

return hostname;
};

0 comments on commit ed389a6

Please sign in to comment.