diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..e6fadf51 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* marcelloraffaele@gmail.com @marcelloraffaele @rmarcello@microsoft.com @rmarcello \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..aa82e69d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,94 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '19 19 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: none + - language: ruby + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..3ead32a2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,40 @@ +name: Azure Bicep + +on: + workflow_dispatch: + inputs: + appenv: + type: choice + description: Choose the target environment + options: + - dev + - test + - prod + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + steps: + # Checkout code + - uses: actions/checkout@main + + # Log into Azure + - uses: azure/login@v2.1.1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + # Deploy ARM template + - name: Run ARM deploy + uses: azure/arm-deploy@v1 + with: + subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + resourceGroupName: ${{ secrets.AZURE_RG }} + template: ./src/InfrastructureAsCode/main.bicep + parameters: environment=${{ github.event.inputs.appenv }} \ No newline at end of file diff --git a/.github/workflows/dotnet-deploy.yml b/.github/workflows/dotnet-deploy.yml new file mode 100644 index 00000000..a78734a6 --- /dev/null +++ b/.github/workflows/dotnet-deploy.yml @@ -0,0 +1,135 @@ +name: .NET CI + +env: + registryName: cq5qj5cc3ob6mmpnpreg.azurecr.io + repositoryName: techexcel/dotnetcoreapp + dockerFolderPath: ./src/Application/src/RazorPagesTestSample + tag: ${{github.run_number}} + +on: + push: + branches: [ main ] + paths: src/Application/** + pull_request: + branches: [ main ] + paths: src/Application/** + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0 + + - name: Restore dependencies + run: dotnet restore ./src/Application/src/RazorPagesTestSample/RazorPagesTestSample.csproj + - name: Build + run: dotnet build --no-restore ./src/Application/src/RazorPagesTestSample/RazorPagesTestSample.csproj + - name: Test + run: dotnet test --verbosity normal ./src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj + - uses: actions/github-script@v6 + if: failure() + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + let body = "${{ env.build_name }} Workflow Failure \n Build Number: ${{ github.run_number }} \n Build Log: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} \n SHA: [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) \n"; + github.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: "${{ env.build_name }} Workflow ${{ github.run_number }} Failed! ", + body: body + }); + + dockerBuildPush: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v3 + + - name: Docker Login + # You may pin to the exact commit or the version. + # uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c + uses: docker/login-action@v1.9.0 + with: + # Server address of Docker registry. If not set then will default to Docker Hub + registry: ${{ secrets.ACR_LOGIN_SERVER }} + # Username used to log against the Docker registry + username: ${{ secrets.ACR_USERNAME }} + # Password or personal access token used to log against the Docker registry + password: ${{ secrets.ACR_PASSWORD }} + # Log out from the Docker registry at the end of a job + logout: true + + - name: Docker Build + run: docker build -t $registryName/$repositoryName:$tag --build-arg build_version=$tag $dockerFolderPath + + - name: Docker Push + run: docker push $registryName/$repositoryName:$tag + + deploy-to-dev: + + runs-on: ubuntu-latest + needs: dockerBuildPush + environment: + name: dev + url: https://cq5qj5cc3ob6m-dev.azurewebsites.net/ + + steps: + - name: 'Login via Azure CLI' + uses: azure/login@v2.1.1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - uses: azure/webapps-deploy@v2 + with: + app-name: 'cq5qj5cc3ob6m-dev' + images: cq5qj5cc3ob6mmpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}} + + deploy-to-test: + + runs-on: ubuntu-latest + needs: deploy-to-dev + environment: + name: test + url: https://cq5qj5cc3ob6m-test.azurewebsites.net/ + + steps: + - uses: actions/checkout@v3 + + - name: 'Login via Azure CLI' + uses: azure/login@v2.1.1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - uses: azure/webapps-deploy@v2 + with: + app-name: 'cq5qj5cc3ob6m-test' + images: cq5qj5cc3ob6mmpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}} + + deploy-to-prod: + + runs-on: ubuntu-latest + needs: deploy-to-test + environment: + name: prod + url: https://cq5qj5cc3ob6m-prod.azurewebsites.net/ + + steps: + - uses: actions/checkout@v3 + + - name: 'Login via Azure CLI' + uses: azure/login@v2.1.1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - uses: azure/webapps-deploy@v2 + with: + app-name: 'cq5qj5cc3ob6m-prod' + images: cq5qj5cc3ob6mmpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}} \ No newline at end of file diff --git a/.github/workflows/first-workflow.yml b/.github/workflows/first-workflow.yml new file mode 100644 index 00000000..41788265 --- /dev/null +++ b/.github/workflows/first-workflow.yml @@ -0,0 +1,23 @@ +name: First Workflow +on: + workflow_dispatch: + issues: + types: [opened] + +jobs: + job1: + runs-on: ubuntu-latest + steps: + - name: Echo Job 1 + env: + SECRET: ${{ secrets.MY_SECRET_VAR }} + run: echo $SECRET + + job2: + runs-on: ubuntu-latest + steps: + - name: Ship it + uses: mscoutermarsh/cowsays-action@master + with: + text: 'Ready for prod–ship it!' + color: 'magenta' \ No newline at end of file diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index cedae546..00000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,62 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll site to Pages - -on: - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow one concurrent deployment -concurrency: - group: "pages" - cancel-in-progress: true - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' # Not needed with a .ruby-version file - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - cache-version: 0 # Increment this number if you need to re-download cached gems - - name: Setup Pages - id: pages - uses: actions/configure-pages@v2 - - name: Build with Jekyll - # Outputs to the './_site' directory by default - run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" - env: - JEKYLL_ENV: production - - name: Upload artifact - # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 - - # Deployment job - deploy: - environment: - name: github-pages - url: "${{ steps.deployment.outputs.page_url }}" - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v1 diff --git a/.github/workflows/undeploy.yml b/.github/workflows/undeploy.yml new file mode 100644 index 00000000..f8f72902 --- /dev/null +++ b/.github/workflows/undeploy.yml @@ -0,0 +1,42 @@ +name: Destroy Azure Resource Group + +on: + workflow_dispatch: + inputs: + appenv: + type: choice + description: Choose the target environment + options: + - dev + - test + - prod + +jobs: + destroy: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + # Checkout code + - uses: actions/checkout@main + + # Log into Azure + - uses: azure/login@v2.1.1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + # Destroy Resource Group + - name: Destroy Resource Group + run: | + az group delete --name ${{ secrets.AZURE_RG }} --yes --no-wait + +# - name: Azure ARM Deployment +# uses: azure/ARM@v1 +# with: +# azcliversion: 2.0.72 +# inlineScript: | +# az group delete --name ${{ secrets.AZURE_RG }} --yes --no-wait \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9bea4330..62a88f7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store +src/Application/src/RazorPagesTestSample/config.json diff --git a/Gemfile.lock b/Gemfile.lock index 045212ad..492a25c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,13 +56,11 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.3.6) - strscan + rexml (3.3.9) rouge (4.0.0) safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.3.0) diff --git a/README.md b/README.md index 21027307..d6208d99 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,6 @@ trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. + +## just a modify +modification diff --git a/src/Application/src/RazorPagesTestSample/Data/Message.cs b/src/Application/src/RazorPagesTestSample/Data/Message.cs index ea99cbd6..7e8955d2 100644 --- a/src/Application/src/RazorPagesTestSample/Data/Message.cs +++ b/src/Application/src/RazorPagesTestSample/Data/Message.cs @@ -9,8 +9,8 @@ public class Message [Required] [DataType(DataType.Text)] - [StringLength(200, ErrorMessage = "There's a 200 character limit on messages. Please shorten your message.")] + [StringLength(250, ErrorMessage = "There's a 250 character limit on messages. Please shorten your message.")] public string Text { get; set; } } #endregion -} +} \ No newline at end of file diff --git a/src/Application/src/RazorPagesTestSample/Dockerfile b/src/Application/src/RazorPagesTestSample/Dockerfile new file mode 100644 index 00000000..ab3fcaf2 --- /dev/null +++ b/src/Application/src/RazorPagesTestSample/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env +WORKDIR /app + +# Copy csproj and restore as distinct layers +COPY *.csproj ./ +RUN dotnet restore + +# Copy everything else and build +COPY . ./ +RUN dotnet publish -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR /app +COPY --from=build-env /app/out . +# Default ASP.NET port changed with .NET 8.0 +ENV ASPNETCORE_HTTP_PORTS=80 +ENTRYPOINT ["dotnet", "RazorPagesTestSample.dll"] \ No newline at end of file diff --git a/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs b/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs index 2e6d94bb..17d8e38a 100644 --- a/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs +++ b/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs @@ -72,12 +72,6 @@ public async Task OnPostAnalyzeMessagesAsync() } else { - // Speed loop. Lower this number once every quarter so we - // get our performance improvement quarterly bonus. - for (int i = 0; i < 3000; i++) { - Thread.Sleep(1); - } - var wordCount = 0; foreach (var message in Messages) @@ -94,7 +88,15 @@ public async Task OnPostAnalyzeMessagesAsync() public static void WriteToDirectory(ZipArchiveEntry entry, string destDirectory) { - string destFileName = Path.Combine(destDirectory, entry.FullName); + string destFileName = Path.GetFullPath(Path.Combine(destDirectory, entry.FullName)); + string fullDestDirPath = Path.GetFullPath(destDirectory + Path.DirectorySeparatorChar); + + // Ensure the destination file is within the destination directory + if (!destFileName.StartsWith(fullDestDirPath, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Entry is trying to write outside of the destination directory."); + } + entry.ExtractToFile(destFileName); } } diff --git a/src/Application/src/RazorPagesTestSample/config.json b/src/Application/src/RazorPagesTestSample/config.json deleted file mode 100644 index 1a3874d4..00000000 --- a/src/Application/src/RazorPagesTestSample/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "registry_key": "8yYKDsFTjatiQI9nVcsoQ1P3rdnh+P5Mlz9gVBgVgx+ACRArmBQ3", - "registry_type": "AzureContainerRegistry" -} \ No newline at end of file diff --git a/src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj b/src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj index a66e0a92..079d0c9f 100644 --- a/src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj +++ b/src/Application/tests/RazorPagesTestSample.Tests/RazorPagesTestSample.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Application/tests/RazorPagesTestSample.Tests/UnitTests/DataAccessLayerTest.cs b/src/Application/tests/RazorPagesTestSample.Tests/UnitTests/DataAccessLayerTest.cs index 91a91aaa..4671de80 100644 --- a/src/Application/tests/RazorPagesTestSample.Tests/UnitTests/DataAccessLayerTest.cs +++ b/src/Application/tests/RazorPagesTestSample.Tests/UnitTests/DataAccessLayerTest.cs @@ -25,7 +25,7 @@ public async Task GetMessagesAsync_MessagesAreReturned() // Assert var actualMessages = Assert.IsAssignableFrom>(result); Assert.Equal( - expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), + expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), actualMessages.OrderBy(m => m.Id).Select(m => m.Text)); } } @@ -35,16 +35,17 @@ public async Task AddMessageAsync_MessageIsAdded() { using (var db = new AppDbContext(Utilities.TestDbContextOptions())) { - // Arrange + // Arrange var recId = 10; var expectedMessage = new Message() { Id = recId, Text = "Message" }; // Act await db.AddMessageAsync(expectedMessage); + var fakeMessage = new Message() { Id = recId, Text = "Invalid!" }; // Assert var actualMessage = await db.FindAsync(recId); - Assert.Equal(expectedMessage, actualMessage); + Assert.Equal(fakeMessage, actualMessage); } } @@ -77,7 +78,7 @@ public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound() await db.AddRangeAsync(seedMessages); await db.SaveChangesAsync(); var recId = 1; - var expectedMessages = + var expectedMessages = seedMessages.Where(message => message.Id != recId).ToList(); #endregion @@ -90,7 +91,7 @@ public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound() // Assert var actualMessages = await db.Messages.AsNoTracking().ToListAsync(); Assert.Equal( - expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), + expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), actualMessages.OrderBy(m => m.Id).Select(m => m.Text)); #endregion } @@ -121,10 +122,36 @@ public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound() // Assert var actualMessages = await db.Messages.AsNoTracking().ToListAsync(); Assert.Equal( - expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), + expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), actualMessages.OrderBy(m => m.Id).Select(m => m.Text)); } } #endregion + + // This picks up inside the DataAccessLayerTest class. + [Theory] + [InlineData(150, true)] + [InlineData(199, true)] + [InlineData(200, true)] + [InlineData(201, true)] + [InlineData(249, true)] + [InlineData(250, true)] + [InlineData(251, false)] + [InlineData(300, false)] + public async Task AddMessageAsync_TestMessageLength(int messageLength, bool expectedValidMessage) + { + using (var db = new AppDbContext(Utilities.TestDbContextOptions())) + { + // Arrange + var recId = 10; + var expectedMessage = new Message() { Id = recId, Text = new string('X', messageLength) }; + + // Act + var isValidMessage = Validator.TryValidateObject(expectedMessage, new ValidationContext(expectedMessage), null, validateAllProperties: true); + + // Assert + Assert.Equal(expectedValidMessage, isValidMessage); + } + } } } diff --git a/src/InfrastructureAsCode/main.bicep b/src/InfrastructureAsCode/main.bicep index 6dc69618..da87dd6d 100644 --- a/src/InfrastructureAsCode/main.bicep +++ b/src/InfrastructureAsCode/main.bicep @@ -8,10 +8,116 @@ var webAppName = '${uniqueString(resourceGroup().id)}-${environment}' var appServicePlanName = '${uniqueString(resourceGroup().id)}-mpnp-asp' var logAnalyticsName = '${uniqueString(resourceGroup().id)}-mpnp-la' var appInsightsName = '${uniqueString(resourceGroup().id)}-mpnp-ai' -var sku = 'S1' +var sku = 'P0V3' var registryName = '${uniqueString(resourceGroup().id)}mpnpreg' var registrySku = 'Standard' var imageName = 'techexcel/dotnetcoreapp' var startupCommand = '' +var redisCacheName = '${uniqueString(resourceGroup().id)}-mpnp-redis' +var redisCacheSku = 'Basic' -// TODO: complete this script +resource redisCache 'Microsoft.Cache/Redis@2023-08-01' = { + name: redisCacheName + location: location + properties: { + sku: { + name: redisCacheSku + family: 'C' + capacity: 0 + } + enableNonSslPort: false + minimumTlsVersion: '1.2' + redisConfiguration: { + 'maxmemory-policy': 'volatile-lru' + } + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: logAnalyticsName + location: location + properties: { + sku: { + name: 'PerGB2018' + } + retentionInDays: 90 + workspaceCapping: { + dailyQuotaGb: 1 + } + } +} + +resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' = { + name: appInsightsName + location: location + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspace.id + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2020-11-01-preview' = { + name: registryName + location: location + sku: { + name: registrySku + } + properties: { + adminUserEnabled: true + } +} + +resource appServicePlan 'Microsoft.Web/serverFarms@2022-09-01' = { + name: appServicePlanName + location: location + kind: 'linux' + properties: { + reserved: true + } + sku: { + name: sku + } +} + +resource appServiceApp 'Microsoft.Web/sites@2020-12-01' = { + name: webAppName + location: location + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + clientAffinityEnabled: false + siteConfig: { + linuxFxVersion: 'DOCKER|${containerRegistry.name}.azurecr.io/${uniqueString(resourceGroup().id)}/${imageName}' + http20Enabled: true + minTlsVersion: '1.2' + appCommandLine: startupCommand + appSettings: [ + { + name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE' + value: 'false' + } + { + name: 'DOCKER_REGISTRY_SERVER_URL' + value: 'https://${containerRegistry.name}.azurecr.io' + } + { + name: 'DOCKER_REGISTRY_SERVER_USERNAME' + value: containerRegistry.name + } + { + name: 'DOCKER_REGISTRY_SERVER_PASSWORD' + value: containerRegistry.listCredentials().passwords[0].value + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: appInsights.properties.InstrumentationKey + } + ] + } + } +} + +output application_name string = appServiceApp.name +output application_url string = appServiceApp.properties.hostNames[0] +output container_registry_name string = containerRegistry.name