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
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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"
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml.backup
Original file line number Diff line number Diff line change
@@ -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"
36 changes: 36 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ===== СТЕЙДЖ 1: СБОРКА =====
FROM golang:1.24-alpine AS builder

WORKDIR /build

# Кешируем зависимости
COPY go.mod go.sum ./
RUN go mod download

# Копируем исходники
COPY . .

# Собираем статический бинарник
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags='-s -w' \
-trimpath \
-o quicknotes .

# ===== СТЕЙДЖ 2: РАНТАЙМ =====
FROM gcr.io/distroless/static:nonroot

WORKDIR /app

COPY --from=builder /build/quicknotes .

COPY --from=busybox:stable-musl /bin/busybox /bin/busybox

# Создаём каталог /data и даём права пользователю 65532
USER root
RUN ["/bin/busybox", "mkdir", "-p", "/data"]
RUN ["/bin/busybox", "chown", "65532:65532", "/data"]
USER 65532

EXPOSE 8080

ENTRYPOINT ["/app/quicknotes"]
Empty file added app/go.sum
Empty file.
35 changes: 35 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
services:
quicknotes:
build:
context: ./app
dockerfile: Dockerfile
image: quicknotes:lab6
ports:
- "8080:8080"
volumes:
- quicknotes-data:/data
environment:
- ADDR=:8080
- DATA_PATH=/data/notes.json
- SEED_PATH=/data/seed.json # если seed.json не нужен, оставь как есть
healthcheck:
test: ["CMD", "/bin/busybox", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped

# Бонусные параметры безопасности (все 6)
user: "65532:65532"
read_only: true
tmpfs:
- /tmp
- /run
cap_drop:
- ALL
security_opt:
- no-new-privileges:true

volumes:
quicknotes-data:
Binary file added submissions/PASS.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/branch.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/fail.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/image.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/image1.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/image10.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/image11.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/image12.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/image2.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/image3.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/image4.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/image5.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/image6.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/image7.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/image8.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/image9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions submissions/lab3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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 и старт раннера. В реальном проекте с сотнями модулей выгода была бы значительной.

Loading