Git. Задача 1a. Поиск появления строки и ветвление

Алгоритм решения:

  1. Клонируем репозиторий и переходим в него:
    git clone https://github.com/RatScanner/RatScanner.git
    cd RatScanner
  2. Ищем коммит, в котором добавилась строчка. Флаг -S (pickaxe) ищет добавление/удаление конкретной строки. Флаги --reverse и --oneline позволят увидеть самый первый коммит (где строка появилась) на самом верху:
    git log -S "<Version>3.2.2</Version>" --reverse --oneline
  3. Копируем хеш (первые 7 символов) из первой строчки вывода (допустим, это a1b2c3d).
  4. Создаем ветку от этого коммита и переключаемся на неё:
    git checkout -b fix-version-branch a1b2c3d
  5. Делаем произвольный коммит:
    touch dummy.txt
    git add dummy.txt
    git commit -m "Arbitrary commit for exam"

Git. Задача 1b. Поиск появления строки (woodpecker)

Алгоритм решения: Аналогично задаче 1a, меняются только репозиторий и строка поиска. 6. Клонирование:

git clone https://github.com/woodpecker-ci/woodpecker.git
cd woodpecker
  1. Поиск первого коммита с добавлением нужной строки:
    git log -S "//go:generate go run github.com/getkin/kin-openapi/cmd/validate ../../docs/openapi.json" --reverse --oneline
  2. Создание ветки от найденного хеша (пусть хеш x9y8z7w):
    git checkout -b add-go-generate x9y8z7w
  3. Произвольный коммит:
    echo "test" > test.md
    git add test.md
    git commit -m "My task commit"

Git. Задача 2. Удаление секретных данных (passwords.txt)

Если пароли были залиты давно и нужно “выпилить” файл из всей истории удаленного репозитория, используется утилита git filter-branch (или git filter-repo, если установлена). Стандартный подход средствами Git “из коробки”:

Шаги решения: 10. Полностью удаляем файл из истории всех веток и индексов:

git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch passwords.txt" \
  --prune-empty --tag-name-filter cat -- --all
  1. Добавляем файл в игнор, чтобы не залить снова:
echo "passwords.txt" >> .gitignore
git add .gitignore
git commit -m "Add passwords.txt to gitignore"
  1. Принудительно отправляем переписанную историю на удаленный сервер (это затрет старые коммиты с паролем на сервере):
git push origin --force --all

(Если секретный файл залили только что в последнем коммите, достаточно сделать git rm --cached passwords.txt, затем git commit --amend и git push -f).

Git. Задача 3a. ТОП-3 контрибьютор и его второй коммит

Алгоритм решения: 13. Клонируем и переходим:

git clone https://github.com/RUB-NDS/PRET.git
cd PRET
  1. Выводим список разработчиков, отсортированный по количеству коммитов:
git shortlog -sn

Смотрим на третью строчку сверху. Допустим, там разработчик с именем “Alice Smith”. 15. Ищем историю коммитов этого разработчика. Флаг --reverse покажет историю от самых старых к новым. Выводим только 2 первых коммита:

git log --author="Alice Smith" --reverse --oneline -n 2
  1. Берем хеш из второй строчки вывода (например, f4e3d2c).
  2. Создаем ветку и коммитим:
git checkout -b alice-second-commit f4e3d2c
touch task.txt && git add task.txt && git commit -m "Done"

Git. Задача 3b. ТОП-3 контрибьютор (woodpecker)

Алгоритм решения: 18. Клонируем и переходим:

git clone https://github.com/woodpecker-ci/woodpecker.git
cd woodpecker
  1. Ищем ТОП-3:
git shortlog -sn | head -n 3

Запоминаем имя третьего в списке, например “Bob”. 20. Получаем его второй по счету коммит:

git log --author="Bob" --reverse --oneline -n 2

(Берем хеш из последней строчки вывода). 21. Ветвимся и делаем коммит:

git checkout -b bob-2nd-commit <найденный_хеш>
echo "success" > success.txt
git add success.txt
git commit -m "Top 3 contributor second commit branch"

Docker. Задача 1. Оптимизация образа

Хотя ссылка недоступна, задача на оптимизацию образа классическая.

