Skip to content

ci: add some github actions#484

Draft
huimiu wants to merge 40 commits intomainfrom
hui/sync-sample
Draft

ci: add some github actions#484
huimiu wants to merge 40 commits intomainfrom
hui/sync-sample

Conversation

@huimiu
Copy link
Copy Markdown
Member

@huimiu huimiu commented Apr 3, 2026

No description provided.

huimiu added 30 commits March 6, 2026 20:13
Comment on lines +13 to +110
name: "[${{ matrix.branch }}] ${{ matrix.language }}/${{ matrix.type }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false # Run all matrix jobs even if one fails
matrix:
branch: [dev, main, pre-release, stable]
language: [dotnet, python]
type: [agent, workflow]

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}

- name: Prepare sample directory
id: prepare
env:
SAFE_PROJECT_NAME: SampleAgent
AGENT_NAME: sample-agent
run: |
WORK_DIR=$(mktemp -d)
cp -r "samples/hosted-agent/${{ matrix.language }}/${{ matrix.type }}/." "$WORK_DIR/"

# Rename any files whose names contain the SafeProjectName placeholder
find "$WORK_DIR" -name '*{{SafeProjectName}}*' | while read -r f; do
mv "$f" "$(echo "$f" | sed "s/{{SafeProjectName}}/$SAFE_PROJECT_NAME/g")"
done

# Replace placeholder values inside all text files
find "$WORK_DIR" -type f -print0 \
| xargs -0 sed -i \
-e "s/{{SafeProjectName}}/$SAFE_PROJECT_NAME/g" \
-e "s/{{AgentName}}/$AGENT_NAME/g"

echo "work_dir=$WORK_DIR" >> "$GITHUB_OUTPUT"

- name: Build Docker image
run: |
docker build "${{ steps.prepare.outputs.work_dir }}" \
-t "sample-${{ matrix.language }}-${{ matrix.type }}:ci-test"

- name: Run container smoke test
run: |
CONTAINER="test-${{ matrix.language }}-${{ matrix.type }}"

docker run -d \
--name "$CONTAINER" \
-p 8088:8088 \
-e AZURE_AI_PROJECT_ENDPOINT="https://fake.services.ai.azure.com" \
-e PROJECT_ENDPOINT="https://fake.services.ai.azure.com" \
-e MODEL_DEPLOYMENT_NAME="gpt-4o" \
"sample-${{ matrix.language }}-${{ matrix.type }}:ci-test"

# Poll port 8088 for up to 30 seconds
echo "Waiting for server to start..."
for i in $(seq 1 10); do
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 2 http://localhost:8088/ || echo "000")
if [ "$http_code" != "000" ]; then
echo "✅ Server responded with HTTP $http_code — container is healthy"
break
fi
if [ "$i" -eq 10 ]; then
echo "❌ Server did not start within 30 seconds"
docker logs "$CONTAINER"
docker rm -f "$CONTAINER" || true
exit 1
fi
echo " attempt $i/10 — not yet ready, retrying in 3s..."
sleep 3
done

# Confirm the container is still running (didn't crash after responding)
running=$(docker inspect -f '{{.State.Running}}' "$CONTAINER")
if [ "$running" != "true" ]; then
echo "❌ Container exited unexpectedly"
docker logs "$CONTAINER"
docker rm -f "$CONTAINER" || true
exit 1
fi

docker rm -f "$CONTAINER"

- name: Save result artifact
if: always()
run: |
mkdir -p /tmp/ci-result
echo "${{ job.status }}" > /tmp/ci-result/status.txt
echo "${{ matrix.branch }}/${{ matrix.language }}/${{ matrix.type }}" > /tmp/ci-result/sample.txt

- name: Upload result artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: result-${{ matrix.branch }}-${{ matrix.language }}-${{ matrix.type }}
path: /tmp/ci-result/

notify:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 15 hours ago

In general, fix this by explicitly declaring permissions for the workflow or for each job, and restrict them to the minimum required. Since neither job needs to write to the repository or modify issues/PRs, we can safely set contents: read at the workflow level so it applies to all jobs, satisfying least-privilege while keeping behavior unchanged.

The single best fix here is to add a root-level permissions section just under the name (or just under on:) in .github/workflows/validate-hosted-agent-samples.yml:

  • Set permissions: contents: read to allow actions/checkout and general read access to the repo.
  • There is no indication that the jobs need any additional scoped write permissions (no PR/issue APIs, no environments, no packages APIs), so we keep the set minimal.

No imports or code changes beyond this YAML addition are needed. The rest of the workflow logic (building Docker images, running containers, uploading/downloading artifacts, and sending notifications via secrets) is unaffected by the GITHUB_TOKEN permission scope.

Suggested changeset 1
.github/workflows/validate-hosted-agent-samples.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/validate-hosted-agent-samples.yml b/.github/workflows/validate-hosted-agent-samples.yml
--- a/.github/workflows/validate-hosted-agent-samples.yml
+++ b/.github/workflows/validate-hosted-agent-samples.yml
@@ -1,5 +1,8 @@
 name: Validate Hosted Agent Samples
 
