Skip to content

Commit 59172c0

Browse files
Merge pull request #175 from microsoft/main
chore: Down merge from main to dev
2 parents 54002b4 + 3a386db commit 59172c0

3 files changed

Lines changed: 80 additions & 164 deletions

File tree

.github/workflows/deploy-orchestrator.yml

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,6 @@ jobs:
8787
cleanup_resources: ${{ inputs.cleanup_resources }}
8888
secrets: inherit
8989

90-
send-notification:
91-
if: "!cancelled()"
92-
needs: [docker-build, deploy]
93-
uses: ./.github/workflows/job-send-notification.yml
94-
with:
95-
trigger_type: ${{ inputs.trigger_type }}
96-
waf_enabled: ${{ inputs.waf_enabled }}
97-
EXP: ${{ inputs.EXP }}
98-
existing_webapp_url: ${{ inputs.existing_webapp_url }}
99-
deploy_result: ${{ needs.deploy.result }}
100-
WEB_APPURL: ${{ needs.deploy.outputs.WEB_APPURL || inputs.existing_webapp_url }}
101-
RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
102-
QUOTA_FAILED: ${{ needs.deploy.outputs.QUOTA_FAILED }}
103-
secrets: inherit
104-
10590
cleanup-deployment:
10691
if: "!cancelled() && needs.deploy.result == 'success' && needs.deploy.outputs.RESOURCE_GROUP_NAME != '' && inputs.existing_webapp_url == '' && (inputs.trigger_type != 'workflow_dispatch' || inputs.cleanup_resources)"
10792
needs: [docker-build, deploy]
@@ -117,3 +102,19 @@ jobs:
117102
ENV_NAME: ${{ needs.deploy.outputs.ENV_NAME }}
118103
IMAGE_TAG: ${{ needs.deploy.outputs.IMAGE_TAG }}
119104
secrets: inherit
105+
106+
send-notification:
107+
if: "!cancelled()"
108+
needs: [docker-build, deploy, cleanup-deployment]
109+
uses: ./.github/workflows/job-send-notification.yml
110+
with:
111+
trigger_type: ${{ inputs.trigger_type }}
112+
waf_enabled: ${{ inputs.waf_enabled }}
113+
EXP: ${{ inputs.EXP }}
114+
existing_webapp_url: ${{ inputs.existing_webapp_url }}
115+
deploy_result: ${{ needs.deploy.result }}
116+
cleanup_result: ${{ needs.cleanup-deployment.result }}
117+
WEB_APPURL: ${{ needs.deploy.outputs.WEB_APPURL || inputs.existing_webapp_url }}
118+
RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
119+
QUOTA_FAILED: ${{ needs.deploy.outputs.QUOTA_FAILED }}
120+
secrets: inherit

.github/workflows/job-send-notification.yml

Lines changed: 58 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ on:
4141
required: false
4242
default: 'false'
4343
type: string
44+
cleanup_result:
45+
description: 'Cleanup job result (success, failure, skipped)'
46+
required: false
47+
default: 'skipped'
48+
type: string
4449

