diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml new file mode 100644 index 0000000..b5f8625 --- /dev/null +++ b/.github/workflows/app.yml @@ -0,0 +1,70 @@ +name: Deploy App + +on: + push: + paths-ignore: + - infrastructure/** + branches: + - main + pull_request: + paths-ignore: + - infrastructure/** + types: [opened, synchronize, reopened, closed] + branches: + - main + +env: + IMAGE_NAME: calque-app + +permissions: + id-token: write + contents: read + +jobs: + versionize: + name: Versionize + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + outputs: + semver: ${{ steps.gitversion.outputs.SemVer }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v1.1.1 + with: + versionSpec: "5.x" + - name: Determine Version + id: gitversion + uses: gittools/actions/gitversion/execute@v1.1.1 + with: + useConfigFile: true + + build_and_publish: + name: Build & Publish + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + needs: versionize + steps: + - uses: actions/checkout@v4 + + - name: Build container images + working-directory: src + run: | + docker build . --file Dockerfile --tag ${{ secrets.ACR_LOGIN_SERVER }}/$IMAGE_NAME:${{needs.versionize.outputs.semver}} + docker build . --file Dockerfile --tag ${{ secrets.ACR_LOGIN_SERVER }}/$IMAGE_NAME:latest + + - name: Login to ACR + uses: azure/docker-login@v2 + with: + login-server: ${{ secrets.ACR_LOGIN_SERVER }} + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Push container images + working-directory: src + run: | + docker push ${{ secrets.ACR_LOGIN_SERVER }}/$IMAGE_NAME:${{needs.versionize.outputs.semver}} + docker push ${{ secrets.ACR_LOGIN_SERVER }}/$IMAGE_NAME:latest diff --git a/.github/workflows/infra.yml b/.github/workflows/infra.yml new file mode 100644 index 0000000..a7f7af4 --- /dev/null +++ b/.github/workflows/infra.yml @@ -0,0 +1,33 @@ +name: Deploy infrastructure + +on: + push: + paths: + - infrastructure/** + branches: + - main + pull_request: + paths: + - infrastructure/** + types: [opened, synchronize, reopened, closed] + branches: + - main + +permissions: + id-token: write + contents: read + +jobs: + production: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + infrastructure + + - name: Deploy bicep + uses: Azure/cli@v2 + with: + inlineScript: | + az deployment sub create --location westeurope --template-file infrastructure/main.bicep --parameters infrastructure/params/prod.bicepparam \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 5fc0918..71c1b04 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,7 @@ { "type": "bun", "request": "launch", - "name": "Debug Bun", + "name": "Start dev", // The path to a JavaScript or TypeScript file to run. "program": "${file}", // The arguments to pass to the program, if any. @@ -29,6 +29,37 @@ // Unlike `args`, these are passed to the executable itself, not the program. "runtimeArgs": [], }, + { + "type": "bun", + "request": "launch", + "name": "Run tests", + // The path to a JavaScript or TypeScript file to run. + "program": "${file}", + // The arguments to pass to the program, if any. + "args": [], + // The working directory of the program. + "cwd": "${workspaceFolder}", + // The environment variables to pass to the program. + "env": {}, + // If the environment variables should not be inherited from the parent process. + "strictEnv": false, + // If the program should be run in watch mode. + // This is equivalent to passing `--watch` to the `bun` executable. + // You can also set this to "hot" to enable hot reloading using `--hot`. + "watchMode": false, + // If the debugger should stop on the first line of the program. + "stopOnEntry": false, + // If the debugger should be disabled. (for example, breakpoints will not be hit) + "noDebug": false, + // The path to the `bun` executable, defaults to your `PATH` environment variable. + "runtime": "bun", + // The arguments to pass to the `bun` executable, if any. + // Unlike `args`, these are passed to the executable itself, not the program. + "runtimeArgs": [ + "--bun", + "test" + ], + }, { "type": "bun", "request": "attach", diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1949db3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM oven/bun:1 as base +WORKDIR /usr/src/app + +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lockb /temp/dev +RUN cd /temp/dev && bun install --frozen-lockfile + +RUN mkdir -p /temp/prod +COPY package.json bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +ENV NODE_ENV=production +ENV SERVER_PRESET=bun +RUN bun test +RUN chmod +x node_modules/.bin/* +RUN bun run build + +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /usr/src/app/bun.lockb . +COPY --from=prerelease /usr/src/app/package.json . +COPY --from=prerelease /usr/src/app/.vinxi .vinxi +COPY --from=prerelease /usr/src/app/.output .output + +USER bun +EXPOSE 3000/tcp +ENTRYPOINT [ "bun", "run", "start" ] \ No newline at end of file diff --git a/infrastructure/app.bicep b/infrastructure/app.bicep new file mode 100644 index 0000000..04a4bdd --- /dev/null +++ b/infrastructure/app.bicep @@ -0,0 +1,90 @@ +import { Context } from 'types.bicep' + +targetScope = 'resourceGroup' + +param context Context +param registry resource'Microsoft.ContainerRegistry/registries@2023-07-01' + +var appName = 'app' +var version = 'latest' + +resource environment 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: 'acr-${context.locationAbbreviation}-${context.environment}-${context.projectName}' + location: context.location + properties: { + appLogsConfiguration: { + destination: 'azure-monitor' + } + } +} + +resource app 'Microsoft.App/containerApps@2024-03-01' = { + name: 'acr-${context.locationAbbreviation}-${context.environment}-${context.projectName}-app' + location: context.location + identity: { + type: 'SystemAssigned' + } + properties: { + environmentId: environment.id + + configuration: { + activeRevisionsMode: 'Single' + + ingress: { + external: true + targetPort: 8080 + transport: 'http2' + allowInsecure: false + traffic: [ + { + weight: 100 + latestRevision: true + } + ] + corsPolicy: { + allowedOrigins: [ + // 'https://localhost:3000' + '*' + ] + allowCredentials: true + allowedHeaders: ['*'] + allowedMethods: ['Get, POST'] + maxAge: 0 + } + } + registries: [ + { + identity: 'system' + server: registry.properties.loginServer + } + ] + } + + template: { + containers: [ + { + image: '${registry.properties.loginServer}/${context.projectName}-${appName}:${version}' + name: '${context.projectName}-${appName}' + resources: { + cpu: json('0.25') + memory: '0.5Gi' + } + } + ] + scale: { + minReplicas: 1 + maxReplicas: 2 + rules: [ + { + name: 'http-rule' + http: { + metadata: { + concurrentRequests: '50' + } + } + } + ] + } + } + } +} diff --git a/infrastructure/bicepconfig.json b/infrastructure/bicepconfig.json new file mode 100644 index 0000000..09945bd --- /dev/null +++ b/infrastructure/bicepconfig.json @@ -0,0 +1,11 @@ +{ + "experimentalFeaturesEnabled": { + "assertions": true, + "testFramework": true, + "extensibility": true, + "resourceDerivedTypes": true, + "resourceTypedParamsAndOutputs": true, + "sourceMapping": true, + "symbolicNameCodegen": true + } +} \ No newline at end of file diff --git a/infrastructure/main.bicep b/infrastructure/main.bicep new file mode 100644 index 0000000..80ba66f --- /dev/null +++ b/infrastructure/main.bicep @@ -0,0 +1,46 @@ +import { Context } from 'types.bicep' + +targetScope = 'subscription' + +param locationAbbreviation string +param location string +param environment string +param projectName string +param deployedAt string = utcNow('yyyyMMdd') + +var context = { + locationAbbreviation: locationAbbreviation + location: location + environment: environment + projectName: projectName + deployedAt: deployedAt +} + +resource calqueResourceGroup 'Microsoft.Resources/resourceGroups@2024-07-01' = { + name: 'rg-${locationAbbreviation}-${environment}-${projectName}' + location: location +} + +module monitoring 'monitoring.bicep' = { + name: 'monitoring' + scope: calqueResourceGroup + params: { + context: context + } +} + +module registry 'registry.bicep' = { + name: 'registry' + scope: calqueResourceGroup + params: { + context: context + } +} + +module app 'app.bicep' = { + name: 'app' + scope: calqueResourceGroup + params: { + context: context + } +} diff --git a/infrastructure/monitoring.bicep b/infrastructure/monitoring.bicep new file mode 100644 index 0000000..01079da --- /dev/null +++ b/infrastructure/monitoring.bicep @@ -0,0 +1,11 @@ +import { Context } from 'types.bicep' + +targetScope = 'resourceGroup' + +param context Context + +// resource monitoring 'Microsoft.___/___@___' = { +// name: 'acr-${context.locationAbbreviation}-${context.environment}-${context.projectName}' +// location: context.location +// properties: {} +// } diff --git a/infrastructure/params/prod.bicepparam b/infrastructure/params/prod.bicepparam new file mode 100644 index 0000000..539b3aa --- /dev/null +++ b/infrastructure/params/prod.bicepparam @@ -0,0 +1,6 @@ +using '../main.bicep' + +param locationAbbreviation = 'euw' +param location = 'westeurope' +param environment = 'prd' +param projectName = 'calque' diff --git a/infrastructure/registry.bicep b/infrastructure/registry.bicep new file mode 100644 index 0000000..f086cc7 --- /dev/null +++ b/infrastructure/registry.bicep @@ -0,0 +1,16 @@ +import { Context } from 'types.bicep' + +targetScope = 'resourceGroup' + +param context Context + +resource registry 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: 'acr-${context.locationAbbreviation}-${context.environment}-${context.projectName}' + location: context.location + sku: { + name: 'Basic' + } + properties: {} +} + +output registry resource'Microsoft.ContainerRegistry/registries@2023-07-01' = registry diff --git a/infrastructure/types.bicep b/infrastructure/types.bicep new file mode 100644 index 0000000..f892c3f --- /dev/null +++ b/infrastructure/types.bicep @@ -0,0 +1,8 @@ +@export() +type Context = { + locationAbbreviation: string + location: string + environment: string + projectName: string + deployedAt: string +}