+permissions:
+  contents: read
+
 on:
   schedule:
     - cron: "0 1 * * *" # Daily at 9:00 AM Beijing time (UTC+8 → 01:00 UTC)
EOF
@@ -1,5 +1,8 @@
name: Validate Hosted Agent Samples

permissions:
contents: read

on:
schedule:
- cron: "0 1 * * *" # Daily at 9:00 AM Beijing time (UTC+8 → 01:00 UTC)
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +111 to +276
name: Send notification
needs: [validate]
if: always()
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Download all results
uses: actions/download-artifact@v4
with:
pattern: result-*
path: results/

- name: Compute summary and render email
id: email
env:
EMAIL_WORKFLOW: "${{ github.workflow }}"
EMAIL_BRANCH: "${{ github.ref_name }}"
EMAIL_COMMIT: "${{ github.sha }}"
EMAIL_TRIGGERED_BY: "${{ github.event_name }}"
EMAIL_ACTOR: "${{ github.actor }}"
EMAIL_RUN_NUMBER: "${{ github.run_number }}"
EMAIL_RUN_ID: "${{ github.run_id }}"
EMAIL_RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
EMAIL_REPOSITORY: "${{ github.repository }}"
EMAIL_SERVER_URL: "${{ github.server_url }}"
run: |
total=0
passed=0

# Collect results into a temp file: "sample|status"
tmpdata=$(mktemp)
for dir in results/result-*/; do
status=$(cat "$dir/status.txt" 2>/dev/null || echo "unknown")
sample=$(cat "$dir/sample.txt" 2>/dev/null || echo "unknown")
total=$((total + 1))
[ "$status" = "success" ] && passed=$((passed + 1))
printf '%s|%s\n' "$sample" "$status" >> "$tmpdata"
done

# Sort by branch (alphabetical on full "branch/language/type" line)
tmpsorted=$(mktemp)
sort "$tmpdata" > "$tmpsorted"
rm -f "$tmpdata"

# Count rows per branch
declare -A branch_count=()
while IFS='|' read -r sample _; do
b="${sample%%/*}"
branch_count["$b"]=$((${branch_count["$b"]:-0} + 1))
done < "$tmpsorted"

