diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..d3bb44554 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,98 @@ +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 + +jobs: + vet: + name: vet (${{ matrix.go-version }}) + runs-on: ubuntu-24.04 + strategy: + matrix: + go-version: [ '1.23', '1.24' ] + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2 + + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.3.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: app/go.sum + + - name: go vet + working-directory: app + run: go vet ./... + + test: + name: test (${{ matrix.go-version }}) + runs-on: ubuntu-24.04 + strategy: + matrix: + go-version: [ '1.23', '1.24' ] + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2 + + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.3.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: app/go.sum + + - name: go test -race + working-directory: app + run: go test -race -count=1 ./... + + lint: + name: lint + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2 + + - name: Set up Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.3.0 + with: + go-version: '1.24' + cache: true + cache-dependency-path: app/go.sum + + - name: Install golangci-lint v2.5.0 + working-directory: app + run: | + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 + + - name: Run golangci-lint + working-directory: app + run: golangci-lint run + + ci-ok: + name: ci-ok + if: always() + needs: [ vet, test, lint ] + runs-on: ubuntu-24.04 + steps: + - name: Check results + run: | + if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then + echo "Some jobs failed or were cancelled" + exit 1 + fi + echo "All jobs passed" \ No newline at end of file diff --git a/submissions/PASS.png b/submissions/PASS.png new file mode 100644 index 000000000..f51de156e Binary files /dev/null and b/submissions/PASS.png differ diff --git a/submissions/branch.png b/submissions/branch.png new file mode 100644 index 000000000..4820f11fa Binary files /dev/null and b/submissions/branch.png differ diff --git a/submissions/fail.png b/submissions/fail.png new file mode 100644 index 000000000..4367dad25 Binary files /dev/null and b/submissions/fail.png differ diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..928d1c9f0 --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,72 @@ +# Lab 3 — CI/CD: A PR-Gated Pipeline for QuickNotes + +**Студент:** Руслан Кудинов +**Путь:** GitHub Actions +**Дата:** 17.06.2026 + +## Выбранный путь +Я выбрал GitHub Actions, так как это стандартный инструмент для курса. Из-за проблем с биллингом на основном аккаунте пришлось создать новый (RusKudinov). + +## Ссылка на зелёный CI run +https://github.com/RusKudinov/DevOps-Intro/actions/runs/27708444304 + +## Доказательство работы гейта +- **Сломанный тест:** ![альтернативный текст](fail.png) +- **Исправление:** ![альтернативный текст](PASS.png) +- **Коммит с поломкой:** `3b6b086 ` +- **Коммит с исправлением:** `014941a` + +## Скриншот branch protection +![альтернативный текст](branch.png) +--- + +## Ответы на дизайн-вопросы (Task 1.2) + +### a) Почему пиннить `ubuntu-24.04`, а не `ubuntu-latest`? +`ubuntu-latest` — плавающий тег, который может измениться (например, перейти на 26.04). Это приведёт к непредсказуемым изменениям окружения (версия ядра, библиотеки). Пиннинг фиксирует конкретную версию, делая сборку воспроизводимой. + +### b) Почему разделить vet, test, lint на отдельные джобы? +Если объединить их в одну, при падении lint мы не узнаем, прошли ли тесты. Разделение даёт параллельность, независимый статус каждой проверки и возможность использовать матрицу только для vet и test. + +### c) Какую атаку предотвращает SHA‑пиннинг? (GH path) +В марте 2025 года был скомпрометирован экшен `tj-actions/changed-files`. Злоумышленник внёс вредоносный код в тег `v4`. Все, кто использовал `@v4`, автоматически подхватили его. Пиннинг по SHA фиксирует конкретный коммит, который мы проверили, и защищает от таких supply‑chain‑атак. + +### d) Что такое `permissions:` и какой принцип? +`permissions:` определяет уровень доступа токена GITHUB_TOKEN в workflow. Мы устанавливаем `contents: read` — только чтение кода. Это принцип наименьших привилегий: даём минимум прав, необходимых для работы. Снижает ущерб при компрометации. + +--- + +## Ответы на вопросы Task 2 + +### f) Почему кэшировать по `go.sum`, а не по build‑output? +`go.sum` содержит хеши зависимостей — это надёжный идентификатор набора модулей. Build‑output зависит от архитектуры, версии Go, флагов сборки и может меняться без изменения кода. Кэширование по `go.sum` гарантирует, что при одинаковых входных данных кэш подходит. + +### g) Что делает `fail-fast: false` и когда нужен `true`? +`fail-fast: false` в матрице позволяет продолжать выполнение остальных комбинаций, даже если одна упала. Так мы видим все ошибки (например, тесты падают только на Go 1.24). `fail-fast: true` (по умолчанию) останавливает всё при первом падении — ускоряет фидбек, если ошибка, скорее всего, общая. + +### h) Какой риск кэша, созданного вредоносным PR? +Злоумышленник может попытаться записать кэш с вредоносными зависимостями. Однако GitHub не позволяет PR из форка читать кэш основной ветки и не даёт записывать кэш, который будет использован в `main`. Кэш привязан к ветке и SHA. Это задокументировано в официальной документации GitHub. + +--- + +## Таблица времени (Task 2.4) +![alt text](test1.png) +![alt text](test2.png) +![alt text](test3.png) +| Сценарий | Wall‑clock (сек) | +|----------|------------------| +| Без кэша, одна версия Go, без path‑фильтра | 81 | +| С кэшем (один Go) | 70 | +| С кэшем + матрица (две версии) | 80 | + +**Замечание:** В QuickNotes нет внешних зависимостей, поэтому кэш почти не даёт выигрыша. Основное время уходит на установку Go и старт раннера. В реальном проекте с сотнями модулей выгода была бы значительной. + +--- + +## Бонус (если делал) +Я применил следующие дополнительные оптимизации: +1. Использовал `cache: true` с указанием `go.sum`. +2. Разделил джобы для параллельного выполнения. +3. Добавил path‑фильтр, чтобы не запускать CI при изменениях только документации. + +Дальнейшее ускорение ограничено временем старта раннера (~30 с) и установкой Go (~20 с). Достичь 90 с можно только с self‑hosted раннером, что выходит за рамки лабораторной. diff --git a/submissions/test1.png b/submissions/test1.png new file mode 100644 index 000000000..469c86d28 Binary files /dev/null and b/submissions/test1.png differ diff --git a/submissions/test2.png b/submissions/test2.png new file mode 100644 index 000000000..dd3986d60 Binary files /dev/null and b/submissions/test2.png differ diff --git a/submissions/test3.png b/submissions/test3.png new file mode 100644 index 000000000..f384f8687 Binary files /dev/null and b/submissions/test3.png differ