Основные принципы и шаги, которые нужно продемонстрировать в измененном Dockerfile: 22. Легковесный базовый образ: Использовать alpine или slim версии. (Заменить FROM python:3.9 на FROM python:3.9-alpine). 23. Объединение слоев (RUN): Вместо нескольких директив RUN объединить их через &&. 24. Очистка кэша: После установки пакетов удалять кэш пакетного менеджера в том же слое. 25. Многоэтапная сборка (Multi-stage build): Если код нужно компилировать (Go, C++), сборка идет в тяжелом образе builder, а готовый бинарник копируется в пустой scratch или alpine образ. 26. Файл .dockerignore: Исключить из копирования node_modules, .git, виртуальные окружения.

Пример неоптимизированного Dockerfile:

FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y python3 pip
COPY . /app
RUN pip install -r /app/requirements.txt

Пример оптимизированного Dockerfile:

FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

Docker. Задача 2. Написание Dockerfile (Python)

Алгоритм решения: 27. Создаем директорию на хосте для монтирования:

mkdir exam_docker
  1. Пишем Dockerfile:
FROM python:latest
# Установка нужных библиотек
RUN pip install --no-cache-dir matplotlib scipy
# Установка рабочей директории
WORKDIR /app
# Создание файла внутри образа
RUN echo "Hi!" > readme.txt
# Чтобы контейнер не завершился сразу, оставляем его работать (или запускаем bash)
CMD ["sleep", "infinity"]
  1. Собираем образ:
docker build -t python_exam .
  1. Важный нюанс монтирования (bind mount): Если примонтировать пустую хостовую папку ./exam_docker прямо в /app, она “перекроет” содержимое директории /app в контейнере, и созданный файл readme.txt станет невидим. Поэтому мы копируем созданный файл в подмонтированную директорию при старте:
docker run -d --name py_exam -v $(pwd)/exam_docker:/mnt_folder python_exam sh -c "cp /app/readme.txt /mnt_folder/ && sleep infinity"

Docker. Задача 3. Линтеры и чекеры Python

Алгоритм решения: 31. Пишем Dockerfile:

FROM python:3.10-slim
RUN pip install --no-cache-dir ruff bandit
WORKDIR /project
 
# Создаем директорию для отчетов
RUN mkdir -p /linters
 
# Запускаем чекеры. Используем символ ; или || true, чтобы падение одного чекера 
# не останавливало выполнение следующих команд. 
# Сохраняем в JSON и TXT форматах, а также выводим в консоль (stdout).
CMD ruff check /project --output-format json > /linters/ruff.json ; \
    ruff check /project > /linters/ruff.txt ; \
    bandit -r /project -f json -o /linters/bandit.json || true; \
    bandit -r /project > /linters/bandit.txt || true; \
    cat /linters/ruff.txt && cat /linters/bandit.txt
  1. Собираем образ:
docker build -t my_linters .
  1. Запускаем контейнер (прокидываем код проекта внутрь, а результаты вытаскиваем наружу):
# Предполагается, что код лежит в ./mycode, а отчеты хотим получить в ./reports
docker run --rm \
  -v $(pwd)/mycode:/project \
  -v $(pwd)/reports:/linters \
  my_linters

После отработки контейнер удалится (--rm), в консоли отобразятся отчеты, а в папке ./reports появятся файлы .json и .txt.

Docker. Задача 4. Царь горы (Гонка за файл)

Суть: Нужно запустить контейнеры, шарящие один и тот же volume.

Шаги решения: 34. Создаем именованный том Docker:

docker volume create arena_vol
  1. Запускаем контейнер-создатель (работает в бесконечном цикле):
docker run -d --name creator -v arena_vol:/data alpine sh -c "while true; do touch /data/delete_it.txt; done"
  1. Запускаем несколько контейнеров-удаляторов. Скрипт будет пытаться удалить файл, и если удаление успешно (файл существовал), увеличивать счетчик: Контейнер 1:
docker run -d --name deleter1 -v arena_vol:/data alpine sh -c "score=0; while true; do if rm /data/delete_it.txt 2>/dev/null; then score=\$((score+1)); echo \"Deleter1 score: \$score\"; fi; done"

Контейнер 2:

docker run -d --name deleter2 -v arena_vol:/data alpine sh -c "score=0; while true; do if rm /data/delete_it.txt 2>/dev/null; then score=\$((score+1)); echo \"Deleter2 score: \$score\"; fi; done"
  1. Чтобы посмотреть, кто побеждает, смотрим логи:
docker logs -f deleter1
docker logs -f deleter2

Compose. Задача 1. RMQ

Ссылка недоступна, но задача обычно требует поднятия брокера сообщений RabbitMQ с management-плагином для доступа к веб-интерфейсу.

Файл docker-compose.yaml:

services:
  rabbitmq:
    image: rabbitmq:3-management
    container_name: rmq_server
    ports:
      - "5672:5672"   # Порт для подключения приложений (AMQP)
      - "15672:15672" # Порт веб-интерфейса управления
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: password
    volumes:
      - rmq_data:/var/lib/rabbitmq
 
volumes:
  rmq_data:

Запуск: docker compose up -d

Compose. Задача 2. Telegram-сервис и генератор

Файл docker-compose.yaml:

services:
  sender:
    image: python:3.10-slim # Или ваш собранный образ sender_image
    container_name: tg_sender
    # Команда-заглушка для примера, в реальности тут запуск API сервера
    command: python -m http.server 8080
    environment:
      - TELEGRAM_TOKEN=123456789:AAH...
    ports:
      - "8080:8080"
 
  generator:
    image: alpine/curl # Или ваш собранный образ generator_image
    container_name: tg_generator
    # Отправка GET запроса каждые 2 секунды
    command: sh -c "while true; do curl -s http://sender:8080/send?msg=hello; sleep 2; done"
    depends_on:
      - sender

Описание взаимодействия: Docker Compose автоматически создает bridge-сеть default и подключает к ней оба контейнера. Встроенный DNS-сервер позволяет контейнеру generator обращаться к контейнеру sender напрямую по его имени в файле (хост sender, порт 8080). Проброс портов (ports: "8080:8080") нужен только для доступа снаружи (с хост-машины), для внутреннего общения генератора с сендером он не обязателен. Директива depends_on гарантирует, что generator запустится только после успешного старта контейнера sender.

Compose. Задача 3. Обожаю запах реплик по утрам

Файл docker-compose.yaml:

services:
  web:
    image: python:3.10-alpine
    # Крошечный скрипт, читающий переменную окружения и отдающий ответ (для примера)
    # В реальности здесь будет build: .
    command: sh -c 'echo -e "HTTP/1.1 200 OK\n\nHi, $USERNAME?" > response.txt && nc -lk -p 8000 -e cat response.txt'
    environment:
      - USERNAME=Examiner
    deploy:
      mode: replicated
      replicas: 5 # Поднимает 5 реплик сервиса
    ports:
      - "1501-1505:8000" # Диапазон портов для привязки реплик

Пояснение: Директива deploy.replicas: 5 говорит Compose создать 5 идентичных контейнеров. Так как нельзя привязать 5 контейнеров к одному физическому порту хоста (возникнет конфликт), мы используем синтаксис диапазонов 1501-1505:8000. Docker автоматически распределит 5 контейнеров (их внутренний порт 8000) по внешним портам хоста от 1501 до 1505 по порядку. Запуск: docker compose up -d.


Блок задач: Docker (Ubuntu + apt)

1. Сборка и запуск (Ubuntu, apt, скрипт)

Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
CMD ["sh", "scriptname.sh"]

Команды в терминале:

docker build -t my_ubuntu_app .
docker run my_ubuntu_app

2. Сборка и запуск в фоновом режиме

Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
CMD ["sh", "scriptname.sh"]

Команды в терминале: Флаг -d запускает контейнер в фоне (detached).

docker build -t my_ubuntu_app .
docker run -d my_ubuntu_app

3. Сборка, запуск и проброс портов

Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
CMD ["sh", "scriptname.sh"]

Команды в терминале: Флаг -p <хост>:<контейнер> пробрасывает порты.

docker build -t my_ubuntu_app .
docker run -p 3080:80 my_ubuntu_app

4. Сборка, запуск и монтирование папки

Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
# Скрипт будет доступен благодаря монтированию (volume)
WORKDIR /contfoldername
CMD ["sh", "scriptname.sh"]

Команды в терминале: Флаг -v <хост>:<контейнер> монтирует директорию. $(pwd) указывает на текущую папку на хосте.

docker build -t my_ubuntu_app .
docker run -v $(pwd)/foldername:/contfoldername my_ubuntu_app

5. Сборка (с COPY) и запуск

Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
COPY foldername/scriptname.sh /contfoldername/
WORKDIR /contfoldername
CMD ["sh", "scriptname.sh"]

Команды в терминале:

docker build -t my_ubuntu_app .
docker run my_ubuntu_app

6. Сборка (с COPY) и запуск в фоне

Dockerfile: (такой же, как в задаче 5)

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y pkg1 pkg2
COPY foldername/scriptname.sh /contfoldername/
WORKDIR /contfoldername
CMD ["sh", "scriptname.sh"]

Команды в терминале:

docker build -t my_ubuntu_app .
docker run -d my_ubuntu_app

7. Сборка (с COPY), запуск и проброс портов

