Merged in feature/redis (pull request #1478)

Distributed cache

* cache deleteKey now uses an options object instead of a lonely argument variable fuzzy

* merge

* remove debug logs and cleanup

* cleanup

* add fault handling

* add fault handling

* add pid when logging redis client creation

* add identifier when logging redis client creation

* cleanup

* feat: add redis-api as it's own app

* feature: use http wrapper for redis

* feat: add the possibility to fallback to unstable_cache

* Add error handling if redis cache is unresponsive

* add logging for unstable_cache

* merge

* don't cache errors

* fix: metadatabase on branchdeploys

* Handle when /en/destinations throws
add ErrorBoundary

* Add sentry-logging when ErrorBoundary catches exception

* Fix error handling for distributed cache

* cleanup code

* Added Application Insights back

* Update generateApiKeys script and remove duplicate

* Merge branch 'feature/redis' of bitbucket.org:scandic-swap/web into feature/redis

* merge


Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2025-03-14 07:54:21 +00:00
committed by Linus Flood
parent a8304e543e
commit fa63b20ed0
141 changed files with 4404 additions and 1941 deletions

View File

@@ -0,0 +1,103 @@
# Docker
# Build a Docker image
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
name: 1.0.0-$(SourceBranchName)-$(Rev:r)
trigger:
- main
parameters:
- name: forcePush
displayName: Force push
type: boolean
default: false
resources:
- repo: self
variables:
tag: "$(Build.BuildNumber)"
imageName: "redis-api"
isMaster: $[eq(variables['Build.SourceBranchName'], 'master')]
shouldPush: $[or(eq(${{parameters.forcePush}}, True), eq(variables['isMaster'], True))]
tags: |
stages:
- stage: Build
displayName: Set version
jobs:
- job: CreateArtifact
displayName: Create version artifact
steps:
- task: Bash@3
displayName: Write buildnumber
inputs:
targetType: "inline"
script: |
echo '$(Build.BuildNumber)' > $(Pipeline.Workspace)/.version
- task: PublishPipelineArtifact@1
inputs:
targetPath: "$(Pipeline.Workspace)/.version"
artifact: "Version"
publishLocation: "pipeline"
- task: Bash@3
displayName: Add tag main-latest if main branch
inputs:
targetType: "inline"
script: |
localTags = $(tag)
localTags += "\nlatest"
if [ $[isMaster] ]; then
localTags += "\nlatest-main"
echo -e "##vso[task.setvariable variable=tags;]$localTags"
fi
echo -e $localTags
- job: Build
displayName: Build
pool:
vmImage: ubuntu-latest
steps:
- task: Bash@3
inputs:
targetType: "inline"
script: |
echo "VERSION=$(tag)" >> .env.production
echo "ShouldPush=$(shouldPush)"
echo "ForcePush=${{ parameters.forcePush }}"
echo "isMaster=$(isMaster)"
- task: AzureCLI@2
displayName: Login to ACR
inputs:
azureSubscription: "mi-devops"
scriptType: "bash"
scriptLocation: "inlineScript"
workingDirectory: "$(build.sourcesDirectory)"
inlineScript: az acr login --name acrscandicfrontend
- task: AzureCLI@2
displayName: Build and push to ACR
inputs:
azureSubscription: "mi-devops"
scriptType: "bash"
scriptLocation: "inlineScript"
workingDirectory: "$(build.sourcesDirectory)"
inlineScript: |
if [ "$(shouldPush)" != "True" ]; then
echo "Not pushing to ACR"
noPush="--no-push"
else
echo "Pushing to ACR"
noPush=""
fi
echo "isMaster: $(isMaster)"
if [ "$(isMaster)" == "True" ]; then
echo "Building with latest tag"
az acr build . --image $(imageName):latest -r acrscandicfrontend $noPush
fi
echo "Building with $(tag) tag"
az acr build . --image $(imageName):$(tag) -r acrscandicfrontend $noPush

View File

@@ -0,0 +1,44 @@
trigger: none
pr: none
resources:
pipelines:
- pipeline: buildPipeline
source: "Build App BFF"
trigger:
branches:
include:
- main
pool:
vmImage: ubuntu-latest
parameters:
- name: containerTag
displayName: Select tag to deploy
type: string
default: "latest"
variables:
- name: containerTag
value: ${{ parameters.containerTag }}
stages:
- stage: Deploy_test
variables:
- group: "BFF test"
jobs:
- template: ./azure-pipelines.deploywebapptemplate.yml
parameters:
environment: test
subscriptionId: 1a126a59-4703-4e36-ad7b-2503d36526c0
containerTag: $(containerTag)
# - stage: Deploy_prod
# variables:
# - group: 'BFF prod'
# jobs:
# - template: ./azure-pipelines.deploywebapptemplate.yml
# parameters:
# environment: prod
# subscriptionId: 1e6ef69e-8719-4924-a311-e66fe00399c7
# containerTag: $(containerTag)

View File

@@ -0,0 +1,75 @@
import { Environment, EnvironmentVar } from '../types.bicep'
param environment Environment
param location string
param containerAppName string
param containerImage string
param containerPort int
param minReplicas int = 1
param maxReplicas int = 3
param envVars EnvironmentVar[] = []
param userAssignedIdentityId string
resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = {
name: 'acrscandicfrontend'
scope: resourceGroup('1e6ef69e-8719-4924-a311-e66fe00399c7', 'rg-shared')
}
resource containerApp 'Microsoft.App/containerApps@2024-10-02-preview' = {
name: containerAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentityId}': {}
}
}
properties: {
environmentId: resourceId('Microsoft.App/managedEnvironments', 'cae-redis-api-${environment}')
configuration: {
activeRevisionsMode: 'Single'
registries: [
{
identity: userAssignedIdentityId
server: acr.properties.loginServer
}
]
ingress: {
external: true
targetPort: containerPort
}
}
template: {
containers: [
{
name: containerAppName
image: containerImage
imageType: 'ContainerImage'
env: [
for envVar in envVars: {
name: envVar.name
value: envVar.value
}
]
probes: [
{
type: 'Liveness'
httpGet: {
port: containerPort
path: '/health'
}
}
]
resources: {
cpu: json('0.25')
memory: '0.5Gi'
}
}
]
scale: {
minReplicas: minReplicas
maxReplicas: maxReplicas
}
}
}
}

