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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
| Passing data between steps and jobs | [passing-data.md](./modules/02-github-actions/passing-data.md) |
| Reusable workflows & composite actions | [reusable-workflows.md](./modules/02-github-actions/reusable-workflows.md) |
| Performance & scale: caching & matrix builds | [caching-and-matrix.md](./modules/02-github-actions/caching-and-matrix.md) |
| Marketplace actions: find, evaluate, use safely | [marketplace-actions.md](./modules/02-github-actions/marketplace-actions.md) |
| Security best practices & SHA pinning | [security-best-practices.md](./modules/02-github-actions/security-best-practices.md) |
| `GITHUB_TOKEN` & least-privilege permissions | [10-permissions.md](./modules/02-github-actions/10-permissions.md) |
| Runners: hosted vs self-hosted | [runners-guide.md](./modules/02-github-actions/runners-guide.md) |
Expand Down
20 changes: 10 additions & 10 deletions modules/02-github-actions/exercises/exercise-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ steps:

| # | TODO | Concept | Read this |
|---|---|---|---|
| ① | `runs.using: "composite"` | Composite action declaration | [reusable-workflows.md → "Composite Actions"](../reusable-workflows.md?plain=1#L102) |
| ② | Use input in composite step | `${{ inputs.<name> }}` in actions | [reusable-workflows.md → composite example](../reusable-workflows.md?plain=1#L106) |
| ③ | `shell: bash` for run steps | Composite action requirement | [reusable-workflows.md → composite example](../reusable-workflows.md?plain=1#L140) |
| ① | `runs.using: "composite"` | Composite action declaration | [reusable-workflows.md → composite `runs:` block](../reusable-workflows.md?plain=1#L126) |
| ② | Use input in composite step | `${{ inputs.<name> }}` in actions | [reusable-workflows.md → `${{ inputs.python-version }}` line](../reusable-workflows.md?plain=1#L131) |
| ③ | `shell: bash` for run steps | Composite action requirement | [reusable-workflows.md → `shell: bash` line](../reusable-workflows.md?plain=1#L140) |

→ Solution: [solutions/03-release-workflow/.github/actions/setup-python-project/action.yml](../../../solutions/03-release-workflow/.github/actions/setup-python-project/action.yml)

Expand Down Expand Up @@ -155,9 +155,9 @@ jobs:

| # | TODO | Concept | Read this |
|---|---|---|---|
| ④ | `on: workflow_call:` | Reusable workflow trigger | [reusable-workflows.md → "Reusable Workflows"](../reusable-workflows.md?plain=1#L21) |
| ⑤ | `fromJSON()` to expand matrix | Dynamic matrix from input string | [caching-and-matrix.md → "Dynamic matrix from a workflow input"](../caching-and-matrix.md?plain=1#L135) |
| ⑥ | Local action via `uses: ./path` | Calling a composite action | [reusable-workflows.md → "Use the composite action"](../reusable-workflows.md?plain=1#L143) |
| ④ | `on: workflow_call:` | Reusable workflow trigger | [reusable-workflows.md → `on: workflow_call:` block](../reusable-workflows.md?plain=1#L29) |
| ⑤ | `fromJSON()` to expand matrix | Dynamic matrix from input string | [caching-and-matrix.md → `fromJSON(inputs.python-versions)` line](../caching-and-matrix.md?plain=1#L154) |
| ⑥ | Local action via `uses: ./path` | Calling a composite action | [reusable-workflows.md → `uses: ./.github/actions/...` line](../reusable-workflows.md?plain=1#L150) |

→ Solution: [solutions/03-release-workflow/.github/workflows/reusable-validate.yml](../../../solutions/03-release-workflow/.github/workflows/reusable-validate.yml)

Expand Down Expand Up @@ -249,10 +249,10 @@ jobs:

| # | TODO | Concept | Read this |
|---|---|---|---|
| ⑦ | `workflow_dispatch` inputs | Manual triggers | [manual-triggers.md → "Inputs"](../manual-triggers.md?plain=1#L20) |
| ⑧ | `jobs.<id>.uses:` | Calling a reusable workflow | [reusable-workflows.md → "Call the reusable workflow"](../reusable-workflows.md?plain=1#L64) |
| ⑨ | Reusing the composite action | DRY principle | [reusable-workflows.md → "When to Use Which"](../reusable-workflows.md?plain=1#L159) |
| ⑩ | Third-party action via SHA-pinned `uses:` | Marketplace actions | [security-best-practices.md → "SHA Pin Third-Party Actions"](../security-best-practices.md?plain=1#L7) |
| ⑦ | `workflow_dispatch` inputs | Manual triggers | [manual-triggers.md → `workflow_dispatch:` snippet](../manual-triggers.md?plain=1#L23) |
| ⑧ | `jobs.<id>.uses:` | Calling a reusable workflow | [reusable-workflows.md → `uses: ./.github/workflows/deploy.yml` line](../reusable-workflows.md?plain=1#L75) |
| ⑨ | Reusing the composite action | DRY principle | [reusable-workflows.md → `uses: ./.github/actions/...` line](../reusable-workflows.md?plain=1#L150) |
| ⑩ | Third-party action via SHA-pinned `uses:` | Marketplace actions | [marketplace-actions.md → `Create GitHub Release` step (copy this block)](../marketplace-actions.md?plain=1#L71) · [security-best-practices.md → SHA-pin example](../security-best-practices.md?plain=1#L16) |

→ Solution: [solutions/03-release-workflow/.github/workflows/release.yml](../../../solutions/03-release-workflow/.github/workflows/release.yml)

Expand Down
120 changes: 120 additions & 0 deletions modules/02-github-actions/marketplace-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# GitHub Actions Marketplace

> **TL;DR** — The Marketplace is GitHub's directory of pre-built actions you can drop into a workflow with one `uses:` line. Treat third-party actions like third-party code: verify, pin to a SHA, and review what permissions they request.

The vast majority of CI/CD steps you'd otherwise write by hand already exist as a Marketplace action. Need to publish a release? `softprops/action-gh-release`. Authenticate to AWS? `aws-actions/configure-aws-credentials`. Run Trivy? `aquasecurity/trivy-action`.

---

## Finding an action

Browse: https://github.com/marketplace?type=actions

```bash
# Search from the CLI
gh api -X GET search/repositories \
-f q='topic:github-action release publish' \
--jq '.items[] | {full_name, stargazers_count, html_url}' | head -20
```

When you read an action's listing, look for:

| Signal | What you want |
|--------|---------------|
| **Verified creator** badge | ✅ official org (e.g. `actions/`, `aws-actions/`, `docker/`) |
| **Stars / installs** | High = battle-tested |
| **Last commit / release** | Recent (< 6 months) |
| **Open issues** | Triaged, not piling up |
| **`action.yml`** | Read it. It's small. You can audit the whole thing. |
| **Required permissions** | The README usually lists them — minimum needed |

---

## Using an action

```yaml
- name: Create GitHub Release
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: ${{ needs.resolve.outputs.version }}
name: Release ${{ needs.resolve.outputs.version }}
generate_release_notes: true
prerelease: ${{ needs.resolve.outputs.prerelease == 'true' }}
files: dist/*
```

Three rules:

1. **Pin to a SHA.** Tags are mutable; SHAs aren't. See [security-best-practices.md → "SHA Pin Third-Party Actions"](./security-best-practices.md?plain=1#L7).
2. **Read its `action.yml`.** Inputs, outputs, and required permissions are all in one short file in the action's repo.
3. **Grant only the permissions it needs.** The release example above needs `permissions: contents: write` — granted at the *job* level only, not workflow-wide.

---

## Example: publish a GitHub Release

This is the pattern the release-pipeline exercise (TODO ⑩) builds toward.

```yaml
publish:
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: write # ← only this job can create a release
steps:
- name: Download dist
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: dist
path: dist/

- name: Create GitHub Release
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: ${{ inputs.version }}
name: Release ${{ inputs.version }}
generate_release_notes: true # auto-generate from PR titles
prerelease: ${{ inputs.prerelease }} # honor the dispatch input
Comment on lines +74 to +77
files: dist/* # attach build artifacts
```

What you get: a new entry on the repo's **Releases** page, the resolved version as a Git tag, auto-generated notes built from PR titles since the previous tag, and every file under `dist/` attached as a downloadable asset.

---

## Other commonly-used Marketplace actions

| Need | Action |
|------|--------|
| Checkout code | [`actions/checkout`](https://github.com/marketplace/actions/checkout) (first-party) |
| Setup Python | [`actions/setup-python`](https://github.com/marketplace/actions/setup-python) (first-party) |
| Cache deps | [`actions/cache`](https://github.com/marketplace/actions/cache) (first-party) |
| Upload/download artifacts | [`actions/upload-artifact`](https://github.com/marketplace/actions/upload-a-build-artifact) (first-party) |
| Build & push Docker images | [`docker/build-push-action`](https://github.com/marketplace/actions/build-and-push-docker-images) |
| Authenticate to AWS | [`aws-actions/configure-aws-credentials`](https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions) |
| Authenticate to Azure | [`azure/login`](https://github.com/marketplace/actions/azure-login) |
| Publish a GitHub Release | [`softprops/action-gh-release`](https://github.com/marketplace/actions/gh-release) |
| Send a Slack notification | [`slackapi/slack-github-action`](https://github.com/marketplace/actions/slack-send) |
| Container vulnerability scan | [`aquasecurity/trivy-action`](https://github.com/marketplace/actions/aqua-security-trivy) |
| Dependency review | [`actions/dependency-review-action`](https://github.com/marketplace/actions/dependency-review) (first-party) |

---

## When *not* to use a Marketplace action

Sometimes a 3-line `run:` is better than a third-party dependency:

```yaml
# Don't pull in an action for this:
- run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"

# Don't pull in an action for this either:
- run: |
if [ "$ENV" = "prod" ]; then ./deploy.sh; fi
```

Rule of thumb: if the action is < 30 lines of shell, just inline it. Every dependency is a supply-chain risk.

---

→ Next: [security-best-practices.md](./security-best-practices.md) for SHA pinning and least-privilege patterns.
Binary file added resources/presentation/github-pr-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.