From fe69fe4d6d1ab88da219149a23c7c4fa9b3eea0d Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Thu, 23 Oct 2025 10:21:29 +0200 Subject: [PATCH 01/15] feat: Add composite action and reusable workflow for number summary --- ci/print_summary.py | 56 ++++++ labs/composite-action.md | 95 ++++++++++ labs/reusable-2.md | 367 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+) create mode 100644 ci/print_summary.py create mode 100644 labs/composite-action.md create mode 100644 labs/reusable-2.md diff --git a/ci/print_summary.py b/ci/print_summary.py new file mode 100644 index 00000000..df7d97cc --- /dev/null +++ b/ci/print_summary.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +""" +Simple helper used by the composite action exercise. +Reads JSON from stdin (or a file path passed as arg) containing a list of numbers +and prints a small summary: count, sum, average, min, max. +""" +import sys +import json +from typing import List + + +def summarize(numbers: List[float]) -> dict: + if not numbers: + return {"count": 0, "sum": 0, "avg": None, "min": None, "max": None} + total = sum(numbers) + return { + "count": len(numbers), + "sum": total, + "avg": total / len(numbers), + "min": min(numbers), + "max": max(numbers), + } + + +def main(): + data = None + if len(sys.argv) > 1: + try: + with open(sys.argv[1], "r", encoding="utf-8") as f: + data = json.load(f) + except Exception as e: + print(f"Failed to read {sys.argv[1]}: {e}", file=sys.stderr) + sys.exit(2) + else: + try: + data = json.load(sys.stdin) + except Exception: + print("No JSON input provided on stdin and no file given", file=sys.stderr) + sys.exit(1) + + if not isinstance(data, list): + print("Expected a JSON array of numbers", file=sys.stderr) + sys.exit(3) + + try: + numbers = [float(x) for x in data] + except Exception: + print("All items in the array must be numeric", file=sys.stderr) + sys.exit(4) + + out = summarize(numbers) + print(json.dumps(out, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/labs/composite-action.md b/labs/composite-action.md new file mode 100644 index 00000000..60a96e70 --- /dev/null +++ b/labs/composite-action.md @@ -0,0 +1,95 @@ +# Composite actions + +In this hands-on lab you'll create a small composite action that runs a few shell steps plus the Python helper located at `ci/print_summary.py`. + + +The goal is to learn how to: create a composite action, accept inputs, expose outputs, and call it from a workflow. + +This lab contains the following steps: + +- Create the composite action metadata +- Use the composite action in a simple workflow +- Run the workflow locally (or on GitHub Actions) and inspect the output + +## Create the composite action + +1. Create a new directory `.github/actions/summary-action` in the repository. +2. Add an `action.yml` file with the following behaviour: + - Accept an input `numbers` which is a JSON array encoded as a string (for simplicity) + - Pipe that string to the Python helper `ci/print_summary.py` via stdin + - Save the printed JSON summary to an output parameter called `summary` + + For more information on action metadata format and the composite action syntax, see the GitHub Docs: https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-an-action-metadata-file + +
+ Solution + +```yaml +name: Summary action +description: "Pipe numbers into a Python helper and print a small summary" +inputs: + numbers: + description: 'JSON array of numbers as a string, e.g. "[1,2,3]"' + required: true +outputs: + summary: + description: 'JSON summary produced by the helper' +runs: + using: "composite" + steps: + - name: Run python summarizer (stdin) + shell: bash + run: | + echo "${{ inputs.numbers }}" | python3 ci/print_summary.py > summary.json + cat summary.json + + - name: Set output + shell: bash + run: echo "summary=$(cat summary.json)" >> $GITHUB_OUTPUT +``` +
+ +Notes: + +- The composite action uses the `composite` runner so it can string together multiple steps. +- We store the temporary file path in an intermediate output (optional). The important part is that the final step writes the `summary` to `$GITHUB_OUTPUT` so the action exposes the output. + +## Use the composite action + +1. Create a workflow `.github/workflows/use-summary.yml` with a manual trigger (`workflow_dispatch`). +2. Add a job that calls the action with a `numbers` value. + +Example consumer (solution): + +
+ Solution + +```yaml +name: Use summary action + +on: [workflow_dispatch] + +jobs: + call-summary: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: summary + uses: ./.github/actions/summary-action + with: + numbers: '[10, 20, 30, 40]' + + - name: Print action output + run: echo "Summary output: ${{ steps.summary.outputs.summary }}" +``` +
+ +## Run the workflow + +- You can dispatch the workflow from the GitHub UI (Workflow -> Run workflow) or run the steps locally using a runner that supports Actions (for example, act). The important part is that the workflow demonstrates how inputs and outputs flow through the composite action. + +## Summary + +You've created a composite action that runs multiple steps and produces an output. You also learned how to call that composite action from a workflow and read its outputs. diff --git a/labs/reusable-2.md b/labs/reusable-2.md new file mode 100644 index 00000000..9516d152 --- /dev/null +++ b/labs/reusable-2.md @@ -0,0 +1,367 @@ +# Reusable workflow that uses a composite action + +This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. + +Follow these steps: + +## Create the reusable workflow + +1. Create `.github/workflows/reusable-2.yml`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +3. The workflow should run a job `summarize` which: + - checks out the repository + - calls the composite action `./.github/actions/summary-action` with the provided input + - publishes the composite action's `summary` as a job output +4. The workflow should expose an output `summary` which maps to the job output. + +
+ Solution: reusable-2.yml + +```yaml +name: Reusable summary workflow + +on: + workflow_call: + inputs: + numbers: + description: 'JSON array of numbers as a string' + type: string + required: true + default: '[1,2,3]' + outputs: + summary: + description: 'Summary produced by the composite action' + value: ${{ jobs.summarize.outputs.summary }} + +jobs: + summarize: + runs-on: ubuntu-latest + outputs: + summary: ${{ steps.call-summary.outputs.summary }} + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: call-summary + uses: ./.github/actions/summary-action + with: + numbers: ${{ inputs.numbers }} +``` +
+ +## Create a consumer workflow + +1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. +2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. +3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. + +
+ Solution: use-reusable-2.yml (consumer) + +```yaml +name: Use reusable summary + +on: [workflow_dispatch] + +jobs: + call-reusable: + uses: ./.github/workflows/reusable-2.yml + with: + numbers: '[5, 15, 25, 35, 45]' + + show-summary: + runs-on: ubuntu-latest + needs: [call-reusable] + steps: + - name: Print reusable workflow output + run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" +``` +
+ +## Try it + +- Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). +- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. +- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. + +## Notes and tips + +- The composite action uses stdin in the example to keep wiring trivial. +- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. +# Reusable workflow that uses a composite action + +This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. + +Follow these steps: + +## Create the reusable workflow + +1. Create `.github/workflows/reusable-2.yml`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +3. The workflow should run a job `summarize` which: + - checks out the repository + - calls the composite action `./.github/actions/summary-action` with the provided input + - publishes the composite action's `summary` as a job output +4. The workflow should expose an output `summary` which maps to the job output. + +
+ Solution: reusable-2.yml + +```yaml +name: Reusable summary workflow + +on: + workflow_call: + inputs: + numbers: + description: 'JSON array of numbers as a string' + type: string + required: true + default: '[1,2,3]' + outputs: + summary: + description: 'Summary produced by the composite action' + value: ${{ jobs.summarize.outputs.summary }} + +jobs: + summarize: + runs-on: ubuntu-latest + outputs: + summary: ${{ steps.call-summary.outputs.summary }} + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: call-summary + uses: ./.github/actions/summary-action + with: + numbers: ${{ inputs.numbers }} +``` +
+ +## Create a consumer workflow + +1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. +2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. +3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. + +
+ Solution: use-reusable-2.yml (consumer) + +```yaml +name: Use reusable summary + +on: [workflow_dispatch] + +jobs: + call-reusable: + uses: ./.github/workflows/reusable-2.yml + with: + numbers: '[5, 15, 25, 35, 45]' + + show-summary: + runs-on: ubuntu-latest + needs: [call-reusable] + steps: + - name: Print reusable workflow output + run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" +``` +
+ +## Try it + +- Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). +- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. +- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. + +## Notes and tips + +- The composite action uses stdin in the example to keep wiring trivial. +- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. +# Reusable workflow that uses a composite action + +This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. + +Follow these steps: + +## Create the reusable workflow + +1. Create `.github/workflows/reusable-2.yml`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +3. The workflow should run a job `summarize` which: + - checks out the repository + - calls the composite action `./.github/actions/summary-action` with the provided input + - publishes the composite action's `summary` as a job output +4. The workflow should expose an output `summary` which maps to the job output. + +
+ Solution: reusable-2.yml + +```yaml +name: Reusable summary workflow + +on: + workflow_call: + inputs: + numbers: + description: 'JSON array of numbers as a string' + type: string + required: true + default: '[1,2,3]' + outputs: + summary: + description: 'Summary produced by the composite action' + value: ${{ jobs.summarize.outputs.summary }} + +jobs: + summarize: + runs-on: ubuntu-latest + outputs: + summary: ${{ steps.call-summary.outputs.summary }} + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: call-summary + uses: ./.github/actions/summary-action + with: + numbers: ${{ inputs.numbers }} +``` +
+ +## Create a consumer workflow + +1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. +2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. +3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. + +
+ Solution: use-reusable-2.yml (consumer) + +```yaml +name: Use reusable summary + +on: [workflow_dispatch] + +jobs: + call-reusable: + uses: ./.github/workflows/reusable-2.yml + with: + numbers: '[5, 15, 25, 35, 45]' + + show-summary: + runs-on: ubuntu-latest + needs: [call-reusable] + steps: + - name: Print reusable workflow output + run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" +``` +
+ +## Try it + +- Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). +- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. +- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. + +## Notes and tips + +- The composite action is simple and purposely uses stdin in the example to keep wiring trivial. +- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. +# Reusable workflow that uses a composite action + +This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. + +Follow these steps: + +## Create the reusable workflow + +1. Create `.github/workflows/reusable-2.yml`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +3. The workflow should run a job `summarize` which: + - checks out the repository + - calls the composite action `./.github/actions/summary-action` with the provided input + - publishes the composite action's `summary` as a job output +4. The workflow should expose an output `summary` which maps to the job output. + + description: 'Summary produced by the composite action' + uses: ./.github/actions/summary-action +## Create the reusable workflow + +1. Create `.github/workflows/reusable-2.yml`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +3. The workflow should run a job `summarize` which: + - checks out the repository + - calls the composite action `./.github/actions/summary-action` with the provided input + - publishes the composite action's `summary` as a job output +4. The workflow should expose an output `summary` which maps to the job output. + +
+ Solution + +```yaml +name: Reusable summary workflow + +name: Use reusable summary + workflow_call: + inputs: + numbers: + description: 'JSON array of numbers as a string' + type: string + required: true + default: '[1,2,3]' + outputs: + summary: + description: 'Summary produced by the composite action' + value: ${{ jobs.summarize.outputs.summary }} + + + summarize: + runs-on: ubuntu-latest + outputs: + summary: ${{ steps.call-summary.outputs.summary }} + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: call-summary + uses: ./.github/actions/summary-action + with: + numbers: ${{ inputs.numbers }} +``` +
+on: [workflow_dispatch] + uses: ./.github/workflows/reusable-2.yml + numbers: '[5, 15, 25, 35, 45]' +
+ Solution + +```yaml +name: Use reusable summary + +- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. + +- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. + call-reusable: + uses: ./.github/workflows/reusable-2.yml + with: + numbers: '[5, 15, 25, 35, 45]' + + show-summary: + runs-on: ubuntu-latest + needs: [call-reusable] + steps: + - name: Print reusable workflow output + run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" +``` +
+ +## Notes and tips + +- The composite action is simple and purposely uses a temporary file to demonstrate passing multi-line or structured inputs into scripts. +- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. + From 6f7883775f0ab06918aa7d2dcda76386f5771f5e Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Thu, 23 Oct 2025 10:33:06 +0200 Subject: [PATCH 02/15] fix: Update reusable workflow documentation for clarity and output details --- labs/reusable-2.md | 318 +++++---------------------------------------- 1 file changed, 34 insertions(+), 284 deletions(-) diff --git a/labs/reusable-2.md b/labs/reusable-2.md index 9516d152..30891962 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -1,6 +1,6 @@ # Reusable workflow that uses a composite action -This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. +This lab builds on the composite action exercise. The reusable workflow will call the composite action `summary-action` (created earlier) to compute a small summary of a list of numbers and expose that summary as outputs. A consuming workflow will call the reusable workflow and then print the outputs. Follow these steps: @@ -11,8 +11,9 @@ Follow these steps: 3. The workflow should run a job `summarize` which: - checks out the repository - calls the composite action `./.github/actions/summary-action` with the provided input - - publishes the composite action's `summary` as a job output -4. The workflow should expose an output `summary` which maps to the job output. + - formats the JSON summary into a concise human-readable string + - publishes both the JSON `summary` and the human-readable `summary_text` as job outputs +4. The workflow should expose outputs that map to the job outputs.
Solution: reusable-2.yml @@ -30,14 +31,18 @@ on: default: '[1,2,3]' outputs: summary: - description: 'Summary produced by the composite action' + description: 'Summary produced by the composite action (JSON)' value: ${{ jobs.summarize.outputs.summary }} + summary_text: + description: 'Human-readable summary text' + value: ${{ jobs.summarize.outputs.summary_text }} jobs: summarize: runs-on: ubuntu-latest outputs: summary: ${{ steps.call-summary.outputs.summary }} + summary_text: ${{ steps.format.outputs.summary_text }} steps: - uses: actions/checkout@v4 @@ -46,97 +51,25 @@ jobs: uses: ./.github/actions/summary-action with: numbers: ${{ inputs.numbers }} -``` -
- -## Create a consumer workflow - -1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. -2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. -3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. - -
- Solution: use-reusable-2.yml (consumer) - -```yaml -name: Use reusable summary - -on: [workflow_dispatch] -jobs: - call-reusable: - uses: ./.github/workflows/reusable-2.yml - with: - numbers: '[5, 15, 25, 35, 45]' - - show-summary: - runs-on: ubuntu-latest - needs: [call-reusable] - steps: - - name: Print reusable workflow output - run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" -``` -
- -## Try it - -- Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). -- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. -- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. - -## Notes and tips - -- The composite action uses stdin in the example to keep wiring trivial. -- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. -- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. -# Reusable workflow that uses a composite action - -This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. - -Follow these steps: - -## Create the reusable workflow - -1. Create `.github/workflows/reusable-2.yml`. -2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. -3. The workflow should run a job `summarize` which: - - checks out the repository - - calls the composite action `./.github/actions/summary-action` with the provided input - - publishes the composite action's `summary` as a job output -4. The workflow should expose an output `summary` which maps to the job output. - -
- Solution: reusable-2.yml - -```yaml -name: Reusable summary workflow - -on: - workflow_call: - inputs: - numbers: - description: 'JSON array of numbers as a string' - type: string - required: true - default: '[1,2,3]' - outputs: - summary: - description: 'Summary produced by the composite action' - value: ${{ jobs.summarize.outputs.summary }} - -jobs: - summarize: - runs-on: ubuntu-latest - outputs: - summary: ${{ steps.call-summary.outputs.summary }} - steps: - - uses: actions/checkout@v4 - - - name: Call summary composite action - id: call-summary - uses: ./.github/actions/summary-action - with: - numbers: ${{ inputs.numbers }} + - name: Format summary (create human-readable text) + id: format + shell: bash + run: | + printf '%s' "${{ steps.call-summary.outputs.summary }}" > summary.json || true + python3 - <<'PY' > summary_text.txt +import json +try: + s = json.load(open('summary.json')) +except Exception: + print('Invalid summary JSON') + raise +if not isinstance(s, dict) or s.get('count', 0) == 0: + print('No numbers provided') +else: + print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") +PY + echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT ```
@@ -144,7 +77,7 @@ jobs: 1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. 2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. -3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. +3. Add a second job `show-summary` that depends on `call-reusable` and prints the outputs.
Solution: use-reusable-2.yml (consumer) @@ -164,8 +97,10 @@ jobs: runs-on: ubuntu-latest needs: [call-reusable] steps: - - name: Print reusable workflow output - run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" + - name: Print reusable workflow outputs + run: | + echo "Reusable summary (JSON): ${{ needs.call-reusable.outputs.summary }}" + echo "Reusable summary (text): ${{ needs.call-reusable.outputs.summary_text }}" ```
@@ -173,195 +108,10 @@ jobs: - Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). - Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. -- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. +- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the outputs. ## Notes and tips - The composite action uses stdin in the example to keep wiring trivial. - In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. -- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. -# Reusable workflow that uses a composite action - -This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. - -Follow these steps: - -## Create the reusable workflow - -1. Create `.github/workflows/reusable-2.yml`. -2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. -3. The workflow should run a job `summarize` which: - - checks out the repository - - calls the composite action `./.github/actions/summary-action` with the provided input - - publishes the composite action's `summary` as a job output -4. The workflow should expose an output `summary` which maps to the job output. - -
- Solution: reusable-2.yml - -```yaml -name: Reusable summary workflow - -on: - workflow_call: - inputs: - numbers: - description: 'JSON array of numbers as a string' - type: string - required: true - default: '[1,2,3]' - outputs: - summary: - description: 'Summary produced by the composite action' - value: ${{ jobs.summarize.outputs.summary }} - -jobs: - summarize: - runs-on: ubuntu-latest - outputs: - summary: ${{ steps.call-summary.outputs.summary }} - steps: - - uses: actions/checkout@v4 - - - name: Call summary composite action - id: call-summary - uses: ./.github/actions/summary-action - with: - numbers: ${{ inputs.numbers }} -``` -
- -## Create a consumer workflow - -1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. -2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. -3. Add a second job `show-summary` that depends on `call-reusable` and prints the output. - -
- Solution: use-reusable-2.yml (consumer) - -```yaml -name: Use reusable summary - -on: [workflow_dispatch] - -jobs: - call-reusable: - uses: ./.github/workflows/reusable-2.yml - with: - numbers: '[5, 15, 25, 35, 45]' - - show-summary: - runs-on: ubuntu-latest - needs: [call-reusable] - steps: - - name: Print reusable workflow output - run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" -``` -
- -## Try it - -- Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). -- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. -- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. - -## Notes and tips - -- The composite action is simple and purposely uses stdin in the example to keep wiring trivial. -- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. -- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. -# Reusable workflow that uses a composite action - -This lab builds on the composite action exercise. Instead of greeting someone, the reusable workflow will call the composite action `summary-action` (created in the previous exercise) to compute a small summary of a list of numbers and expose that summary as a workflow output. A consuming workflow will call the reusable workflow and then print the output. - -Follow these steps: - -## Create the reusable workflow - -1. Create `.github/workflows/reusable-2.yml`. -2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. -3. The workflow should run a job `summarize` which: - - checks out the repository - - calls the composite action `./.github/actions/summary-action` with the provided input - - publishes the composite action's `summary` as a job output -4. The workflow should expose an output `summary` which maps to the job output. - - description: 'Summary produced by the composite action' - uses: ./.github/actions/summary-action -## Create the reusable workflow - -1. Create `.github/workflows/reusable-2.yml`. -2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. -3. The workflow should run a job `summarize` which: - - checks out the repository - - calls the composite action `./.github/actions/summary-action` with the provided input - - publishes the composite action's `summary` as a job output -4. The workflow should expose an output `summary` which maps to the job output. - -
- Solution - -```yaml -name: Reusable summary workflow - -name: Use reusable summary - workflow_call: - inputs: - numbers: - description: 'JSON array of numbers as a string' - type: string - required: true - default: '[1,2,3]' - outputs: - summary: - description: 'Summary produced by the composite action' - value: ${{ jobs.summarize.outputs.summary }} - - - summarize: - runs-on: ubuntu-latest - outputs: - summary: ${{ steps.call-summary.outputs.summary }} - steps: - - uses: actions/checkout@v4 - - - name: Call summary composite action - id: call-summary - uses: ./.github/actions/summary-action - with: - numbers: ${{ inputs.numbers }} -``` -
-on: [workflow_dispatch] - uses: ./.github/workflows/reusable-2.yml - numbers: '[5, 15, 25, 35, 45]' -
- Solution - -```yaml -name: Use reusable summary - -- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. - -- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the JSON summary. - call-reusable: - uses: ./.github/workflows/reusable-2.yml - with: - numbers: '[5, 15, 25, 35, 45]' - - show-summary: - runs-on: ubuntu-latest - needs: [call-reusable] - steps: - - name: Print reusable workflow output - run: echo "Reusable summary: ${{ needs.call-reusable.outputs.summary }}" -``` -
- -## Notes and tips - -- The composite action is simple and purposely uses a temporary file to demonstrate passing multi-line or structured inputs into scripts. -- In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. -- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. - +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. \ No newline at end of file From 81fbe61d954835a143c990755dc4e3932c726772 Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Fri, 31 Oct 2025 13:00:24 +0100 Subject: [PATCH 03/15] Update ci/print_summary.py Co-authored-by: Michael Ingeman-Nielsen --- ci/print_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/print_summary.py b/ci/print_summary.py index df7d97cc..022b5120 100644 --- a/ci/print_summary.py +++ b/ci/print_summary.py @@ -49,7 +49,7 @@ def main(): sys.exit(4) out = summarize(numbers) - print(json.dumps(out, indent=2)) + print(json.dumps(out)) if __name__ == "__main__": From 31b48e09823d62944c3a102df00d45242baa3783 Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Fri, 31 Oct 2025 13:00:38 +0100 Subject: [PATCH 04/15] Update labs/composite-action.md Co-authored-by: Michael Ingeman-Nielsen --- labs/composite-action.md | 1 + 1 file changed, 1 insertion(+) diff --git a/labs/composite-action.md b/labs/composite-action.md index 60a96e70..7951834a 100644 --- a/labs/composite-action.md +++ b/labs/composite-action.md @@ -34,6 +34,7 @@ inputs: outputs: summary: description: 'JSON summary produced by the helper' + value: ${{ steps.set-output.outputs.summary }} runs: using: "composite" steps: From c78b1e41cc38ae201a57c98b149810ad3d37ba7b Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Fri, 31 Oct 2025 13:00:52 +0100 Subject: [PATCH 05/15] Update labs/composite-action.md Co-authored-by: Michael Ingeman-Nielsen --- labs/composite-action.md | 1 + 1 file changed, 1 insertion(+) diff --git a/labs/composite-action.md b/labs/composite-action.md index 7951834a..139b49a4 100644 --- a/labs/composite-action.md +++ b/labs/composite-action.md @@ -45,6 +45,7 @@ runs: cat summary.json - name: Set output + id: set-output shell: bash run: echo "summary=$(cat summary.json)" >> $GITHUB_OUTPUT ``` From 2b31254a77372ab84e5494db0e42acf5b52f9382 Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Fri, 31 Oct 2025 13:01:09 +0100 Subject: [PATCH 06/15] Update labs/reusable-2.md Co-authored-by: Michael Ingeman-Nielsen --- labs/reusable-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labs/reusable-2.md b/labs/reusable-2.md index 30891962..c11882e3 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -7,7 +7,7 @@ Follow these steps: ## Create the reusable workflow 1. Create `.github/workflows/reusable-2.yml`. -2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `[1,2,3]`. +2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `'[1,2,3]'`. 3. The workflow should run a job `summarize` which: - checks out the repository - calls the composite action `./.github/actions/summary-action` with the provided input From bb1f56ee278af7f0cac126b82df5774452cce6f2 Mon Sep 17 00:00:00 2001 From: Noemi Csokas Date: Fri, 31 Oct 2025 13:02:01 +0100 Subject: [PATCH 07/15] Update labs/reusable-2.md Co-authored-by: Michael Ingeman-Nielsen --- labs/reusable-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labs/reusable-2.md b/labs/reusable-2.md index c11882e3..9135821f 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -56,7 +56,7 @@ jobs: id: format shell: bash run: | - printf '%s' "${{ steps.call-summary.outputs.summary }}" > summary.json || true + printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true python3 - <<'PY' > summary_text.txt import json try: From c8c00cc2923db4a81789703dd9dce086cbb81e07 Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 09:32:51 +0100 Subject: [PATCH 08/15] fix: minor composite actions markdown issues --- labs/composite-action.md | 92 ++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/labs/composite-action.md b/labs/composite-action.md index 139b49a4..cca8491c 100644 --- a/labs/composite-action.md +++ b/labs/composite-action.md @@ -2,7 +2,6 @@ In this hands-on lab you'll create a small composite action that runs a few shell steps plus the Python helper located at `ci/print_summary.py`. - The goal is to learn how to: create a composite action, accept inputs, expose outputs, and call it from a workflow. This lab contains the following steps: @@ -15,40 +14,42 @@ This lab contains the following steps: 1. Create a new directory `.github/actions/summary-action` in the repository. 2. Add an `action.yml` file with the following behaviour: + - Accept an input `numbers` which is a JSON array encoded as a string (for simplicity) - - Pipe that string to the Python helper `ci/print_summary.py` via stdin - - Save the printed JSON summary to an output parameter called `summary` + - Pipe that string to the Python helper `ci/print_summary.py` via stdin + - Save the printed JSON summary to an output parameter called `summary` - For more information on action metadata format and the composite action syntax, see the GitHub Docs: https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-an-action-metadata-file + For more information on action metadata format and the composite action syntax, see the GitHub Docs:
Solution -```yaml -name: Summary action -description: "Pipe numbers into a Python helper and print a small summary" -inputs: - numbers: - description: 'JSON array of numbers as a string, e.g. "[1,2,3]"' - required: true -outputs: - summary: - description: 'JSON summary produced by the helper' - value: ${{ steps.set-output.outputs.summary }} -runs: - using: "composite" - steps: - - name: Run python summarizer (stdin) - shell: bash - run: | - echo "${{ inputs.numbers }}" | python3 ci/print_summary.py > summary.json - cat summary.json - - - name: Set output - id: set-output - shell: bash - run: echo "summary=$(cat summary.json)" >> $GITHUB_OUTPUT -``` + ```yaml + name: Summary action + description: "Pipe numbers into a Python helper and print a small summary" + inputs: + numbers: + description: 'JSON array of numbers as a string, e.g. "[1,2,3]"' + required: true + outputs: + summary: + description: 'JSON summary produced by the helper' + value: ${{ steps.set-output.outputs.summary }} + runs: + using: "composite" + steps: + - name: Run python summarizer (stdin) + shell: bash + run: | + echo "${{ inputs.numbers }}" | python3 ci/print_summary.py > summary.json + cat summary.json + + - name: Set output + id: set-output + shell: bash + run: echo "summary=$(cat summary.json)" >> $GITHUB_OUTPUT + ``` +
Notes: @@ -66,26 +67,27 @@ Example consumer (solution):
Solution -```yaml -name: Use summary action + ```yaml + name: Use summary action -on: [workflow_dispatch] + on: [workflow_dispatch] -jobs: - call-summary: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + jobs: + call-summary: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: summary + uses: ./.github/actions/summary-action + with: + numbers: '[10, 20, 30, 40]' - - name: Call summary composite action - id: summary - uses: ./.github/actions/summary-action - with: - numbers: '[10, 20, 30, 40]' + - name: Print action output + run: echo "Summary output: ${{ steps.summary.outputs.summary }}" + ``` - - name: Print action output - run: echo "Summary output: ${{ steps.summary.outputs.summary }}" -```
## Run the workflow From 82b9f420581aac054f50c0068030438b0934d48f Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 09:34:31 +0100 Subject: [PATCH 09/15] fix: minor reusable-2 markdown issues --- labs/reusable-2.md | 150 +++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 74 deletions(-) diff --git a/labs/reusable-2.md b/labs/reusable-2.md index 9135821f..47b0d9a0 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -18,59 +18,60 @@ Follow these steps:
Solution: reusable-2.yml -```yaml -name: Reusable summary workflow - -on: - workflow_call: - inputs: - numbers: - description: 'JSON array of numbers as a string' - type: string - required: true - default: '[1,2,3]' - outputs: - summary: - description: 'Summary produced by the composite action (JSON)' - value: ${{ jobs.summarize.outputs.summary }} - summary_text: - description: 'Human-readable summary text' - value: ${{ jobs.summarize.outputs.summary_text }} - -jobs: - summarize: - runs-on: ubuntu-latest - outputs: - summary: ${{ steps.call-summary.outputs.summary }} - summary_text: ${{ steps.format.outputs.summary_text }} - steps: - - uses: actions/checkout@v4 - - - name: Call summary composite action - id: call-summary - uses: ./.github/actions/summary-action - with: - numbers: ${{ inputs.numbers }} - - - name: Format summary (create human-readable text) - id: format - shell: bash - run: | - printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true - python3 - <<'PY' > summary_text.txt -import json -try: - s = json.load(open('summary.json')) -except Exception: - print('Invalid summary JSON') - raise -if not isinstance(s, dict) or s.get('count', 0) == 0: - print('No numbers provided') -else: - print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") -PY - echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT -``` + ```yaml + name: Reusable summary workflow + + on: + workflow_call: + inputs: + numbers: + description: 'JSON array of numbers as a string' + type: string + required: true + default: '[1,2,3]' + outputs: + summary: + description: 'Summary produced by the composite action (JSON)' + value: ${{ jobs.summarize.outputs.summary }} + summary_text: + description: 'Human-readable summary text' + value: ${{ jobs.summarize.outputs.summary_text }} + + jobs: + summarize: + runs-on: ubuntu-latest + outputs: + summary: ${{ steps.call-summary.outputs.summary }} + summary_text: ${{ steps.format.outputs.summary_text }} + steps: + - uses: actions/checkout@v4 + + - name: Call summary composite action + id: call-summary + uses: ./.github/actions/summary-action + with: + numbers: ${{ inputs.numbers }} + + - name: Format summary (create human-readable text) + id: format + shell: bash + run: | + printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true + python3 - <<'PY' > summary_text.txt + import json + try: + s = json.load(open('summary.json')) + except Exception: + print('Invalid summary JSON') + raise + if not isinstance(s, dict) or s.get('count', 0) == 0: + print('No numbers provided') + else: + print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") + PY + echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT + ``` +
## Create a consumer workflow @@ -82,26 +83,27 @@ PY
Solution: use-reusable-2.yml (consumer) -```yaml -name: Use reusable summary - -on: [workflow_dispatch] - -jobs: - call-reusable: - uses: ./.github/workflows/reusable-2.yml - with: - numbers: '[5, 15, 25, 35, 45]' - - show-summary: - runs-on: ubuntu-latest - needs: [call-reusable] - steps: - - name: Print reusable workflow outputs - run: | - echo "Reusable summary (JSON): ${{ needs.call-reusable.outputs.summary }}" - echo "Reusable summary (text): ${{ needs.call-reusable.outputs.summary_text }}" -``` + ```yaml + name: Use reusable summary + + on: [workflow_dispatch] + + jobs: + call-reusable: + uses: ./.github/workflows/reusable-2.yml + with: + numbers: '[5, 15, 25, 35, 45]' + + show-summary: + runs-on: ubuntu-latest + needs: [call-reusable] + steps: + - name: Print reusable workflow outputs + run: | + echo "Reusable summary (JSON): ${{ needs.call-reusable.outputs.summary }}" + echo "Reusable summary (text): ${{ needs.call-reusable.outputs.summary_text }}" + ``` +
## Try it @@ -114,4 +116,4 @@ jobs: - The composite action uses stdin in the example to keep wiring trivial. - In real actions you might want to validate inputs more strictly and avoid writing secrets to disk. -- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. \ No newline at end of file +- This example shows how composite actions and reusable workflows can be composed to build small, testable building blocks. From 4b389e4d382b83966e8b17d32d9292ae8be295bf Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 09:58:15 +0100 Subject: [PATCH 10/15] fix: extract summary formatting into standalone python file --- ci/format_summary.py | 12 ++++++++++++ labs/reusable-2.md | 13 +------------ 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 ci/format_summary.py diff --git a/ci/format_summary.py b/ci/format_summary.py new file mode 100644 index 00000000..0093b809 --- /dev/null +++ b/ci/format_summary.py @@ -0,0 +1,12 @@ +import json + +try: + s = json.load(open('summary.json')) +except Exception: + print('Invalid summary JSON') + raise + +if not isinstance(s, dict) or s.get('count', 0) == 0: + print('No numbers provided') +else: + print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") \ No newline at end of file diff --git a/labs/reusable-2.md b/labs/reusable-2.md index 47b0d9a0..1879b2f7 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -57,18 +57,7 @@ Follow these steps: shell: bash run: | printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true - python3 - <<'PY' > summary_text.txt - import json - try: - s = json.load(open('summary.json')) - except Exception: - print('Invalid summary JSON') - raise - if not isinstance(s, dict) or s.get('count', 0) == 0: - print('No numbers provided') - else: - print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") - PY + python3 ./ci/format_summary.py > summary_text.txt echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT ``` From c90a52f452ba62c4cedff9ab3cb9d909d86570ab Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 10:01:53 +0100 Subject: [PATCH 11/15] fix: replace deprecated List with builtin list type --- ci/print_summary.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ci/print_summary.py b/ci/print_summary.py index 022b5120..c4df0f9a 100644 --- a/ci/print_summary.py +++ b/ci/print_summary.py @@ -6,10 +6,8 @@ """ import sys import json -from typing import List - -def summarize(numbers: List[float]) -> dict: +def summarize(numbers: list[float]) -> dict: if not numbers: return {"count": 0, "sum": 0, "avg": None, "min": None, "max": None} total = sum(numbers) From 272c74827ad4931a1d1dc6cfc75a04f5d2c3cb10 Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 10:31:42 +0100 Subject: [PATCH 12/15] fix: add missing summary output to the reusable workflow --- labs/reusable-2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/labs/reusable-2.md b/labs/reusable-2.md index 1879b2f7..b542639b 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -57,6 +57,8 @@ Follow these steps: shell: bash run: | printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true + echo "summary_text=$(cat summary.json)" >> $GITHUB_OUTPUT + python3 ./ci/format_summary.py > summary_text.txt echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT ``` From f9e71e94cdae82b3faecc6bd6b4fd0e17ac18bf5 Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 10:34:59 +0100 Subject: [PATCH 13/15] fix: take summary file as input for format_summary.py instead of hardcoding --- ci/format_summary.py | 60 +++++++++++++++++++++++++++++++++++++------- labs/reusable-2.md | 2 +- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/ci/format_summary.py b/ci/format_summary.py index 0093b809..63d0b9e4 100644 --- a/ci/format_summary.py +++ b/ci/format_summary.py @@ -1,12 +1,54 @@ +#!/usr/bin/env python3 +""" +Format and display a summary from a JSON file. +Takes a JSON file path as a command-line argument. +""" import json +import sys +from typing import Any -try: - s = json.load(open('summary.json')) -except Exception: - print('Invalid summary JSON') - raise -if not isinstance(s, dict) or s.get('count', 0) == 0: - print('No numbers provided') -else: - print(f"count={s['count']}, sum={s['sum']}, avg={s['avg']:.2f}, min={s['min']}, max={s['max']}") \ No newline at end of file +def load_json_file(file_path: str) -> dict[str, Any]: + """Load and parse a JSON file with proper error handling. + + Args: + file_path: Path to the JSON file to load + + Returns: + The parsed JSON data as a dictionary + + Raises: + SystemExit: On file or JSON parsing errors + """ + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: File '{file_path}' not found", file=sys.stderr) + sys.exit(2) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in '{file_path}': {e}", file=sys.stderr) + sys.exit(2) + except OSError as e: + print(f"Error: Failed to read '{file_path}': {e}", file=sys.stderr) + sys.exit(2) + + +def main() -> None: + """Load a number summary in json format and output a human readable string instead + """ + if len(sys.argv) != 2: + print("Usage: python format_summary.py ", file=sys.stderr) + sys.exit(1) + + json_file = sys.argv[1] + summary = load_json_file(json_file) + + if not isinstance(summary, dict) or summary.get('count', 0) == 0: + print('No numbers provided') + else: + print(f"count={summary['count']}, sum={summary['sum']}, avg={summary['avg']:.2f}, min={summary['min']}, max={summary['max']}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/labs/reusable-2.md b/labs/reusable-2.md index b542639b..a8305ef7 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-2.md @@ -59,7 +59,7 @@ Follow these steps: printf '%s' '${{ steps.call-summary.outputs.summary }}' > summary.json || true echo "summary_text=$(cat summary.json)" >> $GITHUB_OUTPUT - python3 ./ci/format_summary.py > summary_text.txt + python3 ./ci/format_summary.py summary.json > summary_text.txt echo "summary_text=$(cat summary_text.txt)" >> $GITHUB_OUTPUT ``` From 1b370d538fe049bfec51c9622ebcf71be8c37ddd Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 11:27:38 +0100 Subject: [PATCH 14/15] feat: replace reusable.md with new reusable-workflow.md lab --- README.md | 2 +- labs/{ => old-labs}/reusable.md | 0 labs/{reusable-2.md => reusable-workflow.md} | 16 ++++++++-------- 3 files changed, 9 insertions(+), 9 deletions(-) rename labs/{ => old-labs}/reusable.md (100%) rename labs/{reusable-2.md => reusable-workflow.md} (87%) diff --git a/README.md b/README.md index 4de8a568..9c880ecd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This repository contains a set of exercises to learn Github Actions. * [Storing Artifacts](./labs/storing-artifacts.md) * [Building Docker images](./labs/docker-image.md) * [Systems test](./labs/systems-test.md) -* [Reusable workflows](./labs/reusable.md) +* [Reusable workflows](./labs/reusable-workflow.md) * [Pull Request based workflow](./labs/pr-workflow.md) * [Build app on multiple environments](./labs/matrix-builds.md) diff --git a/labs/reusable.md b/labs/old-labs/reusable.md similarity index 100% rename from labs/reusable.md rename to labs/old-labs/reusable.md diff --git a/labs/reusable-2.md b/labs/reusable-workflow.md similarity index 87% rename from labs/reusable-2.md rename to labs/reusable-workflow.md index a8305ef7..d2012843 100644 --- a/labs/reusable-2.md +++ b/labs/reusable-workflow.md @@ -6,7 +6,7 @@ Follow these steps: ## Create the reusable workflow -1. Create `.github/workflows/reusable-2.yml`. +1. Create `.github/workflows/reusable.yml`. 2. Use the `workflow_call` trigger and add an input `numbers` of type `string` (JSON array encoded as a string). The input should have a default of `'[1,2,3]'`. 3. The workflow should run a job `summarize` which: - checks out the repository @@ -16,7 +16,7 @@ Follow these steps: 4. The workflow should expose outputs that map to the job outputs.
- Solution: reusable-2.yml + Solution: reusable.yml ```yaml name: Reusable summary workflow @@ -67,12 +67,12 @@ Follow these steps: ## Create a consumer workflow -1. Create `.github/workflows/use-reusable-2.yml` with a `workflow_dispatch` trigger. -2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable-2.yml` and passes a custom `numbers` value. +1. Create `.github/workflows/use-reusable.yml` with a `workflow_dispatch` trigger. +2. Add a job `call-reusable` that uses the reusable workflow `./.github/workflows/reusable.yml` and passes a custom `numbers` value. 3. Add a second job `show-summary` that depends on `call-reusable` and prints the outputs.
- Solution: use-reusable-2.yml (consumer) + Solution: use-reusable.yml (consumer) ```yaml name: Use reusable summary @@ -81,7 +81,7 @@ Follow these steps: jobs: call-reusable: - uses: ./.github/workflows/reusable-2.yml + uses: ./.github/workflows/reusable.yml with: numbers: '[5, 15, 25, 35, 45]' @@ -100,8 +100,8 @@ Follow these steps: ## Try it - Make sure `ci/print_summary.py` is executable (if running on a GitHub runner the `python3` call will work). -- Commit the `action.yml` for the composite action, the `reusable-2.yml` workflow, and the consumer workflow. -- Dispatch `use-reusable-2.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the outputs. +- Commit the `action.yml` for the composite action, the `reusable.yml` workflow, and the consumer workflow. +- Dispatch `use-reusable.yml` from the GitHub Actions UI and inspect the `show-summary` job logs to see the outputs. ## Notes and tips From bfbdb57bbde94a534fa127805c6fe02fb8791419 Mon Sep 17 00:00:00 2001 From: Michael Ingeman-Nielsen Date: Tue, 4 Nov 2025 20:26:50 +0100 Subject: [PATCH 15/15] fix: reformat a line to avoid warning in GH online editor --- labs/composite-action.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/labs/composite-action.md b/labs/composite-action.md index cca8491c..736dc445 100644 --- a/labs/composite-action.md +++ b/labs/composite-action.md @@ -85,7 +85,8 @@ Example consumer (solution): numbers: '[10, 20, 30, 40]' - name: Print action output - run: echo "Summary output: ${{ steps.summary.outputs.summary }}" + run: | + echo "Summary output: ${{ steps.summary.outputs.summary }}" ```