View File

@@ -0,0 +1,42 @@
import { Environment, EnvironmentVar } from '../types.bicep'
targetScope = 'subscription'
param environment Environment
param containerImageTag string
param redisConnection string
param primaryApiKey string
param secondaryApiKey string
param timestamp string = utcNow()
@description('The location for the resource group')
param location string = 'westeurope'
resource rgRedisApi 'Microsoft.Resources/resourceGroups@2021-04-01' existing = {
name: 'rg-redis-api-${environment}'
}
resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
name: 'mi-redis-api-${environment}'
scope: rgRedisApi
}
module containerApp 'containerApp.bicep' = {
name: 'containerApp'
scope: rgRedisApi
params: {
location: location
environment: environment
userAssignedIdentityId: mi.id
containerAppName: 'ca-redis-api-${environment}'
containerImage: 'acrscandicfrontend.azurecr.io/redis-api:${containerImageTag}'
containerPort: 3001
envVars: [
{ name: 'REDIS_CONNECTION', value: redisConnection }
{ name: 'PRIMARY_API_KEY', value: primaryApiKey }
{ name: 'SECONDARY_API_KEY', value: secondaryApiKey }
{ name: 'timestamp', value: timestamp }
]
}
}

View File

@@ -0,0 +1,49 @@
import { Environment } from '../types.bicep'
param environment Environment
@description('The location for the resource group')
param location string = 'westeurope'
var testSKU = {
name: 'Basic'
family: 'C'
capacity: 0
}
var prodSKU = {
name: 'Standard'
family: 'C'
capacity: 1
}
var sku = environment == 'prod' ? prodSKU : testSKU
resource redisResource 'Microsoft.Cache/Redis@2024-11-01' = {
name: 'redis-scandic-frontend-${environment}'
location: location
properties: {
redisVersion: '6.0'
sku: {
name: sku.name
family: sku.family
capacity: sku.capacity
}
enableNonSslPort: false
minimumTlsVersion: '1.2'
publicNetworkAccess: 'Enabled'
redisConfiguration: {
'aad-enabled': 'false'
'maxmemory-reserved': '30'
'maxfragmentationmemory-reserved': '30'
'maxmemory-delta': '30'
}
updateChannel: 'Stable'
disableAccessKeyAuthentication: false
}
}
output hostname string = redisResource.properties.hostName
output connectionString string = '${redisResource.properties.hostName}:6380,password=${redisResource.properties.accessKeys.primaryKey},ssl=True,abortConnect=False'
output primaryAccessKey string = redisResource.properties.accessKeys.primaryKey

View File

@@ -0,0 +1,19 @@
param principalId string
module acrPull '../roles/acr-pull.bicep' = {
name: 'acrPull'
}
resource registry 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = {
name: 'acrscandicfrontend'
}
resource rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(registry.name, 'ServicePrincipal', principalId, acrPull.name)
scope: registry
properties: {
principalType: 'ServicePrincipal'
principalId: principalId
roleDefinitionId: acrPull.outputs.id
}
}

View File

@@ -0,0 +1,26 @@
import { Environment } from '../types.bicep'
param location string = 'westeurope'
param environment Environment
param userAssignedIdentityId string
resource containerEnv 'Microsoft.App/managedEnvironments@2024-02-02-preview' = {
name: 'cae-redis-api-${environment}'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentityId}': {}
}
}
properties: {
publicNetworkAccess: 'Enabled'
workloadProfiles: [
{
name: 'Consumption'
workloadProfileType: 'Consumption'
}
]
zoneRedundant: false
}
}

View File

@@ -0,0 +1,40 @@
import { Environment } from '../types.bicep'
targetScope = 'subscription'
param environment Environment
var location = deployment().location
var productionSubscriptionId = '799cbffe-5209-41fd-adf9-4ffa3d1feead'
resource rgBff 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-redis-api-${environment}'
location: location
}
module mi '../managedIdentity.bicep' = {
name: 'mi-redis-api-${environment}'
scope: rgBff
params: {
principalName: 'mi-redis-api-${environment}'
location: location
}
}
module allowAcrPull 'allow-acr-pull.bicep' = {
name: 'allowAcrPull'
scope: resourceGroup('1e6ef69e-8719-4924-a311-e66fe00399c7', 'rg-shared')
params: {
principalId: mi.outputs.principalId
}
}
module containerEnv 'containerEnvironment.bicep' = {
name: 'containerEnv'
scope: rgBff
params: {
location: location
environment: environment
userAssignedIdentityId: mi.outputs.id
}
}

View File

@@ -0,0 +1,47 @@
import { Environment, EnvironmentVar } from 'types.bicep'
targetScope = 'subscription'
param environment Environment
param containerImageTag string = 'latest'
param primaryApiKey string
param secondaryApiKey string
@description('The location for the resource group')
param location string = 'westeurope'
resource rgRedisApi 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-redis-api-${environment}'
location: location
}
module mi 'managedIdentity.bicep' = {
name: 'mi-redis-api-${environment}'
scope: rgRedisApi
params: {
principalName: 'mi-redis-api-${environment}'
location: location
}
}
module redis 'cache/redis.bicep' = {
name: 'redisCache'
scope: rgRedisApi
params: {
location: location
environment: environment
}
}
module containerApp 'app/main.bicep' = {
name: 'containerApp'
params: {
location: location
environment: environment
containerImageTag: containerImageTag
redisConnection: 'default:${redis.outputs.primaryAccessKey}@${redis.outputs.hostname}:6380'
primaryApiKey: primaryApiKey
secondaryApiKey: secondaryApiKey
}
}

View File

@@ -0,0 +1,10 @@
param location string = 'westeurope'
param principalName string
resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: principalName
location: location
}
output principalId string = mi.properties.principalId
output id string = mi.id

View File

@@ -0,0 +1,5 @@
@description('Pull artifacts from a container registry. Ref: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/containers#acrpull')
resource rd 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' existing = {
name: '7f951dda-4ed3-4680-a7ca-43fe172d538d'
}
output id string = rd.id

View File

@@ -0,0 +1,9 @@
@export()
@description('Type with allowed environments.')
type Environment = 'test' | 'prod'
@export()
type EnvironmentVar = {
name: string
value: string
}