4550
env:
4651
GPT_MIN_CAPACITY: 100
@@ -55,199 +60,104 @@ jobs:
5560
env:
5661
accelerator_name: "Container Migration"
5762
steps:
58-
- name: Validate Workflow Input Parameters
63+
- name: Determine Cleanup Status
64+
id: cleanup
5965
shell: bash
6066
env:
61-
INPUT_TRIGGER_TYPE: ${{ inputs.trigger_type }}
62-
INPUT_WAF_ENABLED: ${{ inputs.waf_enabled }}
63-
INPUT_EXP: ${{ inputs.EXP }}
64-
INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
65-
INPUT_DEPLOY_RESULT: ${{ inputs.deploy_result }}
66-
INPUT_WEB_APPURL: ${{ inputs.WEB_APPURL }}
67-
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
68-
INPUT_QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
67+
CLEANUP_RESULT: ${{ inputs.cleanup_result }}
6968
run: |
70-
echo "🔍 Validating workflow input parameters..."
71-
VALIDATION_FAILED=false
72-
73-
# Validate trigger_type (required - alphanumeric with underscores)
74-
if [[ -z "$INPUT_TRIGGER_TYPE" ]]; then
75-
echo "❌ ERROR: trigger_type is required but was not provided"
76-
VALIDATION_FAILED=true
77-
elif [[ ! "$INPUT_TRIGGER_TYPE" =~ ^[a-zA-Z0-9_]+$ ]]; then
78-
echo "❌ ERROR: trigger_type '$INPUT_TRIGGER_TYPE' is invalid. Must contain only alphanumeric characters and underscores"
79-
VALIDATION_FAILED=true
80-
else
81-
echo "✅ trigger_type: '$INPUT_TRIGGER_TYPE' is valid"
82-
fi
83-
84-
# Validate waf_enabled (boolean)
85-
if [[ "$INPUT_WAF_ENABLED" != "true" && "$INPUT_WAF_ENABLED" != "false" ]]; then
86-
echo "❌ ERROR: waf_enabled must be 'true' or 'false', got: '$INPUT_WAF_ENABLED'"
87-
VALIDATION_FAILED=true
88-
else
89-
echo "✅ waf_enabled: '$INPUT_WAF_ENABLED' is valid"
90-
fi
91-
92-
# Validate EXP (boolean)
93-
if [[ "$INPUT_EXP" != "true" && "$INPUT_EXP" != "false" ]]; then
94-
echo "❌ ERROR: EXP must be 'true' or 'false', got: '$INPUT_EXP'"
95-
VALIDATION_FAILED=true
96-
else
97-
echo "✅ EXP: '$INPUT_EXP' is valid"
98-
fi
99-
100-
# Validate existing_webapp_url (must start with https if provided)
101-
if [[ -n "$INPUT_EXISTING_WEBAPP_URL" ]]; then
102-
if [[ ! "$INPUT_EXISTING_WEBAPP_URL" =~ ^https:// ]]; then
103-
echo "❌ ERROR: existing_webapp_url must start with 'https://', got: '$INPUT_EXISTING_WEBAPP_URL'"
104-
VALIDATION_FAILED=true
105-
else
106-
echo "✅ existing_webapp_url: '$INPUT_EXISTING_WEBAPP_URL' is valid"
107-
fi
108-
fi
109-
110-
# Validate deploy_result (required, must be specific values)
111-
if [[ -z "$INPUT_DEPLOY_RESULT" ]]; then
112-
echo "❌ ERROR: deploy_result is required but not provided"
113-
VALIDATION_FAILED=true
114-
else
115-
ALLOWED_DEPLOY_RESULTS=("success" "failure" "skipped")
116-
if [[ ! " ${ALLOWED_DEPLOY_RESULTS[@]} " =~ " ${INPUT_DEPLOY_RESULT} " ]]; then
117-
echo "❌ ERROR: deploy_result '$INPUT_DEPLOY_RESULT' is invalid. Allowed values: ${ALLOWED_DEPLOY_RESULTS[*]}"
118-
VALIDATION_FAILED=true
119-
else
120-
echo "✅ deploy_result: '$INPUT_DEPLOY_RESULT' is valid"
121-
fi
122-
fi
123-
124-
# Validate WEB_APPURL (must start with https if provided)
125-
if [[ -n "$INPUT_WEB_APPURL" ]]; then
126-
if [[ ! "$INPUT_WEB_APPURL" =~ ^https:// ]]; then
127-
echo "❌ ERROR: WEB_APPURL must start with 'https://', got: '$INPUT_WEB_APPURL'"
128-
VALIDATION_FAILED=true
129-
else
130-
echo "✅ WEB_APPURL: '$INPUT_WEB_APPURL' is valid"
131-
fi
132-
fi
133-
134-
# Validate RESOURCE_GROUP_NAME (Azure resource group naming convention if provided)
135-
if [[ -n "$INPUT_RESOURCE_GROUP_NAME" ]]; then
136-
if [[ ! "$INPUT_RESOURCE_GROUP_NAME" =~ ^[a-zA-Z0-9._\(\)-]+$ ]] || [[ "$INPUT_RESOURCE_GROUP_NAME" =~ \.$ ]]; then
137-
echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' is invalid. Must contain only alphanumerics, periods, underscores, hyphens, and parentheses. Cannot end with period."
138-
VALIDATION_FAILED=true
139-
elif [[ ${#INPUT_RESOURCE_GROUP_NAME} -gt 90 ]]; then
140-
echo "❌ ERROR: RESOURCE_GROUP_NAME '$INPUT_RESOURCE_GROUP_NAME' exceeds 90 characters"
141-
VALIDATION_FAILED=true
142-
else
143-
echo "✅ RESOURCE_GROUP_NAME: '$INPUT_RESOURCE_GROUP_NAME' is valid"
144-
fi
145-
fi
146-
147-
# Validate QUOTA_FAILED (must be 'true', 'false', or empty string)
148-
if [[ "$INPUT_QUOTA_FAILED" != "true" && "$INPUT_QUOTA_FAILED" != "false" && "$INPUT_QUOTA_FAILED" != "" ]]; then
149-
echo "❌ ERROR: QUOTA_FAILED must be 'true', 'false', or empty string, got: '$INPUT_QUOTA_FAILED'"
150-
VALIDATION_FAILED=true
151-
else
152-
echo "✅ QUOTA_FAILED: '$INPUT_QUOTA_FAILED' is valid"
153-
fi
154-
155-
# Fail workflow if any validation failed
156-
if [[ "$VALIDATION_FAILED" == "true" ]]; then
157-
echo ""
158-
echo "❌ Parameter validation failed. Please correct the errors above and try again."
159-
exit 1
160-
fi
161-
162-
echo ""
163-
echo "✅ All input parameters validated successfully!"
69+
case "$CLEANUP_RESULT" in
70+
success) echo "CLEANUP_STATUS=✅ SUCCESS" >> $GITHUB_OUTPUT ;;
71+
failure) echo "CLEANUP_STATUS=❌ FAILED (Needs Manual Cleanup)" >> $GITHUB_OUTPUT ;;
72+
*) echo "CLEANUP_STATUS=⏭️ SKIPPED (Needs Manual Cleanup)" >> $GITHUB_OUTPUT ;;
73+
esac
74+
75+
- name: Determine Configuration Label
76+
id: config
77+
shell: bash
78+
env:
79+
WAF_ENABLED: ${{ env.WAF_ENABLED }}
80+
EXP: ${{ env.EXP }}
81+
run: |
82+
WAF_LABEL=$( [ "$WAF_ENABLED" = "true" ] && echo "WAF" || echo "Non-WAF" )
83+
EXP_LABEL=$( [ "$EXP" = "true" ] && echo "EXP" || echo "Non-EXP" )
84+
echo "CONFIG_LABEL=${WAF_LABEL} + ${EXP_LABEL}" >> $GITHUB_OUTPUT
16485
16586
- name: Send Quota Failure Notification
16687
if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED == 'true'
16788
shell: bash
16889
env:
169-
DEPLOY_RESULT: ${{ inputs.deploy_result }}
170-
QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
90+
GITHUB_REPOSITORY: ${{ github.repository }}
91+
GITHUB_RUN_ID: ${{ github.run_id }}
92+
ACCELERATOR_NAME: ${{ env.accelerator_name }}
93+
LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
94+
CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }}
95+
CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }}
17196
run: |
172-
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
97+
RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
17398
EMAIL_BODY=$(cat <<EOF
17499
{
175-
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${{ env.accelerator_name }} deployment has failed due to insufficient quota in the requested regions.</p><p><strong>Issue Details:</strong><br>• Quota check failed for GPT model<br>• Required GPT Capacity: ${{ env.GPT_MIN_CAPACITY }}<br>• Checked Regions: ${{ vars.AZURE_REGIONS }}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Please resolve the quota issue and retry the deployment.</p><p>Best regards,<br>Your Automation Team</p>",
176-
"subject": "${{ env.accelerator_name }} Pipeline - Failed (Insufficient Quota)"
100+
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed due to insufficient quota.</p><p><strong>Status Summary:</strong><br><table border='1' cellpadding='5' cellspacing='0'><tr><th>Stage</th><th>Status</th></tr><tr><td>Deployment</td><td>❌ FAILED (Insufficient Quota)</td></tr><tr><td>Cleanup</td><td>${CLEANUP_STATUS}</td></tr></table></p><p><strong>Configuration:</strong> ${CONFIG_LABEL}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Please resolve the quota issue and retry the deployment.</p><p>Best regards,<br>Your Automation Team</p>",
101+
"subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] Insufficient Quota"
177102
}
178103
EOF
179104
)
180105
181-
curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
106+
curl -X POST "${LOGICAPP_URL}" \
182107
-H "Content-Type: application/json" \
183108
-d "$EMAIL_BODY" || echo "Failed to send quota failure notification"
184109
185110
- name: Send Deployment Failure Notification
186111
if: inputs.deploy_result == 'failure' && inputs.QUOTA_FAILED != 'true'
187112
shell: bash
188113
env:
189-
DEPLOY_RESULT: ${{ inputs.deploy_result }}
190-
QUOTA_FAILED: ${{ inputs.QUOTA_FAILED }}
191-
RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
114+
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
115+
ACCELERATOR_NAME: ${{ env.accelerator_name }}
116+
LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
117+
CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }}
118+
CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }}
192119
run: |
193120
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
194-
RESOURCE_GROUP="${RESOURCE_GROUP_NAME}"
121+
RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
195122
196123
EMAIL_BODY=$(cat <<EOF
197124
{
198-
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${{ env.accelerator_name }} deployment process has encountered an issue and has failed to complete successfully.</p><p><strong>Deployment Details:</strong><br>• Resource Group: ${RESOURCE_GROUP}<br>• WAF Enabled: ${{ env.WAF_ENABLED }}<br>• EXP Enabled: ${{ env.EXP }}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Please investigate the deployment failure at your earliest convenience.</p><p>Best regards,<br>Your Automation Team</p>",
199-
"subject": "${{ env.accelerator_name }} Pipeline - Failed"
125+
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${ACCELERATOR_NAME} deployment has failed.</p><p><strong>Status Summary:</strong><br><table border='1' cellpadding='5' cellspacing='0'><tr><th>Stage</th><th>Status</th></tr><tr><td>Deployment</td><td>❌ FAILED (Deployment Issue)</td></tr><tr><td>Cleanup</td><td>${CLEANUP_STATUS}</td></tr></table></p><p><strong>Deployment Details:</strong><br>• Resource Group: ${RESOURCE_GROUP}</p><p><strong>Configuration:</strong> ${CONFIG_LABEL}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Please investigate the deployment failure at your earliest convenience.</p><p>Best regards,<br>Your Automation Team</p>",
126+
"subject": "❌[CI/CD-Automation] [${ACCELERATOR_NAME}] Deployment-Failed"
200127
}
201128
EOF
202129
)
203130
204-
curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
131+
curl -X POST "${LOGICAPP_URL}" \
205132
-H "Content-Type: application/json" \
206133
-d "$EMAIL_BODY" || echo "Failed to send deployment failure notification"
207134
208135
- name: Send Success Notification
209136
if: inputs.deploy_result == 'success'
210137
shell: bash
211138
env:
212-
DEPLOY_RESULT: ${{ inputs.deploy_result }}
213-
WEB_APPURL: ${{ inputs.WEB_APPURL }}
214-
EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
215-
RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
139+
INPUT_WEB_APPURL: ${{ inputs.WEB_APPURL }}
140+
INPUT_EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
141+
INPUT_RESOURCE_GROUP_NAME: ${{ inputs.RESOURCE_GROUP_NAME }}
142+
ACCELERATOR_NAME: ${{ env.accelerator_name }}
143+
LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}
144+
GITHUB_REPOSITORY: ${{ github.repository }}
145+
GITHUB_RUN_ID: ${{ github.run_id }}
146+
CONFIG_LABEL: ${{ steps.config.outputs.CONFIG_LABEL }}
147+
CLEANUP_STATUS: ${{ steps.cleanup.outputs.CLEANUP_STATUS }}
216148
run: |
217-
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
218-
WEBAPP_URL="${WEB_APPURL:-$EXISTING_WEBAPP_URL}"
219-
RESOURCE_GROUP="${RESOURCE_GROUP_NAME}"
149+
RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
150+
WEBAPP_URL="${INPUT_WEB_APPURL:-$INPUT_EXISTING_WEBAPP_URL}"
151+
RESOURCE_GROUP="$INPUT_RESOURCE_GROUP_NAME"
220152
221153
EMAIL_BODY=$(cat <<EOF
222154
{
223-
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${{ env.accelerator_name }} deployment has completed successfully.</p><p><strong>Deployment Details:</strong><br>• Resource Group: ${RESOURCE_GROUP}<br>• Web App URL: <a href='${WEBAPP_URL}'>${WEBAPP_URL}</a></p><p><strong>Configuration:</strong><br>• WAF Enabled: ${{ env.WAF_ENABLED }}<br>• EXP Enabled: ${{ env.EXP }}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Best regards,<br>Your Automation Team</p>",
224-
"subject": "${{ env.accelerator_name }} Pipeline - Deployment Success"
155+
"body": "<p>Dear Team,</p><p>We would like to inform you that the ${ACCELERATOR_NAME} deployment has completed successfully.</p><p><strong>Status Summary:</strong><br><table border='1' cellpadding='5' cellspacing='0'><tr><th>Stage</th><th>Status</th></tr><tr><td>Deployment</td><td>✅ SUCCESS</td></tr><tr><td>Cleanup</td><td>${CLEANUP_STATUS}</td></tr></table></p><p><strong>Deployment Details:</strong><br>• Resource Group: ${RESOURCE_GROUP}<br>• Web App URL: <a href='${WEBAPP_URL}'>${WEBAPP_URL}</a></p><p><strong>Configuration:</strong> ${CONFIG_LABEL}</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Best regards,<br>Your Automation Team</p>",
156+
"subject": "✅[CI/CD-Automation] [${ACCELERATOR_NAME}] Success"
225157
}
226158
EOF
227159
)
228160
229-
curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
161+
curl -X POST "${LOGICAPP_URL}" \
230162
-H "Content-Type: application/json" \
231163
-d "$EMAIL_BODY" || echo "Failed to send success notification"
232-
233-
- name: Send Existing URL Test Failure Notification
234-
if: inputs.deploy_result == 'skipped' && inputs.existing_webapp_url != ''
235-
shell: bash
236-
env:
237-
DEPLOY_RESULT: ${{ inputs.deploy_result }}
238-
EXISTING_WEBAPP_URL: ${{ inputs.existing_webapp_url }}
239-
run: |
240-
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
241-
EXISTING_URL="${EXISTING_WEBAPP_URL}"
242-
243-
EMAIL_BODY=$(cat <<EOF
244-
{
245-
"body": "<p>Dear Team,</p><p>The ${{ env.accelerator_name }} pipeline executed against the <strong>existing WebApp URL</strong>.</p><p><strong>Details:</strong><br>• Target URL: <a href='${EXISTING_URL}'>${EXISTING_URL}</a><br>• Deployment: Skipped</p><p><strong>Run URL:</strong> <a href='${RUN_URL}'>${RUN_URL}</a></p><p>Best regards,<br>Your Automation Team</p>",
246-
"subject": "${{ env.accelerator_name }} Pipeline - Existing URL Notification"
247-
}
248-
EOF
249-
)
250-
251-
curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
252-
-H "Content-Type: application/json" \
253-
-d "$EMAIL_BODY" || echo "Failed to send existing URL test failure notification"

docs/DeploymentGuide.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Ensure you have access to an [Azure subscription](https://azure.microsoft.com/fr
1616
|------------------------------|-----------|-------------|
1717
| **Contributor** | Subscription level | Create and manage Azure resources |
1818
| **User Access Administrator** | Subscription level | Manage user access and role assignments |
19-
| **Role Based Access Control** | Subscription/Resource Group level | Configure RBAC permissions |
19+
| **Role Based Access Control Admin** | Subscription/Resource Group level | Configure RBAC permissions |
2020
| **App Registration Creation** | Azure Active Directory | Create and configure authentication |
2121

2222
**🔍 How to Check Your Permissions:**
@@ -271,6 +271,11 @@ azd auth login --tenant-id <tenant-id>
271271
> ⚠️ **Critical: Redeployment Warning**
272272
> If you have previously run `azd up` in this folder (i.e., a `.azure` folder exists), you must [create a fresh environment](#creating-a-new-environment) to avoid conflicts and deployment failures.
273273
274+
**NOTE:** If you are running the latest azd version (version 1.23.9), please run the following command.
275+
```bash
276+
azd config set provision.preflight off
277+
```
278+
274279
```shell
275280
azd up
276281
```

0 commit comments

Comments
 (0)