Skip to content
Open
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
108 changes: 108 additions & 0 deletions .github/workflows/issue-kind-label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Apply kind/area labels and milestone from issue body

on:
issues:
types: [opened, edited]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:
types: [opened, edited] omits reopened, which pr-kind-label.yaml includes for the same trigger. If reopening an issue can leave a stale label state, add reopened for parity; otherwise fine as is.


jobs:
kind-labels:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Sync kind labels
env:
GH_TOKEN: ${{ github.token }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Extract /kind values from issue body. Absence of a directive is a no-op
# to avoid wiping kind/* labels set by maintainers through the UI.
kinds=$(printf '%s' "$ISSUE_BODY" | tr -d '\r' | sed -n 's|^/kind[[:space:]][[:space:]]*||p' | sed 's|[[:space:]]*$||' | sed '/^$/d' | sort -u)
[ -z "$kinds" ] && exit 0

# Validate every requested label before mutating. Removing first and
# failing to add a mistyped label would leave the issue with no kind/*.
existing=$(gh label list --repo "$REPO" --limit 200 --json name -q '.[].name')
unknown=$(printf '%s\n' "$kinds" | while IFS= read -r kind; do
printf '%s\n' "$existing" | grep -qxF "kind/$kind" || printf "kind/%s " "$kind"
done)
if [ -n "$unknown" ]; then
echo "::warning::Unknown label(s) $(esc "${unknown% }"), skipping kind sync"
exit 0
fi

# Remove existing kind/* labels
gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json labels \
-q '.labels[].name | select(startswith("kind/"))' | \
while IFS= read -r label; do
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$(esc "$label")'"
done

printf '%s\n' "$kinds" | \
while IFS= read -r kind; do
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label "kind/$kind" || \
echo "::warning::Label 'kind/$(esc "$kind")' not found in repository"
done

- name: Set area labels
env:
GH_TOKEN: ${{ github.token }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Absence of a directive is a no-op. Area labels are also set by
# maintainers through the UI and clearing here would wipe those.
areas=$(printf '%s' "$ISSUE_BODY" | tr -d '\r' | sed -n 's|^/area[[:space:]][[:space:]]*||p' | \
sed 's|[[:space:]]*$||' | sed '/^$/d' | sort -u)
[ -z "$areas" ] && exit 0

existing=$(gh label list --repo "$REPO" --limit 200 --json name -q '.[].name')
unknown=$(printf '%s\n' "$areas" | while IFS= read -r area; do
printf '%s\n' "$existing" | grep -qxF "area/$area" || printf "area/%s " "$area"
done)
if [ -n "$unknown" ]; then
echo "::warning::Unknown label(s) $(esc "${unknown% }"), skipping area sync"
exit 0
fi

# Remove existing area/* labels
gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json labels \
-q '.labels[].name | select(startswith("area/"))' | \
while IFS= read -r label; do
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$(esc "$label")'"
done

printf '%s\n' "$areas" | \
while IFS= read -r area; do
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label "area/$area" || \
echo "::warning::Label 'area/$(esc "$area")' not found in repository"
done

- name: Set milestone
env:
GH_TOKEN: ${{ github.token }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Use the last /milestone directive in the issue body
milestone=$(printf '%s' "$ISSUE_BODY" | tr -d '\r' | sed -n 's|^/milestone[[:space:]][[:space:]]*||p' | \
sed 's|[[:space:]]*$||' | sed '/^$/d' | tail -n 1)
[ -z "$milestone" ] && exit 0
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --milestone "$milestone" || \
echo "::warning::Milestone '$(esc "$milestone")' not found in repository"
52 changes: 44 additions & 8 deletions .github/workflows/pr-kind-label.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,38 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Extract /kind values from PR body. Absence of a directive is a no-op
# to avoid wiping kind/* labels set by maintainers through the UI.
kinds=$(printf '%s' "$PR_BODY" | tr -d '\r' | sed -n 's|^/kind[[:space:]][[:space:]]*||p' | \
sed 's|[[:space:]]*$||' | sed '/^$/d' | sort -u)
[ -z "$kinds" ] && exit 0

# Validate every requested label before mutating. Removing first and
# failing to add a mistyped label would leave the PR with no kind/*.
existing=$(gh label list --repo "$REPO" --limit 200 --json name -q '.[].name')
unknown=$(printf '%s\n' "$kinds" | while IFS= read -r kind; do
printf '%s\n' "$existing" | grep -qxF "kind/$kind" || printf "kind/%s " "$kind"
done)
if [ -n "$unknown" ]; then
echo "::warning::Unknown label(s) $(esc "${unknown% }"), skipping kind sync"
exit 0
fi

# Remove existing kind/* labels
gh pr view "$PR_NUMBER" --repo "$REPO" --json labels \
-q '.labels[].name | select(startswith("kind/"))' | \
while IFS= read -r label; do
gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$label'"
gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$(esc "$label")'"
done

# Extract /kind values from PR body and apply labels
printf '%s' "$PR_BODY" | tr -d '\r' | sed -n 's|^/kind[[:space:]][[:space:]]*||p' | sed 's|[[:space:]]*$||' | sort -u | \
printf '%s\n' "$kinds" | \
while IFS= read -r kind; do
[ -z "$kind" ] && continue
gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "kind/$kind" || \
echo "::warning::Label 'kind/$kind' not found in repository"
echo "::warning::Label 'kind/$(esc "$kind")' not found in repository"
done

- name: Set area labels
Expand All @@ -41,23 +60,36 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Absence of a directive is a no-op. Area labels are also set by the
# /area comment command and clearing here would wipe those.
areas=$(printf '%s' "$PR_BODY" | tr -d '\r' | sed -n 's|^/area[[:space:]][[:space:]]*||p' | \
sed 's|[[:space:]]*$||' | sed '/^$/d' | sort -u)
[ -z "$areas" ] && exit 0

existing=$(gh label list --repo "$REPO" --limit 200 --json name -q '.[].name')
unknown=$(printf '%s\n' "$areas" | while IFS= read -r area; do
printf '%s\n' "$existing" | grep -qxF "area/$area" || printf "area/%s " "$area"
done)
if [ -n "$unknown" ]; then
echo "::warning::Unknown label(s) $(esc "${unknown% }"), skipping area sync"
exit 0
fi

# Remove existing area/* labels
gh pr view "$PR_NUMBER" --repo "$REPO" --json labels \
-q '.labels[].name | select(startswith("area/"))' | \
while IFS= read -r label; do
gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$label'"
gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || echo "::warning::Failed to remove label '$(esc "$label")'"
done

printf '%s\n' "$areas" | \
while IFS= read -r area; do
gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "area/$area" || \
echo "::warning::Label 'area/$area' not found in repository"
echo "::warning::Label 'area/$(esc "$area")' not found in repository"
done

- name: Set milestone
Expand All @@ -67,9 +99,13 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
# Escape percent sequences before echoing body derived text into a
# ::warning:: so it cannot forge additional workflow commands.
esc() { local s=$1; s=${s//'%'/%25}; s=${s//$'\r'/%0D}; s=${s//$'\n'/%0A}; printf '%s' "$s"; }

# Use the last /milestone directive in the PR body
milestone=$(printf '%s' "$PR_BODY" | tr -d '\r' | sed -n 's|^/milestone[[:space:]][[:space:]]*||p' | \
sed 's|[[:space:]]*$||' | sed '/^$/d' | tail -n 1)
[ -z "$milestone" ] && exit 0
gh pr edit "$PR_NUMBER" --repo "$REPO" --milestone "$milestone" || \
echo "::warning::Milestone '$milestone' not found in repository"
echo "::warning::Milestone '$(esc "$milestone")' not found in repository"
Loading