# Build rows with rowspan on the Branch column
rows=""
prev_branch=""
cell_base="padding:8px 12px;border-bottom:1px solid #eeeeee;font-size:13px;color:#111111;"
while IFS='|' read -r sample status; do
branch="${sample%%/*}"
rest="${sample#*/}"
if [ "$status" = "success" ]; then
icon="&#x2705;"
bg=""
else
icon="&#x274C;"
bg="background-color:#fff8f8;"
fi
if [ "$branch" != "$prev_branch" ]; then
count=${branch_count["$branch"]}
branch_cell="<td rowspan=\"$count\" style=\"${cell_base}border-right:2px solid #eeeeee;vertical-align:middle;text-align:center;\"><code style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;font-size:12px;\">$branch</code></td>"
prev_branch="$branch"
else
branch_cell=""
fi
rows="${rows}<tr>${branch_cell}<td style=\"${cell_base}${bg}\"><code style=\"background:#f5f5f5;padding:1px 6px;border-radius:4px;font-size:12px;\">$rest</code></td><td style=\"${cell_base}${bg}white-space:nowrap;\">$icon $status</td></tr>"
done < "$tmpsorted"
rm -f "$tmpsorted"
failed=$((total - passed))
if [ "$failed" -eq 0 ]; then
overall="PASSED"
header_color="#137333"
else
overall="FAILED"
header_color="#d93025"
fi
export EMAIL_OVERALL="$overall"
export EMAIL_PASSED="$passed"
export EMAIL_TOTAL="$total"
export EMAIL_ROWS="$rows"
export EMAIL_HEADER_COLOR="$header_color"
body=$(envsubst \
'${EMAIL_OVERALL} ${EMAIL_PASSED} ${EMAIL_TOTAL} ${EMAIL_ROWS} ${EMAIL_HEADER_COLOR}
${EMAIL_WORKFLOW} ${EMAIL_BRANCH} ${EMAIL_COMMIT}
${EMAIL_TRIGGERED_BY} ${EMAIL_ACTOR} ${EMAIL_RUN_NUMBER} ${EMAIL_RUN_ID}
${EMAIL_RUN_URL} ${EMAIL_REPOSITORY} ${EMAIL_SERVER_URL}' \
< .github/email-templates/validate-samples-failure.html)
body=$(printf '%s' "$body" | tr -d '\n' | sed 's/>[[:space:]]\+</></g')
echo "overall=$overall" >> "$GITHUB_OUTPUT"
echo "passed=$passed" >> "$GITHUB_OUTPUT"
echo "total=$total" >> "$GITHUB_OUTPUT"
echo "body<<DELIM" >> "$GITHUB_OUTPUT"
echo "$body" >> "$GITHUB_OUTPUT"
echo "DELIM" >> "$GITHUB_OUTPUT"

- name: Send notification
env:
TO: ${{ secrets.MAIL_TO }}
SUBJECT: "[${{ steps.email.outputs.overall }}] Validate Hosted Agent Samples (${{ steps.email.outputs.passed }}/${{ steps.email.outputs.total }} Passed)"
BODY: ${{ steps.email.outputs.body }}
LOGIC_APP_URL: ${{ secrets.LOGIC_APP_URL }}
MAIL_CLIENT_ID: ${{ secrets.MAIL_CLIENT_ID }}
MAIL_CLIENT_SECRET: ${{ secrets.MAIL_CLIENT_SECRET }}
MAIL_TENANT_ID: ${{ secrets.MAIL_TENANT_ID }}
run: |
if [ -z "$TO" ] || [ -z "$LOGIC_APP_URL" ]; then
echo "⚠️ MAIL_TO or LOGIC_APP_URL not configured. Skipping email send."
exit 0
fi

auth_header=""
if [ -n "$MAIL_CLIENT_ID" ] && [ -n "$MAIL_CLIENT_SECRET" ] && [ -n "$MAIL_TENANT_ID" ]; then
echo "🔐 Getting Azure AD access token..."
response=$(curl -s \
--request POST \
--header "Content-Type: application/x-www-form-urlencoded" \
--data "grant_type=client_credentials&client_id=${MAIL_CLIENT_ID}&client_secret=${MAIL_CLIENT_SECRET}&resource=https://management.core.windows.net" \
"https://login.microsoftonline.com/${MAIL_TENANT_ID}/oauth2/token")
access_token=$(echo "$response" | jq -r '. | select(.access_token) | .access_token')
if [ -z "$access_token" ] || [ "$access_token" = "null" ]; then
echo "⚠️ Failed to get access token. Skipping email send."
exit 0
fi
echo "✅ Got access token"
auth_header="Authorization: Bearer $access_token"
fi

body_file=$(mktemp)
printf '%s' "$BODY" > "$body_file"
payload=$(jq -n \
--arg to "$TO" \
--arg subject "$SUBJECT" \
--rawfile body "$body_file" \
'{to: $to, subject: $subject, body: $body, bodyHtml: $body, contentType: "text/html"}')
rm -f "$body_file"

if [ -n "$auth_header" ]; then
http_code=$(curl -s -o /tmp/email_response.txt -w "%{http_code}" \
--request POST \
--header "Content-Type: application/json" \
--header "$auth_header" \
--data "$payload" \
"$LOGIC_APP_URL")
else
http_code=$(curl -s -o /tmp/email_response.txt -w "%{http_code}" \
--request POST \
--header "Content-Type: application/json" \
--data "$payload" \
"$LOGIC_APP_URL")
fi

if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "✅ Email sent successfully! (HTTP $http_code)"
else
echo "⚠️ Email send failed with HTTP $http_code"
cat /tmp/email_response.txt 2>/dev/null || true
fi

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 15 hours ago

To fix the problem, explicitly set minimal GITHUB_TOKEN permissions in the workflow. The simplest and least-privilege configuration is to add a root-level permissions block with contents: read, which applies to all jobs unless they override it. Neither validate nor notify performs any GitHub write operations (no pushes, issue/PR updates, or artifact uploads that require elevated token scopes beyond the defaults), so contents: read is sufficient.

The best fix without changing functionality is:

  • At the top level of .github/workflows/validate-hosted-agent-samples.yml, add:
    permissions:
      contents: read
    between the on: block and the jobs: block.
  • This constrains the GITHUB_TOKEN for both validate and notify jobs to read-only repository contents. All other actions (Docker builds, curl calls, jq, emailing via Logic App, artifacts upload/download) continue to work because they either don’t use GITHUB_TOKEN or rely only on read access to repo contents.

No additional methods, imports, or definitions are needed; this is purely a YAML configuration change.

Suggested changeset 1
.github/workflows/validate-hosted-agent-samples.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/validate-hosted-agent-samples.yml b/.github/workflows/validate-hosted-agent-samples.yml
--- a/.github/workflows/validate-hosted-agent-samples.yml
+++ b/.github/workflows/validate-hosted-agent-samples.yml
@@ -8,6 +8,9 @@
       - hui/validation-ci
   workflow_dispatch: # Allow manual trigger
 
+permissions:
+  contents: read
+
 jobs:
   validate:
     name: "[${{ matrix.branch }}] ${{ matrix.language }}/${{ matrix.type }}"
EOF
@@ -8,6 +8,9 @@
- hui/validation-ci
workflow_dispatch: # Allow manual trigger

permissions:
contents: read

jobs:
validate:
name: "[${{ matrix.branch }}] ${{ matrix.language }}/${{ matrix.type }}"
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants