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
51 changes: 41 additions & 10 deletions .github/workflows/agentics-maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,31 @@ on:
required: false
type: string
default: ''
workflow_call:
inputs:
operation:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The workflow_call trigger's operation input uses type: string since choice isn't supported for reusable workflows. Consider adding validation logic (e.g., a step that checks the input against valid operations) to surface helpful errors when an unsupported operation string is passed via workflow_call.

description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, clean_cache_memories, validate)'
required: false
type: string
default: ''
run_url:
description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
required: false
type: string
default: ''
outputs:
operation_completed:
description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)'
value: ${{ jobs.run_operation.outputs.operation || inputs.operation }}
applied_run_url:
description: 'The run URL that safe outputs were applied from'
value: ${{ jobs.apply_safe_outputs.outputs.run_url }}

permissions: {}

jobs:
close-expired-entities:
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }}
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
runs-on: ubuntu-slim
permissions:
discussions: write
Expand Down Expand Up @@ -109,7 +128,7 @@ jobs:
await main();

cleanup-cache-memory:
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '' || github.event.inputs.operation == 'clean_cache_memories') }}
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
runs-on: ubuntu-slim
permissions:
actions: write
Expand All @@ -136,12 +155,14 @@ jobs:
await main();

run_operation:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation != '' && github.event.inputs.operation != 'safe_outputs' && github.event.inputs.operation != 'create_labels' && github.event.inputs.operation != 'clean_cache_memories' && github.event.inputs.operation != 'validate' && (!(github.event.repository.fork)) }}
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
actions: write
contents: write
pull-requests: write
outputs:
operation: ${{ steps.record.outputs.operation }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down Expand Up @@ -176,7 +197,7 @@ jobs:
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_OPERATION: ${{ github.event.inputs.operation }}
GH_AW_OPERATION: ${{ inputs.operation }}
GH_AW_CMD_PREFIX: ./gh-aw
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -186,15 +207,21 @@ jobs:
const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs');
await main();

- name: Record outputs
id: record
run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT"

apply_safe_outputs:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }}
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
actions: read
contents: write
discussions: write
issues: write
pull-requests: write
outputs:
run_url: ${{ steps.record.outputs.run_url }}
steps:
- name: Checkout actions folder
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down Expand Up @@ -222,7 +249,7 @@ jobs:
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_RUN_URL: ${{ github.event.inputs.run_url }}
GH_AW_RUN_URL: ${{ inputs.run_url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
Expand All @@ -231,8 +258,12 @@ jobs:
const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs');
await main();

- name: Record outputs
id: record
run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT"

create_labels:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }}
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
contents: read
Expand Down Expand Up @@ -280,7 +311,7 @@ jobs:
await main();

validate_workflows:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'validate' && (!(github.event.repository.fork)) }}
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -328,7 +359,7 @@ jobs:
await main();

compile-workflows:
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }}
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
runs-on: ubuntu-slim
permissions:
contents: read
Expand Down Expand Up @@ -368,7 +399,7 @@ jobs:
await main();

secret-validation:
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }}
if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
runs-on: ubuntu-slim
permissions:
contents: read
Expand Down
96 changes: 70 additions & 26 deletions pkg/workflow/maintenance_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,25 @@ on:
required: false
type: string
default: ''
workflow_call:
inputs:
operation:
description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, clean_cache_memories, validate)'
required: false
type: string
default: ''
run_url:
description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
required: false
type: string
default: ''
outputs:
operation_completed:
description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)'
value: ${{ jobs.run_operation.outputs.operation || inputs.operation }}
applied_run_url:
description: 'The run URL that safe outputs were applied from'
value: ${{ jobs.apply_safe_outputs.outputs.run_url }}

permissions: {}

Expand Down Expand Up @@ -392,6 +411,8 @@ jobs:
actions: write
contents: write
pull-requests: write
outputs:
operation: ${{ steps.record.outputs.operation }}
steps:
- name: Checkout repository
uses: ` + GetActionPin("actions/checkout") + `
Expand Down Expand Up @@ -420,7 +441,7 @@ jobs:
uses: ` + GetActionPin("actions/github-script") + `
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_OPERATION: ${{ github.event.inputs.operation }}
GH_AW_OPERATION: ${{ inputs.operation }}
GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + `
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -429,6 +450,10 @@ jobs:
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs');
await main();

- name: Record outputs
id: record
run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT"
`)

