diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..748b5b09a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,114 @@ +name: ci + +on: + push: + branches: + - main + + paths: + - 'app/**' + - '.github/workflows/**' + + pull_request: + branches: + - main + + paths: + - 'app/**' + - '.github/workflows/**' + +permissions: + contents: read + +jobs: + vet: + name: vet (${{ matrix.go-version }}) + + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + + matrix: + go-version: + - '1.23' + - '1.24' + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + + - name: Run go vet + working-directory: app + run: go vet ./... + + test: + name: test (${{ matrix.go-version }}) + + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + + matrix: + go-version: + - '1.23' + - '1.24' + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + + - name: Run tests + working-directory: app + run: go test -race -count=1 ./... + + lint: + name: lint + + runs-on: ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: '1.24' + cache: true + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee + with: + version: v2.5.0 + working-directory: app + + ci-ok: + name: ci-ok + + if: always() + + needs: + - vet + - test + - lint + + runs-on: ubuntu-24.04 + + steps: + - name: Aggregate results + run: | + test "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false" diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..8f3a6d82d --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,112 @@ +# Lab 3 submission + +# Lab 3 + +## Platform choice + +I chose GitHub Actions because the project is already hosted on GitHub and GitHub Actions +is tightly integrated with pull requests, branch protection rules and repository management. +This makes it easy to implement CI/CD for QuickNotes without introducing an additional platform. + +## Task 1.1 and 1.4 - CI implementation and iteration to green + +I implemented a CI pipeline for QuickNotes using GitHub Actions. + +The workflow executes three independent jobs: +* vet +* test +* lint + +The workflow is configured to run on: + +* pushes to main +* pull requests targeting main + +I iterated on the workflow configuration until all checks passed successfully. + +![Ci1](screenshots/greenci1.png) + +Link: https://github.com/Littlepr1nce/DevOps-Intro/actions/runs/27525351060 + +## 1.2 Design questions + +### a) Why pin the runner version (ubuntu-24.04) instead of ubuntu-latest? What breaks otherwise? + +`ubuntu-24.04` is pinned to guarantee reproducible builds. +`ubuntu-latest` may suddenly point to a newer operating system version, +which can introduce different package versions, +deprecations or compatibility issues and unexpectedly break the pipeline. + +### b) Why split vet + test + lint into separate units? What would happen with one combined job? + +`vet`, `test` and `lint` are separated to isolate responsibilities and allow parallel execution. +If they were combined into one job, a failure in one step would stop +the entire pipeline and make troubleshooting harder. + +### c) GH path: what real attack does SHA pinning prevent? Cite the date + name of the incident from Lecture 3 + +SHA pinning prevents supply-chain attacks caused by a compromised third-party GitHub Action. +It ensures that the workflow always executes the exact audited commit instead of a mutable tag. + +Lecture 3 referenced the March 2025 tj-actions/changed-files compromise, +where attackers modified a GitHub Action and exposed CI secrets. + +### d) GH path: what is permissions: and what's the principle behind it? + +`permissions:` defines which GitHub API permissions are granted to the workflow token (`GITHUB_TOKEN`). +The underlying security principle is least privilege: +workflows should receive only the permissions they actually need. + +### e) GitLab path: what's the difference between a stage and a job? What would dependencies: do that stages: doesn't? + +In GitLab CI, a stage is a high-level execution phase (for example: build, test, deploy), +while a job is an individual task executed within a stage. +`dependencies:` allows a job to download artifacts from specific earlier jobs, +whereas `stages:` only defines the execution order between groups of jobs. + +## Task 1.3 - Resources used + +I used the official documentation provided in the assignment: + +GitHub Actions Quickstart +GitHub Actions Workflow syntax reference +actions/setup-go documentation +golangci/golangci-lint-action documentation + +The workflow was adapted to the QuickNotes project structure by using app/ as the working directory. + +## Task 1.5 Prove the gate works + +To prove that the PR gate works, I intentionally broke a test in `app/handlers_test.go` by changing the expected HTTP status code. + +Broken commit: + +`test(lab3): intentionally break CI` + +![Brokencommit](screenshots/redci.png) + +Link: https://github.com/Littlepr1nce/DevOps-Intro/actions/runs/27525728100 + +Fix commit: + +`test(lab3): restore passing CI` + +![Ci2](screenshots/greenci2.png) + +Link: https://github.com/Littlepr1nce/DevOps-Intro/actions/runs/27525935469 + +## Task 1.6 Branch protection + +I configured a branch protection rule for `main` with the following settings: + +* Require a pull request before merging +* Require status checks to pass before merging +* Require branches to be up to date before merging +* Required checks: + * ci / vet + * ci / test + * ci / lint + +![Rules](screenshots/rules.png) + + diff --git a/submissions/screenshots/greenci1.png b/submissions/screenshots/greenci1.png new file mode 100644 index 000000000..6364e88b4 Binary files /dev/null and b/submissions/screenshots/greenci1.png differ diff --git a/submissions/screenshots/greenci2.png b/submissions/screenshots/greenci2.png new file mode 100644 index 000000000..9cb867b1f Binary files /dev/null and b/submissions/screenshots/greenci2.png differ diff --git a/submissions/screenshots/redci.png b/submissions/screenshots/redci.png new file mode 100644 index 000000000..603056d94 Binary files /dev/null and b/submissions/screenshots/redci.png differ diff --git a/submissions/screenshots/rules.png b/submissions/screenshots/rules.png new file mode 100644 index 000000000..012c26700 Binary files /dev/null and b/submissions/screenshots/rules.png differ