Dockerfile: (такой же, как в задаче 5) Команды в терминале:

docker build -t my_ubuntu_app .
docker run -p 3080:80 my_ubuntu_app

8. Сборка (с COPY), запуск и монтирование (Volume)

Dockerfile: (такой же, как в задаче 5) Команды в терминале: (Нюанс: при монтировании папки с хоста поверх /contfoldername, она полностью перекроет то, что было скопировано директивой COPY во время сборки. Тем не менее, команда выглядит так):

docker build -t my_ubuntu_app .
docker run -v $(pwd)/foldername:/contfoldername my_ubuntu_app

Блок задач: Docker (Python + pip)

9. Сборка и запуск (Python, pip)

Dockerfile:

FROM python:3.7
RUN pip install pkg1 pkg2
CMD ["python", "scriptname.py"]

Команды в терминале:

docker build -t my_python_app .
docker run my_python_app

10. Сборка и запуск в фоновом режиме (Python)

Dockerfile: (такой же, как в задаче 9) Команды в терминале:

docker build -t my_python_app .
docker run -d my_python_app

11. Сборка, запуск и проброс портов (Python)

Dockerfile: (такой же, как в задаче 9) Команды в терминале:

docker build -t my_python_app .
docker run -p 3080:80 my_python_app

12. Сборка, запуск и монтирование (Python)

Dockerfile:

FROM python:3.7
RUN pip install pkg1 pkg2
WORKDIR /contfoldername
CMD ["python", "scriptname.py"]

Команды в терминале:

docker build -t my_python_app .
docker run -v $(pwd)/foldername:/contfoldername my_python_app

13. Сборка (с COPY) и запуск (Python)

Dockerfile:

FROM python:3.7
RUN pip install pkg1 pkg2
COPY foldername/scriptname.py /contfoldername/
WORKDIR /contfoldername
CMD ["python", "scriptname.py"]

Команды в терминале:

docker build -t my_python_app .
docker run my_python_app

14. Сборка (с COPY) и запуск в фоне (Python)

Dockerfile: (такой же, как в задаче 13) Команды в терминале:

docker build -t my_python_app .
docker run -d my_python_app

15. Сборка (с COPY), запуск в фоне и проброс портов (Python)

Dockerfile: (такой же, как в задаче 13) Команды в терминале: Обновляем команду запуска, добавляя флаги -d (фон) и -p (порт).

docker build -t my_python_app .
docker run -d -p 3080:80 my_python_app

16. Сборка (с COPY), запуск в фоне и монтирование (Python)

Dockerfile: (такой же, как в задаче 13) Команды в терминале: Обновляем команду запуска, добавляя флаги -d (фон) и -v (монтирование).

docker build -t my_python_app .
docker run -d -v $(pwd)/foldername:/contfoldername my_python_app

Блок задач: Git

Git 1. Инициализация, коммиты и ветвление

Алгоритм решения:

  1. Создаем папку и инициализируем репозиторий:
    mkdir rep1 && cd rep1
    git init
  2. Создаем файлы и фиксируем их в первом коммите:
    touch file1 file2
    git add file1 file2
    git commit -m "Add file1 and file2"
  3. Создаем новую ветку, переключаемся на неё и фиксируем file3:
    git checkout -b new_branch
    touch file3
    git add file3
    git commit -m "Add file3"

Git 2. Слияние веток

Алгоритм решения: Убеждаемся, что мы находимся в целевой ветке b1 (куда нужно влить изменения), и выполняем слияние ветки b2.

git checkout b1
git merge b2

(Если возникнут конфликты, Git попросит разрешить их вручную, после чего нужно будет сделать git add . и git commit).

Git 3. Клонирование, добавление и Push

Алгоритм решения: 4. Копируем удаленный проект на свой компьютер:

git clone linkname my_project
cd my_project
  1. Создаем и индексируем новые файлы:
    touch file1 file2
    git add file1 file2
    git commit -m "Add file1 and file2"
  2. Отправляем изменения ветки b1 на удаленный сервер:
    git push origin b1

Git 4. Скачивание изменений и слияние (Pull)

Алгоритм решения: Команда git pull автоматически скачивает новые коммиты с удаленного сервера (fetch) и объединяет их с твоей локальной веткой (merge).

# Простой вариант (если удаленная ветка уже связана с локальной):
git pull
 
# Явный вариант (скачать и слить изменения из удаленной ветки main):
git pull origin main

Альтернативный способ (разбитый на два шага для безопасности):

git fetch origin
git merge origin/main