Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ region, and participant object IDs. The participant already has Owner on that
group. There is no interactive login, Graph application/service-principal creation,
confidential computing, Azure Local, or shared participant Kubernetes cluster here.

Default scope: **resourcegroup**, **two labs per subscription**, in **Sweden Central**
Default scope: **resourcegroup**, **five labs per subscription**, in **Sweden Central**
(`swedencentral`). Organizers may override the event region before creating lab
scopes; the shared preflight must pass for that subscription and region. Every participant
gets a dedicated two-node AKS cluster and a separate private K3s VM. The organizer
needs a **Linux runner**, PowerShell 7, current Az.Accounts/Az.Resources/Az.Network,
Azure CLI, a Bicep compiler usable by Az deployments, Bash, curl, tar, jq, awk,
sha256sum and standard Linux utilities. The explicit
`../resources/install-console-tools.sh` installs the versioned client toolchain
downloaded `resources/install-console-tools.sh` installs the versioned client toolchain
and Bastion extension before any bootstrap phase. It does not authenticate.
Outbound access to Azure, GitHub, Kubernetes/Helm/K3s release endpoints and
container registries is required. Package the **whole MicroHack**, including
`resources/`, `iac/` (including `iac/bicepconfig.json` and its referenced files),
not only this folder.
container registries is required, including HTTPS access to
`raw.githubusercontent.com`. Console can publish this automation folder as its
`lab/` folder without sibling workshop source directories. The hook downloads
only the required files from `microsoft/MicroHack`, using the immutable commit
and per-file SHA-256 hashes in `bootstrap-source.json`. No Git installation or
GitHub credential is required.

Successful provisioning prepares **platform state through challenge 05**.
Participants still complete **challenge 01's workstation setup** before connecting:
Expand Down Expand Up @@ -65,8 +68,8 @@ prerequisites. No GitHub or AI API credentials are provisioned by these hooks.
without a **Location** restriction, and for available family and total regional
compute quota. A Zone restriction does not exclude this non-zonal topology.
4. Require **16 free vCPUs per lab**: 8 for the AKS nodes, 4 for the K3s VM and
4 for one AKS upgrade surge node. Two new labs therefore require **32 free
vCPUs**, although their steady-state baseline is 24. Both `standardDSv5Family`
4 for one AKS upgrade surge node. Five new labs therefore require **80 free
vCPUs**, although their steady-state baseline is 60. Both `standardDSv5Family`
and `cores` are checked after subtracting existing usage. The shared check
intentionally budgets the entire requested fan-out as new capacity, including
on reruns; arrange sufficient free quota before repeating it. It does not
Expand Down Expand Up @@ -128,13 +131,15 @@ Console mounts the source content **read-only**. The hook uses a private,
GUID-named HOME under the worker's writable `[IO.Path]::GetTempPath()` for `.kube`,
`.rad`, `.ssh`, downloaded clients and scratch files; it never creates directories
under the mounted source. The platform's **absolute,
isolated `AZURE_CONFIG_DIR` is preserved**. The complete `resources/` and `iac/`
directories (including `iac/bicepconfig.json` and hidden files) are copied into
a private working lab root beneath that HOME. All Bash phases run from this copy,
whose owner-write permissions are enabled without modifying the read-only source,
so generated `artifacts/types.tgz` and recipe artifacts cannot race with another
participant or modify the source checkout. Existing source `artifacts/` are not
copied. There is no root-level Bicep configuration in the package.
isolated `AZURE_CONFIG_DIR` is preserved**. Before participant provisioning, the
hook downloads the allowlisted `resources/` and `iac/` files into a private working
lab root beneath that HOME and verifies every hash. Downloads have bounded
timeouts and retries. A missing file, network failure or hash mismatch stops the
hook before ARM deployment or Bash execution; it never falls back to `main`.
All six phases reuse the verified files. Generated `artifacts/types.tgz` and
recipe artifacts cannot race with another participant or modify the source
checkout. No credentials or local configuration are downloaded, and all temporary
files are removed on success or failure. There is no root-level Bicep configuration.
A host-assigned ephemeral loopback port
is passed as `K3S_LOCAL_PORT`; tunnel startup still detects a port collision and
fails rather than taking another lab's tunnel. HOME/PATH/environment are restored
Expand Down Expand Up @@ -166,7 +171,7 @@ bearer token, registry password or Kubernetes private key is emitted.

## Rough costs and cleanup

Budget **US$35 per lab per 24 hours**, or about **US$70/day for the default two
Budget **US$35 per lab per 24 hours**, or about **US$175/day for the default five
labs**, before optional exercises and significant traffic. This is an approximate
pay-as-you-go planning allowance, not a quote:

Expand All @@ -190,6 +195,28 @@ remove them when releasing a dedicated subscription, but must not remove an
initiative still used by another hosted event. No cleanup hook deletes resources
automatically on deployment failure.

