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
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Goal
<!-- What does this PR accomplish? 1 sentence. -->

## Changes
-

## Testing
<!-- How did you verify it? -->

## Checklist
- [ ] Title is a clear sentence (<= 70 chars)
- [ ] Commits are signed (`git log --show-signature`)
- [ ] `submissions/labN.md` updated
117 changes: 117 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: CI

on:
push:
branches:
- main
paths:
- "app/**"
- ".github/workflows/ci.yml"
pull_request:
branches:
- main
paths:
- "app/**"
- ".github/workflows/ci.yml"

permissions:
contents: read

env:
GOFLAGS: -buildvcs=false
GOTOOLCHAIN: local

jobs:
vet:
name: vet (${{ matrix.go-version }})
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
go-version:
- "1.23"
- "1.24"
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: app/go.mod

- name: Download modules
working-directory: app
run: go mod download

- name: Run go vet
working-directory: app
run: go vet ./...

test:
name: test (${{ matrix.go-version }})
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
go-version:
- "1.23"
- "1.24"
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: app/go.mod

- name: Download modules
working-directory: app
run: go mod download

- name: Run tests with race detector
working-directory: app
run: go test -race -count=1 ./...

lint:
name: lint
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.24"
cache: true
cache-dependency-path: app/go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.5.0
working-directory: app
args: --timeout=2m

ci-ok:
name: ci-ok
runs-on: ubuntu-24.04
timeout-minutes: 2
if: always()
needs:
- vet
- test
- lint
steps:
- name: Check required jobs
run: |
test "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false"
2 changes: 1 addition & 1 deletion app/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module quicknotes

go 1.24
go 1.23
Binary file added submissions/img/broken-ci.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/img/ci-ok.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/img/fixed-ci.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/img/pr-checks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions submissions/lab3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Lab 3 Submission

## Path

I chose the GitHub Actions path because this fork is hosted on GitHub and GitHub Actions gives the most direct PR-gate integration for this course repository.

## CI Evidence

- Green CI run: ![ci-ok](img/ci-ok.png)
- Deliberately failed run from Task 1.5: ![broken-ci](img/broken-ci.png)
- Fix commit that restored green CI: ![fixed-ci](img/fixed-ci.png).
- Branch protection evidence: ![pr-checks](img/pr-checks.png)

## Pipeline Summary

- Workflow file: `.github/workflows/ci.yml`.
- Trigger: pushes to `main` and pull requests targeting `main`.
- Path filter: CI runs only when `app/**` or `.github/workflows/ci.yml` changes.
- Runtime: `ubuntu-24.04`, with Go `1.23` and `1.24` matrix cells for `vet` and `test`.
- Gate units:
- `vet`: `go vet ./...`
- `test`: `go test -race -count=1 ./...`
- `lint`: `golangci-lint run`, pinned to `v2.5.0`
- Aggregation check: `ci-ok`; this is the single required status check for branch protection.
- Cache: `actions/setup-go` cache enabled and keyed by `app/go.mod`. QuickNotes has no `go.sum` because it has no third-party module dependencies.

## Task 1 Design Questions

### a) Why pin `ubuntu-24.04` instead of `ubuntu-latest`?

`ubuntu-latest` is a moving alias. GitHub can repoint it to a newer image, which can change installed packages, compilers, shells, OpenSSL behavior, or preinstalled tools without a repository change. Pinning `ubuntu-24.04` makes CI failures correlate with our commits instead of a platform image migration.

### b) Why split vet, test, and lint into separate units?

Separate jobs run in parallel and report the failing class of problem directly. A combined job is simpler, but it serializes unrelated work and stops at the first failure, so a lint failure could hide a test failure until the next run.

### c) What real attack does SHA pinning prevent?

SHA pinning prevents tag-retargeting attacks against actions. Lecture 3 cites the March 2025 compromise of `tj-actions/changed-files`, where the attacker rewrote tags to malicious code and leaked secrets from public CI logs. A full commit SHA is immutable in the normal Git object model, so `v4` or `v4.2.2` moving does not change what the workflow executes.

### d) What is `permissions:` and what principle is behind it?

`permissions:` sets the default `GITHUB_TOKEN` scopes for the workflow or job. This workflow uses `contents: read`, following least privilege: CI needs to read the repository, not write issues, packages, deployments, or pull requests.

### e) GitLab-only question

I used the GitHub path, so this does not apply to my implementation. In GitLab, a stage is an ordering group and a job is a concrete unit of work inside a stage. `stages:` controls execution order; `dependencies:` controls which previous job artifacts are downloaded by a job.

## Task 2 Timing Table

| Scenario | Wall-clock |
|----------|-----------:|
| Baseline: no cache, single Go version, no path filter | 35s |
| With cache | 36s |
| With cache + matrix | 38s |

QuickNotes has no third-party dependencies, so caching doesn't affect total wall-clock time. The dominant cost is runner provisioning, checkout, setup, and linter download rather than module download. Time with matrix was increased a little due to parallel job management overhead.

## Task 2 Optimizations

- Enabled the Go cache through `actions/setup-go` so module and build caches are restored from a dependency-file key.
- Added a matrix for `vet` and `test` across Go `1.23` and `1.24`.
- Set `fail-fast: false` so all matrix cells report even if one version fails.
- Added path filters so docs-only changes outside `app/` do not start the workflow.
- Added `ci-ok` as a stable required check, avoiding branch-protection churn when matrix labels change.

## Task 2 Design Questions

### f) Why cache `go.sum`-keyed inputs and not build outputs?

Dependency inputs are deterministic: if the module lock data is unchanged, restoring those dependencies is safe and repeatable. Build outputs are more environment-sensitive because they can encode architecture, compiler version, build flags, CGO settings, or stale generated state. For this repo there is no `go.sum`, so the workflow keys the cache on `app/go.mod`; if dependencies are added later, the cache key should include `app/go.sum`.

### g) What does `fail-fast: false` change in a matrix run?

With `fail-fast: false`, one failing matrix cell does not cancel the remaining cells. That is useful for compatibility checks because I want to know whether only Go `1.23` failed or both versions failed. I would use `fail-fast: true` for expensive jobs where the first failure already proves the PR cannot merge and extra cells are unlikely to add useful diagnosis.

### h) What is the cache poisoning risk?

The risk is that untrusted PR code writes malicious files into a cache, then a trusted workflow later restores and executes those files. GitHub mitigates this with cache scope restrictions: caches created by pull-request runs are scoped to the PR merge ref and cannot be restored by the base branch or other pull requests. The relevant GitHub reference is "Dependency caching reference", especially the cache access restrictions section: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching