diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..3ab04fcd
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,7 @@
+* @global-owner1 @global-owner2
+/docs/ @docs-owner
+/src/ @src-owner
+README.md @readme-owner
+*.js @js-owner
+*.css @css-owner
+/scripts/*.sh @scripts-owner
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..f798a996
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,40 @@
+name: Azure Bicep Advanced
+
+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..e0b557d7
--- /dev/null
+++ b/.github/workflows/dotnet-deploy.yml
@@ -0,0 +1,135 @@
+name: .NET CI
+
+env:
+ registryName: ut3bnazh3pnt6mpnpreg.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 --no-build --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://ut3bnazh3pnt6-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: 'ut3bnazh3pnt6-dev'
+ images: ut3bnazh3pnt6mpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}}
+
+ deploy-to-test:
+
+ runs-on: ubuntu-latest
+ needs: deploy-to-dev
+ environment:
+ name: test
+ url: https://ut3bnazh3pnt6-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: 'ut3bnazh3pnt6-test'
+ images: ut3bnazh3pnt6mpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}}
+
+ deploy-to-prod:
+
+ runs-on: ubuntu-latest
+ needs: deploy-to-test
+ environment:
+ name: prod
+ url: https://ut3bnazh3pnt6-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: 'ut3bnazh3pnt6-prod'
+ images: ut3bnazh3pnt6mpnpreg.azurecr.io/techexcel/dotnetcoreapp:${{github.run_number}}
diff --git a/.github/workflows/first-workflow.yml b/.github/workflows/first-workflow.yml
new file mode 100644
index 00000000..37d1177c
--- /dev/null
+++ b/.github/workflows/first-workflow.yml
@@ -0,0 +1,39 @@
+name: First Workflow
+
+on:
+ workflow_dispatch:
+ issues:
+ types: [opened]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Run a one-line script
+ run: echo "Hello, GitHub Actions!"
+
+ job1:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Step 1
+ run: echo "Step 1 complete!"
+
+ - name: Step 2
+ run: echo "Step 2 complete!"
+
+ job2:
+ runs-on: ubuntu-latest
+ needs: job1 # Ensure job2 waits for job1 to complete
+
+
+ steps:
+ - name: Cowsays Action
+ 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/Solution/Exercise-03/Task-2/deploy.yml b/Solution/Exercise-03/Task-2/deploy.yml
index fb4c9ad1..7d6f719a 100644
--- a/Solution/Exercise-03/Task-2/deploy.yml
+++ b/Solution/Exercise-03/Task-2/deploy.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch
env:
- targetEnv: dev
+ targetEnv: test
jobs:
build-and-deploy:
diff --git a/src/Application/src/RazorPagesTestSample/Data/Message.cs b/src/Application/src/RazorPagesTestSample/Data/Message.cs
index ea99cbd6..f056d2fb 100644
--- a/src/Application/src/RazorPagesTestSample/Data/Message.cs
+++ b/src/Application/src/RazorPagesTestSample/Data/Message.cs
@@ -7,9 +7,18 @@ public class Message
{
public int Id { get; set; }
+ ///
+ /// Gets or sets the message text.
+ ///
+ ///
+ /// The message text, which is required and must be a string with a maximum length of 250 characters.
+ ///
+ ///
+ /// Thrown when the text exceeds the 200 character limit.
+ ///
[Required]
[DataType(DataType.Text)]
- [StringLength(200, ErrorMessage = "There's a 200 character limit on messages. Please shorten your message.")]
+ [StringLength(200, ErrorMessage = "There's a 250 character limit on messages. Please shorten your message.")]
public string Text { get; set; }
}
#endregion
diff --git a/Solution/Exercise-03/Task-3/Dockerfile b/src/Application/src/RazorPagesTestSample/Dockerfile
similarity index 96%
rename from Solution/Exercise-03/Task-3/Dockerfile
rename to src/Application/src/RazorPagesTestSample/Dockerfile
index 09b182ef..ab3fcaf2 100644
--- a/Solution/Exercise-03/Task-3/Dockerfile
+++ b/src/Application/src/RazorPagesTestSample/Dockerfile
@@ -1,18 +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
+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..c5edc192 100644
--- a/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs
+++ b/src/Application/src/RazorPagesTestSample/Pages/Index.cshtml.cs
@@ -68,7 +68,7 @@ public async Task OnPostAnalyzeMessagesAsync()
if (Messages.Count == 0)
{
- MessageAnalysisResult = "There are no messages to analyze.";
+ MessageAnalysisResult = "FEAURE BNEOIT There are no messages to analyze.";
}
else
{
@@ -86,7 +86,7 @@ public async Task OnPostAnalyzeMessagesAsync()
}
var avgWordCount = Decimal.Divide(wordCount, Messages.Count);
- MessageAnalysisResult = $"The average message length is {avgWordCount:0.##} words.";
+ MessageAnalysisResult = $"FEAURE BNEOIT The average message length is {avgWordCount:0.##} words.";
}
return RedirectToPage();
diff --git a/src/InfrastructureAsCode/federated-credential-params.json b/src/InfrastructureAsCode/federated-credential-params.json
new file mode 100644
index 00000000..69e52036
--- /dev/null
+++ b/src/InfrastructureAsCode/federated-credential-params.json
@@ -0,0 +1,9 @@
+{
+ "name": "GitHubDevOpsCredential3",
+ "issuer": "https://token.actions.githubusercontent.com",
+ "subject": "repo:bmoussaud/TechExcel-Accelerate-developer-productivity-with-GitHub-Copilot-and-Dev-Box:ref:refs/heads/main",
+ "description": "Deploy Azure resources from the TechExcel DevOps practices GitHub repo",
+ "audiences": [
+ "api://AzureADTokenExchange"
+ ]
+}
\ No newline at end of file
diff --git a/src/InfrastructureAsCode/main.bicep b/src/InfrastructureAsCode/main.bicep
index 6dc69618..d8e740d2 100644
--- a/src/InfrastructureAsCode/main.bicep
+++ b/src/InfrastructureAsCode/main.bicep
@@ -8,10 +8,98 @@ 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 = ''
-// TODO: complete this script
+
+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