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
33 changes: 33 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
on:
push:
branches:
- v*

env:
GO111MODULE: "on"

jobs:
tests_by_makefile:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ^1.24

- name: Check out code
uses: actions/checkout@v3

- name: Install libvips-dev
run: |
sudo apt-get update
sudo apt-get install -y libvips-dev

- name: make lint
run: make lint

- name: make build
run: make build

- name: make test
run: make test
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

# env file
.env

.golangci.bck.yml
83 changes: 83 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
version: "2"
run:
build-tags:
- bench
- ""
tests: true
linters:
default: none
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- dupl
- durationcheck
- errorlint
- exhaustive
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- goheader
- goprintffuncname
- gosec
- govet
- importas
- ineffassign
- lll
- makezero
- misspell
- nestif
- nilerr
- noctx
- nolintlint
- prealloc
- predeclared
- revive
- staticcheck
- tagliatelle
- thelper
- unconvert
- unparam
- unused
- whitespace
settings:
funlen:
lines: 150
statements: 80
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- depguard
- dupl
- errcheck
- gocyclo
- gosec
path: _test\.go
- linters:
- depguard
path: .*\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
17 changes: 17 additions & 0 deletions .scripts/lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/zsh

sed -i.bak '/- deadcode/d' .golangci.yml
sed -i '' '/- unused/d' .golangci.yml
sed -i '' '/- structcheck/d' .golangci.yml

for d in $(ls)
do
if [[ $d == internal ]]; then
cd $d
echo "Lint ${d}..."
golangci-lint run ./...
cd ..
fi
done

mv .golangci.yml.bak .golangci.yml
16 changes: 16 additions & 0 deletions .scripts/sync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

dst=$1
if [[ ! -d "${dst}" ]]; then
echo "Usage: ./.scripts/sync.sh <destination dir>."
echo "The destination dir should exist"
exit 1
fi

GLOBIGNORE=".:..:.git"
for f in *; do
[[ -d "${dst}/${f}" ]] && [[ ! -f "${dst}/${f}/.sync" ]] && continue

echo "syncing ${f}..."
cp -R "${f}" "${dst}"
done
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Stas Demin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
BIN := "./bin/previewer"
DOCKER_IMG="previewer:develop"
DOCKER_TEST_IMG="previewer:test"

GIT_HASH := $(shell git log --format="%h" -n 1)
LDFLAGS := -X main.release="develop" -X main.buildDate=$(shell date -u +%Y-%m-%dT%H:%M:%S) -X main.gitHash=$(GIT_HASH)

build:
go build -v -o $(BIN) -ldflags "$(LDFLAGS)" ./cmd/previewer

run: build
$(BIN) -config ./configs/config.toml

build-img:
docker build \
--build-arg=LDFLAGS="$(LDFLAGS)" \
-t $(DOCKER_IMG) \
-f build/Dockerfile .

run-img: build-img
docker run $(DOCKER_IMG)

version: build
$(BIN) version

test:
go test -race ./internal/...

integration-test:
set -e ;\
docker build -t $(DOCKER_TEST_IMG) -f tests/Dockerfile .
test_status_code=0 ;\
docker run $(DOCKER_TEST_IMG) go test || test_status_code=$$? ;\
docker stop $(DOCKER_TEST_IMG) ;\
exit $$test_status_code ;

install-lint-deps:
(which golangci-lint > /dev/null) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v2.1.6

lint: install-lint-deps
golangci-lint run ./...

.PHONY: build run build-img run-img version test lint
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ТЗ на разработку сервиса "Превьювер изображений"

## Общее описание
Сервис предназначен для изготовления preview (создания изображения
с новыми размерами на основе имеющегося изображения).

#### Пример превьюшек в папке [examples](./examples/image-previewer)

## Архитектура
Сервис представляет собой web-сервер (прокси), загружающий изображения,
масштабирующий/обрезающий их до нужного формата и возвращающий пользователю.

## Основной обработчик
http://cut-service.com/fill/300/200/raw.githubusercontent.com/OtusGolang/final_project/master/examples/image-previewer/_gopher_original_1024x504.jpg

<---- микросервис ----><- размеры превью -><--------- URL исходного изображения --------------------------------->