## Maintaining the bootstrap source pin

The workshop's `resources/` and `iac/` are the only editable source copies.
`bootstrap-source.json` contains only a commit identifier and file hashes, not
duplicate code. When bootstrap inputs change, publish them to `microsoft/MicroHack`
first, then update the pin to that published commit:

```powershell
pwsh -File "./03-Azure/01-01-App Innovation/04-adaptive-apps/labautomation/update-bootstrap-source.ps1" -Commit "<full-40-character-commit-sha>"
```

The updater downloads that revision and verifies it matches the local sources
before writing the manifest. Use `-Check` instead of `-Commit` for an offline
local-source/hash check. Tests reject a stale pin, so source edits deliberately
require refreshing the pin before publishing the automation update.

This is a two-step release when source changes are not yet published: first
publish the canonical inputs, then publish the verified manifest update. If an
input file is added, update the allowlist in `Get-AdaptiveBootstrapFiles` as well.
After publishing the automation update, refresh/reimport the content in Console
before retrying. A pin update is unnecessary for documentation-only changes.

## Offline tests and live acceptance

With PowerShell 7, Pester 5 and Bicep installed, from the repository root:
Expand All @@ -203,7 +230,8 @@ tools outside PATH. The runner confines Pester scratch files to a unique directo
under the current checkout and removes it on exit. Tests mock Azure and Console
commands; they cover quota boundaries, PostgreSQL capability schemas, failure
propagation, packaging, region consistency, token refresh, phase ordering, output
hygiene, read-only source compatibility, private-directory cleanup, and compiled
hygiene, a read-only lab-only Console layout with no sibling sources, immutable
source pins, hash mismatches, download failures, private-directory cleanup, and compiled
ARM resource properties. Only the local test runner puts its test scratch beneath
a writable checkout; the production hook uses writable worker scratch instead.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"commit": "98b452ef419fcb1f94c78c2a6d18b125004e9bea",
"files": {
"resources/install-console-tools.sh": "dd0a4e8fd762d469e74797086164ba20ac76af5bb216bbbabdd2aefae7a61334",
"resources/bootstrap-console.sh": "3785f5120af2a817485e4930e476023b41ae874bfa70438b70d43a5a775b809c",
"resources/azure-session.sh": "31cf81870be4d08cb73da18929e49dbce1543fbd31f198b17ee281ba40bb8eca",
"resources/console-session.sh": "6a5af1db76fdb611882caa057d3e0d5abd3a443d5d70eaf93e7dc11a5a3a427c",
"resources/verify-recipes.sh": "cd0bd7a4d08473be9b2c71b94973c4eda4dc64c5477343a003e0cd83b4ff6210",
"resources/connect-console.sh": "2dca1544de5726dc4ff1d636566ad6f3ccf8ee1beff919532a32c0a55649c246",
"resources/prepare-k3s-azure-vm.sh": "ac17a02d6aa15ad99d086580afd3c3bd0da0fe86b2f3bc64e51ae4f2e27fe949",
"resources/deploy-radius-aks.sh": "57b85557d839702e4222f67c62b25cfb52846d64e9f3a58e7d52eea23d209f63",
"resources/deploy-radius-k3s.sh": "2abe585c081466b40b4c5934aa5401086d486588aeb94f5df9a53b16dedcff96",
"resources/configure-resource-types-aks.sh": "880fdc356ee9c253bea6a2c27a9f33e5c1aae01724cdad4cbc08dc0ce3a6a274",
"resources/configure-resource-types-k3s.sh": "0135477202c347c284f9978556297eb13d7bb99d2ebbe1d24d6fb86e3bd9305c",
"resources/configure-recipes.sh": "d1591583364163722a6fa9c076be1c0ecbf104bc19cd00188bc38f5b00abcaf2",
"iac/bicepconfig.json": "3f75d2cfc9f3938dad9f0db5e90d62332aab5861e3f13326fcb32c1f626051ee",
"iac/aks-env.bicep": "7fb58291e1380bcf4a8e1a889b1b3c7b0eb8749da98f764a6f7f42edfcb12f80",
"iac/local-env.bicep": "7ea33c8764e6e2d1d6475745b9b8c0fb5af94ef8c47ce90f846fe2c117f02daa",
"iac/sql-databases.yaml": "5b6bdbaa193f407541e81a7b148ef9b4c4886b7f5bf75256d1eb642d4a6ddf66",
"iac/recipes/postgres-azure-flex.bicep": "d4ab3daa3d32e41bd1403aea8425d938ceda0575d63227977272ade389819d73",
"iac/recipes/postgres-kubernetes.bicep": "4a4fd0991bcf5ea99e8a4e5d5922fea15d4afcd166879db00971eaf0c7d3477f",
"iac/recipes/sql-server.bicep": "08792c840a9dd237273a11124dcece1759c8d0dbdce7c284f63dfe4f6ec9e31c",
"iac/recipes/trading-schema.sql": "b9d3fa06d9c973daf6bb268effa0e94d43ad812b830a461932fa24e6817477b4"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ function Invoke-AdaptiveBash {
}
}