// Add apply_safe_outputs job for workflow_dispatch with operation == 'safe_outputs'
Expand All @@ -442,6 +467,8 @@ jobs:
discussions: write
issues: write
pull-requests: write
outputs:
run_url: ${{ steps.record.outputs.run_url }}
steps:
- name: Checkout actions folder
uses: ` + GetActionPin("actions/checkout") + `
Expand Down Expand Up @@ -469,14 +496,18 @@ jobs:
uses: ` + GetActionPin("actions/github-script") + `
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_RUN_URL: ${{ github.event.inputs.run_url }}
GH_AW_RUN_URL: ${{ inputs.run_url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs');
await main();

- name: Record outputs
id: record
run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT"
`)

// Add create_labels job for workflow_dispatch with operation == 'create_labels'
Expand Down Expand Up @@ -698,57 +729,67 @@ func buildNotForkCondition() ConditionNode {
}
}

// buildNotDispatchOrEmptyOperation creates a condition that is true when the event
// is not a workflow_dispatch or the operation input is empty.
func buildNotDispatchOrEmptyOperation() ConditionNode {
// buildNotDispatchOrCallOrEmptyOperation creates a condition that is true when the event
// is not a workflow_dispatch or workflow_call, or the operation input is empty.
// Uses the `inputs.operation` context which works for both workflow_dispatch and workflow_call.
func buildNotDispatchOrCallOrEmptyOperation() ConditionNode {
return BuildOr(
BuildNotEquals(
BuildPropertyAccess("github.event_name"),
BuildStringLiteral("workflow_dispatch"),
BuildAnd(
BuildNotEquals(
BuildPropertyAccess("github.event_name"),
BuildStringLiteral("workflow_dispatch"),
),
BuildNotEquals(
BuildPropertyAccess("github.event_name"),
BuildStringLiteral("workflow_call"),
),
),
BuildEquals(
BuildPropertyAccess("github.event.inputs.operation"),
BuildPropertyAccess("inputs.operation"),
BuildStringLiteral(""),
),
)
}

// buildNotForkAndScheduledOrOperation creates a condition for jobs that run on
// schedule (or empty operation) AND when a specific operation is selected.
// Condition: !fork && (not_dispatch || operation == || operation == op)
// Condition: !fork && (not_dispatch_or_call || operation == \'\' || operation == op)
func buildNotForkAndScheduledOrOperation(operation string) ConditionNode {
return BuildAnd(
buildNotForkCondition(),
BuildOr(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice refactor of buildNotDispatchOrCallOrEmptyOperation — using BuildAnd with two BuildNotEquals conditions is cleaner than a nested OR. Could add a brief comment noting that this relies on operator precedence being correctly handled by the expression builder for clarity.

buildNotDispatchOrEmptyOperation(),
buildNotDispatchOrCallOrEmptyOperation(),
BuildEquals(
BuildPropertyAccess("github.event.inputs.operation"),
BuildPropertyAccess("inputs.operation"),
BuildStringLiteral(operation),
),
),
)
}

// buildNotForkAndScheduled creates a condition for jobs that should run on any
// non-dispatch event (e.g. schedule, push) or on workflow_dispatch with an empty
// operation, and never on forks.
// Condition: !fork && (event_name != 'workflow_dispatch' || operation == "")
// non-dispatch/call event (e.g. schedule, push) or on workflow_dispatch/workflow_call
// with an empty operation, and never on forks.
// Condition: !fork && ((event_name != \'workflow_dispatch\' && event_name != \'workflow_call\') || operation == \'\')
func buildNotForkAndScheduled() ConditionNode {
return BuildAnd(
buildNotForkCondition(),
buildNotDispatchOrEmptyOperation(),
buildNotDispatchOrCallOrEmptyOperation(),
)
}

// buildDispatchOperationCondition creates a condition for jobs that should run
// only when a specific workflow_dispatch operation is selected and not a fork.
// Condition: dispatch && operation == op && !fork
// only when a specific workflow_dispatch or workflow_call operation is selected and not a fork.
// Condition: (dispatch || call) && operation == op && !fork
func buildDispatchOperationCondition(operation string) ConditionNode {
return BuildAnd(
BuildAnd(
BuildEventTypeEquals("workflow_dispatch"),
BuildOr(
BuildEventTypeEquals("workflow_dispatch"),
BuildEventTypeEquals("workflow_call"),
),
BuildEquals(
BuildPropertyAccess("github.event.inputs.operation"),
BuildPropertyAccess("inputs.operation"),
BuildStringLiteral(operation),
),
),
Expand All @@ -757,14 +798,17 @@ func buildDispatchOperationCondition(operation string) ConditionNode {
}

// buildRunOperationCondition creates the condition for the unified run_operation
// job that handles all dispatch operations except the ones with dedicated jobs.
// Condition: dispatch && operation != && operation != each excluded && !fork.
// job that handles all dispatch/call operations except the ones with dedicated jobs.
// Condition: (dispatch || call) && operation != \'\' && operation != each excluded && !fork.
func buildRunOperationCondition(excludedOperations ...string) ConditionNode {
// Start with: event is workflow_dispatch AND operation is not empty
// Start with: event is workflow_dispatch or workflow_call AND operation is not empty
condition := BuildAnd(
BuildEventTypeEquals("workflow_dispatch"),
BuildOr(
BuildEventTypeEquals("workflow_dispatch"),
BuildEventTypeEquals("workflow_call"),
),
BuildNotEquals(
BuildPropertyAccess("github.event.inputs.operation"),
BuildPropertyAccess("inputs.operation"),
BuildStringLiteral(""),
),
)
Expand All @@ -774,7 +818,7 @@ func buildRunOperationCondition(excludedOperations ...string) ConditionNode {
condition = BuildAnd(
condition,
BuildNotEquals(
BuildPropertyAccess("github.event.inputs.operation"),
BuildPropertyAccess("inputs.operation"),
BuildStringLiteral(op),
),
)
Expand Down
Loading