В URL выше мы видим:
- http://cut-service.com/fill/300/200/ - endpoint нашего сервиса,
в котором 300x200 - это размеры финального изображения.
- https://raw.githubusercontent.com/OtusGolang/final_project/master/examples/image-previewer/_gopher_original_1024x504.jpg -
адрес исходного изображения; сервис должен скачать его, произвести resize, закэшировать и отдать клиенту.

Сервис должен получить URL исходного изображения, скачать его, изменить до необходимых размеров и вернуть как HTTP-ответ.

- Работаем только с HTTP.
- Ошибки удалённого сервиса или проксируем как есть, или логируем и отвечаем клиенту 502 Bad Gateway.
- Поддержка JPEG является минимальным и достаточным требованием.

**Важно**: необходимо проксировать все заголовки исходного HTTP запроса к целевому сервису (raw.githubusercontent.com в примере).

Сервис должен сохранить (кэшировать) полученное preview на локальном диске и при повторном запросе
отдавать изображение с диска, без запроса к удаленному HTTP-серверу.

Поскольку размер места для кэширования ограничен, то для удаления редко используемых изображений
необходимо использовать алгоритм **"Least Recent Used"**.

## Конфигурация
Основной параметр конфигурации сервиса - разрешенный размер LRU-кэша.

Он может измеряться как количеством закэшированных изображений, так и суммой их байт (на выбор разработчика).

## Развертывание
Развертывание микросервиса должно осуществляться командой `make run` (внутри `docker compose up`)
в директории с проектом.

## Тестирование
Реализацию алгоритма LRU нужно покрыть unit-тестами.

Для интеграционного тестирования можно использовать контейнер с Nginx в качестве удаленного HTTP-сервера,
раздающего вам заданный набор изображений.

Необходимо проверить работу сервера в разных сценариях:
* картинка найдена в кэше;
* удаленный сервер не существует;
* удаленный сервер существует, но изображение не найдено (404 Not Found);
* удаленный сервер существует, но изображение не изображение, а скажем, exe-файл;
* удаленный сервер вернул ошибку;
* удаленный сервер вернул изображение;
* изображение меньше, чем нужный размер;
и пр.

## Разбалловка
Максимум - **15 баллов**
(при условии выполнения [обязательных требований](./README.md)):

* Реализован HTTP-сервер, проксирующий запросы к удаленному серверу - 2 балла.
* Реализована нарезка изображений - 2 балла.
* Кэширование нарезанных изображений на диске - 1 балл.
* Ограничение кэша одним из способов (LRU кэш) - 1 балл.
* Прокси сервер правильно передает заголовки запроса - 1 балл.
* Написаны интеграционные тесты - 3 балла.
* Тесты адекватны и полностью покрывают функциональность - 1 балл.
* Проект возможно собрать через `make build`, запустить через `make run`
и протестировать через `make test` - 1 балл.
* Понятность и чистота кода - до 3 баллов.

#### Зачёт от 10 баллов
46 changes: 46 additions & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Собираем в гошке
FROM golang:1.24 AS build

ENV BIN_FILE=/opt/previewer/previewer-app
ENV CODE_DIR=/go/src/
ENV CC=gcc

WORKDIR ${CODE_DIR}

# Кэшируем слои с модулями
COPY go.mod .
COPY go.sum .
RUN go mod download

COPY . ${CODE_DIR}

RUN apt-get update && \
apt-get -qq install -y libvips-dev

# Собираем статический бинарник Go
ARG LDFLAGS
RUN CGO_ENABLED=1 PKG_CONFIG_PATH="/usr/lib/pkgconfig" go build \
-ldflags "$LDFLAGS" \
-o ${BIN_FILE} cmd/previewer/*

# На выходе тонкий образ
FROM build AS base

ENV CGO_ENABLED=1
ENV TARGETOS=linux
ENV TARGETARCH=amd64

LABEL ORGANIZATION="OTUS Online Education"
LABEL SERVICE="previewer"
LABEL MAINTAINERS="otdupli@gmail.com"

ENV BIN_FILE=/opt/previewer/previewer-app
COPY --from=build ${BIN_FILE} ${BIN_FILE}

ENV CONFIG_FILE=/etc/previewer/config.toml

COPY ./configs/config.toml ${CONFIG_FILE}

RUN chmod +x ${BIN_FILE}

CMD ["/opt/previewer/previewer-app", "-config", "/etc/previewer/config.toml"]
Loading