function Assert-AdaptivePackage {
param([Parameter(Mandatory)][string]$LabRoot)
foreach ($path in @(
function Get-AdaptiveBootstrapFiles {
@(
'resources/install-console-tools.sh', 'resources/bootstrap-console.sh',
'resources/azure-session.sh', 'resources/console-session.sh', 'resources/verify-recipes.sh',
'resources/connect-console.sh', 'resources/prepare-k3s-azure-vm.sh',
Expand All @@ -84,9 +83,70 @@ function Assert-AdaptivePackage {
'iac/aks-env.bicep', 'iac/local-env.bicep', 'iac/sql-databases.yaml',
'iac/recipes/postgres-azure-flex.bicep', 'iac/recipes/postgres-kubernetes.bicep',
'iac/recipes/sql-server.bicep', 'iac/recipes/trading-schema.sql'
)) {
)
}

function Assert-AdaptivePackage {
param([Parameter(Mandatory)][string]$LabRoot)
foreach ($path in Get-AdaptiveBootstrapFiles) {
if (-not (Test-Path -LiteralPath (Join-Path $LabRoot $path))) {
throw "Incomplete Adaptive Apps package: missing '$path'. Package the entire MicroHack, including resources/ and iac/, not just labautomation/."
throw "Incomplete Adaptive Apps sources: missing '$path'. Verify the pinned source revision and refresh the Console content before retrying."
}
}
}

function Read-AdaptiveSourceManifest {
param([Parameter(Mandatory)][string]$Path)
$manifest = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop
if ($manifest.commit -isnot [string] -or $manifest.commit -cnotmatch '^[0-9a-f]{40}$') {
throw 'Bootstrap source must be pinned to a full, lowercase Git commit SHA; branches and tags are not accepted.'
}
$paths = @(Get-AdaptiveBootstrapFiles)
if ($manifest.files -isnot [Collections.IDictionary] -or $manifest.files.Count -ne $paths.Count) {
throw 'Bootstrap source manifest must contain exactly the required file allowlist.'
}
foreach ($path in $paths) {
if ($manifest.files[$path] -isnot [string] -or $manifest.files[$path] -notmatch '^[0-9a-f]{64}$') {
throw "Bootstrap source manifest is missing a valid SHA-256 hash for '$path'."
}
}
return $manifest
}

function Save-AdaptiveSourceFile {
param(
[Parameter(Mandatory)][ValidatePattern('^[0-9a-f]{40}$')][string]$Commit,
[Parameter(Mandatory)][string]$RelativePath,
[Parameter(Mandatory)][string]$Destination
)
if ($RelativePath -cnotin @(Get-AdaptiveBootstrapFiles)) {
throw "Bootstrap download path '$RelativePath' is not allowlisted."
}
$repositoryPath = "03-Azure/01-01-App Innovation/04-adaptive-apps/$RelativePath"
$encodedPath = ($repositoryPath.Split('/') | ForEach-Object { [Uri]::EscapeDataString($_) }) -join '/'
$uri = "https://raw.githubusercontent.com/microsoft/MicroHack/$Commit/$encodedPath"
New-Item -ItemType Directory -Path (Split-Path $Destination -Parent) -Force -ErrorAction Stop | Out-Null
try {
Invoke-WebRequest -Uri $uri -OutFile $Destination -TimeoutSec 60 `
-MaximumRetryCount 3 -RetryIntervalSec 2 -ErrorAction Stop
} catch {
throw "Unable to download '$RelativePath' at commit '$Commit': $($_.Exception.Message)"
}
}

function Receive-AdaptiveBootstrap {
param(
[Parameter(Mandatory)][hashtable]$Manifest,
[Parameter(Mandatory)][string]$Destination
)
foreach ($path in Get-AdaptiveBootstrapFiles) {
$target = Join-Path $Destination $path
Save-AdaptiveSourceFile -Commit $Manifest.commit -RelativePath $path -Destination $target
$hash = (Get-FileHash -LiteralPath $target -Algorithm SHA256 -ErrorAction Stop).Hash
if ($hash -ne $Manifest.files[$path]) {
throw "SHA-256 verification failed for bootstrap file '$path' at commit '$($Manifest.commit)'. No bootstrap commands will run."
}
}
Assert-AdaptivePackage -LabRoot $Destination
Write-Host "Verified all $($Manifest.files.Count) bootstrap files from microsoft/MicroHack commit $($Manifest.commit)."
}
Loading
Loading