mirror of
https://github.com/elyby/chrly.git
synced 2025-05-31 14:11:51 +05:30
Compare commits
25 Commits
4.0.0-alph
...
4.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33b86b809 | ||
|
|
80fa307915 | ||
|
|
2e9520db89 | ||
|
|
74564b4747 | ||
|
|
18909776a8 | ||
|
|
d1b1f22a93 | ||
|
|
cb928a3918 | ||
|
|
d9aeaba627 | ||
|
|
645f6ac694 | ||
|
|
eab7c6ecaa | ||
|
|
ac714de8df | ||
|
|
8007b082d6 | ||
|
|
9cb6502f9c | ||
|
|
76a3f3ad26 | ||
|
|
bdd7c5e15e | ||
|
|
340b24d862 | ||
|
|
cf99a0eab2 | ||
|
|
a661f9aac3 | ||
|
|
0d41f0c347 | ||
|
|
1a906cfc09 | ||
|
|
8b51c1bd0c | ||
|
|
73205648d2 | ||
|
|
58a1c6ec33 | ||
|
|
6a54af62aa | ||
|
|
45007ba1c5 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -2,8 +2,14 @@
|
|||||||
/.idea
|
/.idea
|
||||||
|
|
||||||
# Docker Compose file
|
# Docker Compose file
|
||||||
docker-compose.yml
|
/docker-compose.yml
|
||||||
docker-compose.override.yml
|
/docker-compose.override.yml
|
||||||
|
|
||||||
# vendor
|
# vendor
|
||||||
vendor
|
/vendor
|
||||||
|
|
||||||
|
# Cover output
|
||||||
|
.cover
|
||||||
|
|
||||||
|
# Local config
|
||||||
|
/config.yml
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ variables:
|
|||||||
CONTAINER_IMAGE: registry.ely.by/elyby/skinsystem
|
CONTAINER_IMAGE: registry.ely.by/elyby/skinsystem
|
||||||
|
|
||||||
.golang_template: &setup_go_environment
|
.golang_template: &setup_go_environment
|
||||||
image: golang:1.8.3-stretch
|
image: golang:1.9.0-alpine3.6
|
||||||
before_script:
|
before_script:
|
||||||
|
- apk add --no-cache git
|
||||||
- mkdir -p $GOPATH/src/$CI_PROJECT_NAMESPACE
|
- mkdir -p $GOPATH/src/$CI_PROJECT_NAMESPACE
|
||||||
- cp -r $(pwd) $GOPATH/src/$CI_PROJECT_PATH
|
- cp -r $(pwd) $GOPATH/src/$CI_PROJECT_PATH
|
||||||
- cd $GOPATH/src/$CI_PROJECT_PATH
|
- cd $GOPATH/src/$CI_PROJECT_PATH
|
||||||
@@ -25,7 +26,7 @@ variables:
|
|||||||
.docker_template: &setup_docker_environment
|
.docker_template: &setup_docker_environment
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
before_script:
|
before_script:
|
||||||
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
|
- docker login -u gitlab-ci -p $CI_JOB_TOKEN registry.ely.by
|
||||||
- export TEMP_IMAGE_NAME="$CONTAINER_IMAGE:$CI_PIPELINE_ID"
|
- export TEMP_IMAGE_NAME="$CONTAINER_IMAGE:$CI_PIPELINE_ID"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@@ -38,7 +39,13 @@ build executable:
|
|||||||
<<: *setup_go_environment
|
<<: *setup_go_environment
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- env GOOS=linux go build -o $CI_PROJECT_DIR/minecraft-skinsystem main.go
|
- export VERSION="${CI_COMMIT_TAG:-dev-$CI_COMMIT_REF_NAME-${CI_COMMIT_SHA:0:8}+build-$CI_JOB_ID}"
|
||||||
|
- >
|
||||||
|
env GOOS=linux
|
||||||
|
go build
|
||||||
|
-o $CI_PROJECT_DIR/minecraft-skinsystem
|
||||||
|
-ldflags "-X ${CI_PROJECT_PATH}/bootstrap.version=${VERSION}"
|
||||||
|
main.go
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "${CI_JOB_STAGE} executable"
|
name: "${CI_JOB_STAGE} executable"
|
||||||
paths:
|
paths:
|
||||||
@@ -49,7 +56,7 @@ build docker image:
|
|||||||
<<: *setup_docker_environment
|
<<: *setup_docker_environment
|
||||||
stage: build_docker_image
|
stage: build_docker_image
|
||||||
script:
|
script:
|
||||||
- docker build -t $TEMP_IMAGE_NAME .
|
- docker build -t $TEMP_IMAGE_NAME -f docker/Dockerfile .
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
- develop
|
- develop
|
||||||
@@ -72,7 +79,7 @@ push tag:
|
|||||||
variables:
|
variables:
|
||||||
GIT_STRATEGY: none
|
GIT_STRATEGY: none
|
||||||
script:
|
script:
|
||||||
- export IMAGE_NAME="$CONTAINER_IMAGE:$CI_BUILD_TAG"
|
- export IMAGE_NAME="$CONTAINER_IMAGE:$CI_COMMIT_TAG"
|
||||||
- export LATEST_IMAGE_NAME="$CONTAINER_IMAGE:latest"
|
- export LATEST_IMAGE_NAME="$CONTAINER_IMAGE:latest"
|
||||||
- docker tag $TEMP_IMAGE_NAME $IMAGE_NAME
|
- docker tag $TEMP_IMAGE_NAME $IMAGE_NAME
|
||||||
- docker tag $TEMP_IMAGE_NAME $LATEST_IMAGE_NAME
|
- docker tag $TEMP_IMAGE_NAME $LATEST_IMAGE_NAME
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
FROM scratch
|
|
||||||
|
|
||||||
COPY ./minecraft-skinsystem /app/
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/minecraft-skinsystem"]
|
|
||||||
CMD ["serve"]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
FROM golang:1.9-alpine
|
|
||||||
|
|
||||||
RUN mkdir -p /go/src/elyby/minecraft-skinsystem \
|
|
||||||
/go/src/elyby/minecraft-skinsystem/data/capes \
|
|
||||||
&& ln -s /go/src/elyby/minecraft-skinsystem /go/src/app
|
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
|
||||||
|
|
||||||
COPY ./ /go/src/app/
|
|
||||||
|
|
||||||
RUN apk add --no-cache git \
|
|
||||||
&& go get -u github.com/golang/dep/cmd/dep \
|
|
||||||
&& dep ensure \
|
|
||||||
&& go clean -i github.com/golang/dep \
|
|
||||||
&& rm -rf $GOPATH/src/github.com/golang/dep \
|
|
||||||
&& apk del git \
|
|
||||||
&& go build main.go \
|
|
||||||
&& mv main /usr/local/bin/minecraft-skinsystem
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
ENTRYPOINT ["minecraft-skinsystem"]
|
|
||||||
CMD ["serve"]
|
|
||||||
36
Gopkg.lock
generated
36
Gopkg.lock
generated
@@ -1,6 +1,18 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/assembla/cony"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "dd62697b0adb9adfda8589520cb85f4cbc2361f1"
|
||||||
|
version = "v0.3.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/certifi/gocertifi"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3fd9e1adb12b72d2f3f82191d49be9b93c69f67c"
|
||||||
|
version = "2017.07.27"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
packages = ["spew"]
|
packages = ["spew"]
|
||||||
@@ -13,6 +25,12 @@
|
|||||||
revision = "629574ca2a5df945712d3079857300b5e4da0236"
|
revision = "629574ca2a5df945712d3079857300b5e4da0236"
|
||||||
version = "v1.4.2"
|
version = "v1.4.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/getsentry/raven-go"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "d175f85701dfbf44cb0510114c9943e665e60907"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/golang/mock"
|
name = "github.com/golang/mock"
|
||||||
packages = ["gomock"]
|
packages = ["gomock"]
|
||||||
@@ -35,7 +53,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/hashicorp/hcl"
|
name = "github.com/hashicorp/hcl"
|
||||||
packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
|
packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
|
||||||
revision = "392dba7d905ed5d04a5794ba89f558b27e2ba1ca"
|
revision = "8f6b1344a92ff8877cf24a5de9177bf7d0a2a187"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
@@ -53,7 +71,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/mediocregopher/radix.v2"
|
name = "github.com/mediocregopher/radix.v2"
|
||||||
packages = ["cluster","pool","redis","util"]
|
packages = ["cluster","pool","redis","util"]
|
||||||
revision = "ae7309086d191442b36bf69f7f5eeca5fdbd329e"
|
revision = "d234cfb904a91daafa4e1f92599a893b349cc0c2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@@ -64,7 +82,7 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/mono83/slf"
|
name = "github.com/mono83/slf"
|
||||||
packages = [".","params","rays","recievers","recievers/ansi","recievers/statsd","wd"]
|
packages = [".","filters","params","rays","recievers","recievers/ansi","recievers/statsd","wd"]
|
||||||
revision = "8188a95c8d6b74c43953abb38b8bd6fdbc412ff5"
|
revision = "8188a95c8d6b74c43953abb38b8bd6fdbc412ff5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
@@ -95,7 +113,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/spf13/afero"
|
name = "github.com/spf13/afero"
|
||||||
packages = [".","mem"]
|
packages = [".","mem"]
|
||||||
revision = "9be650865eab0c12963d8753212f4f9c66cdcf12"
|
revision = "ee1bd8ee15a1306d1f9201acc41ef39cd9f99a1b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/cast"
|
name = "github.com/spf13/cast"
|
||||||
@@ -107,13 +125,13 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "4a7b7e65864c064d48dce82efbbfed2bdc0bf2aa"
|
revision = "3c0b56b677e04926dfa835a1b3f11cd4f62f076e"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/spf13/jwalterweatherman"
|
name = "github.com/spf13/jwalterweatherman"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "0efa5202c04663c757d84f90f5219c1250baf94f"
|
revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/pflag"
|
name = "github.com/spf13/pflag"
|
||||||
@@ -143,13 +161,13 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix"]
|
||||||
revision = "07c182904dbd53199946ba614a412c61d3c548f5"
|
revision = "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/text"
|
name = "golang.org/x/text"
|
||||||
packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"]
|
packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"]
|
||||||
revision = "e56139fd9c5bc7244c76116c68e500765bb6db6b"
|
revision = "bd91bbf73e9a4a801adbfb97133c992678533126"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "gopkg.in/h2non/gock.v1"
|
name = "gopkg.in/h2non/gock.v1"
|
||||||
@@ -166,6 +184,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "75c18193e3b7f0ae90987dc2e69a73c08c4329ab0ea3839112c05d45564d09c3"
|
inputs-digest = "dd545fafc23f9b6429b5b679ad5c213c14c819f1e4ea381823acf338651122e1"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ ignored = ["elyby/minecraft-skinsystem"]
|
|||||||
name = "github.com/spf13/viper"
|
name = "github.com/spf13/viper"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/streadway/amqp"
|
name = "github.com/getsentry/raven-go"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/assembla/cony"
|
||||||
|
version = "^0.3.2"
|
||||||
|
|
||||||
# Testing dependencies
|
# Testing dependencies
|
||||||
|
|
||||||
|
|||||||
91
README.md
91
README.md
@@ -1,61 +1,74 @@
|
|||||||
# Это заготовка для нормального файла
|
# Ely.by Minecraft Skinsystem
|
||||||
|
|
||||||
Для настройки Dev-окружения нужно склонировать проект в удобное место,
|
Реализация API системы скинов для Minecraft v4.
|
||||||
за тем сделать символьную ссылку в свой GOPATH:
|
|
||||||
|
|
||||||
```sh
|
## Config
|
||||||
# Выполнять, находясь внутри директории репозитория
|
|
||||||
mkdir -p $GOPATH/src/elyby
|
|
||||||
ln -s $PWD $GOPATH/src/elyby/minecraft-skinsystem
|
|
||||||
```
|
|
||||||
|
|
||||||
Или можно склонировать репозиторий сразу в нужную локацию:
|
Конфигурация может задаваться посредством любого из перечисленных форматов файлов: JSON, TOML, YAML, HCL и
|
||||||
|
Java properties. Кроме того, параметры конфигурации могут перезаписываться доступными при запуске программы
|
||||||
|
ENV переменными.
|
||||||
|
|
||||||
```sh
|
> **Заметка**: ENV переменные именуются как KEY.SUBKEY.SUBSUBKEY, т.е. все символы должны быть заглавными,
|
||||||
git clone git@bitbucket.org:elyby/minecraft-skinsystem.git $GOPATH/src/elyby/minecraft-skinsystem
|
а точки должны отделять уровень вложенности.
|
||||||
```
|
|
||||||
|
|
||||||
Нужно скопировать правильный docker-compose файл для желаемого окружения:
|
Пример файла конфигурации находится в [config.dist.yml](config.dist.yml). Внутри dist-файла есть комментарии,
|
||||||
|
поясняющие назначение тех или иных параметров. Для работы его следует скопировать в локальный `config.yml`
|
||||||
|
и отредактировать под свои нужды.
|
||||||
|
|
||||||
```sh
|
## Развёртывание
|
||||||
cp docker-compose.dev.yml docker-compose.yml # dev env
|
|
||||||
cp docker-compose.prod.yml docker-compose.yml # prod env
|
|
||||||
```
|
|
||||||
|
|
||||||
И за тем всё это поднять:
|
Деплоить проект можно двумя способами:
|
||||||
|
|
||||||
|
1. Скомпилировав и запустив бинарный файл, а также обеспечив ему доступ ко всем необходмым сервисам.
|
||||||
|
|
||||||
|
2. Используя Docker и docker-compose.
|
||||||
|
|
||||||
|
*Первый случай не буду описывать, т.к. долго, мучительно и никто так делать не будет, я гарантирую это*,
|
||||||
|
поэтому перейдём сразу ко второму.
|
||||||
|
|
||||||
|
Прежде всего необходимо установить [Docker](https://docs.docker.com/engine/installation/) и
|
||||||
|
[docker-compose](https://docs.docker.com/compose/install/).
|
||||||
|
|
||||||
|
Для запуска последней версии проекта достаточно скопировать содержимое файла
|
||||||
|
[docker/docker-compose.prod.yml](docker/docker-compose.prod.yml) в файл `docker-compose.yml` непосредственно
|
||||||
|
на месте установки, после чего ввести в консоль команду:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Если нужно пересобрать весь контейнер, то выполняем следующее:
|
Web-приложение, amqp worker и все сопутствующие сервисы будут автоматически запущены. Данные из контейнеров
|
||||||
|
будут синхронизироваться в папку `data`.
|
||||||
|
|
||||||
```
|
## Разработка
|
||||||
docker-compose stop app # Останавливаем конейтнер, если он ещё работает
|
|
||||||
docker-compose rm -f app # Удаляем конейтнер
|
|
||||||
docker-compose build app # Запускаем билд по новой
|
|
||||||
docker-compose up -d app # Поднимаем свежесобранный контейнер обратно
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шорткаты для разработки
|
Перво-наперво необходимо [установить последнюю версию Go](https://golang.org/doc/install) и сконфигурировать
|
||||||
|
переменную окружения GOPATH, а также установить инструмент контроля версий [dep](https://github.com/golang/dep).
|
||||||
|
|
||||||
Потом это надо преобразовать в нормальные доки.
|
Затем можно склонировать репозиторий хитрым способом, чтобы удовлетворить все прекрасные особенности Go:
|
||||||
|
|
||||||
Run Redis:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run --rm \
|
# Сперва создадим подпапку для приватных Go проектов Ely.by
|
||||||
-p 6379:6379 \
|
mkdir -p $GOPATH/src/elyby
|
||||||
redis:3.0-alpine
|
# Затем непосредственно клинируем репозиторий туда, где его ожидает увидеть Go
|
||||||
|
git clone git@gitlab.ely.by:elyby/minecraft-skinsystem.git $GOPATH/src/elyby/minecraft-skinsystem
|
||||||
|
# Переходим в папку проекта
|
||||||
|
cd $GOPATH/src/elyby/minecraft-skinsystem
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
dep ensure
|
||||||
```
|
```
|
||||||
|
|
||||||
Run RabbitMQ:
|
Чтобы запустить проект достаточно написать `go run main.go`, но без файла конфигурации и Redis
|
||||||
|
программа долго не проработает. Поэтому сперва копируем `config.dist.yml` в `config.yml` и, при необходимости,
|
||||||
|
затачиваем его под себя.
|
||||||
|
|
||||||
|
Redis можно установить в систему самостоятельно, но гораздо удобнее воспользоваться готовыми сервисами,
|
||||||
|
описанными в [docker/docker-compose.dev.yml](docker/docker-compose.dev.yml). Для этого просто копируем
|
||||||
|
`docker-compose.dev.yml` и поднимаем сервисы:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run --rm \
|
cp docker/docker-compose.dev.yml docker-compose.yml
|
||||||
-p 5672:5672 \
|
docker-compose up -d
|
||||||
-e RABBITMQ_DEFAULT_USER=ely-skinsystem-app \
|
|
||||||
-e RABBITMQ_DEFAULT_PASS=ely-skinsystem-app-password \
|
|
||||||
-e RABBITMQ_DEFAULT_VHOST=/ely \
|
|
||||||
rabbitmq:3.6
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
После этого `go run main.go serve` должен запустить web-сервер без дополнительной модификации файла конфигурации.
|
||||||
|
|||||||
@@ -5,15 +5,27 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/assembla/cony"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
"github.com/mono83/slf/rays"
|
"github.com/mono83/slf/rays"
|
||||||
"github.com/mono83/slf/recievers/ansi"
|
|
||||||
"github.com/mono83/slf/recievers/statsd"
|
"github.com/mono83/slf/recievers/statsd"
|
||||||
|
"github.com/mono83/slf/recievers/writer"
|
||||||
"github.com/mono83/slf/wd"
|
"github.com/mono83/slf/wd"
|
||||||
"github.com/streadway/amqp"
|
|
||||||
|
"elyby/minecraft-skinsystem/logger/receivers/sentry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateLogger(statsdAddr string) (wd.Watchdog, error) {
|
var version = ""
|
||||||
wd.AddReceiver(ansi.New(true, true, false))
|
|
||||||
|
func GetVersion() string {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLogger(statsdAddr string, sentryAddr string) (wd.Watchdog, error) {
|
||||||
|
wd.AddReceiver(writer.New(writer.Options{
|
||||||
|
Marker: false,
|
||||||
|
TimeFormat: "15:04:05.000",
|
||||||
|
}))
|
||||||
if statsdAddr != "" {
|
if statsdAddr != "" {
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
statsdReceiver, err := statsd.NewReceiver(statsd.Config{
|
statsdReceiver, err := statsd.NewReceiver(statsd.Config{
|
||||||
@@ -29,6 +41,29 @@ func CreateLogger(statsdAddr string) (wd.Watchdog, error) {
|
|||||||
wd.AddReceiver(statsdReceiver)
|
wd.AddReceiver(statsdReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sentryAddr != "" {
|
||||||
|
ravenClient, err := raven.New(sentryAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ravenClient.SetEnvironment("production")
|
||||||
|
ravenClient.SetDefaultLoggerName("sentry-watchdog-receiver")
|
||||||
|
programVersion := GetVersion()
|
||||||
|
if programVersion != "" {
|
||||||
|
raven.SetRelease(programVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
sentryReceiver, err := sentry.NewReceiverWithCustomRaven(ravenClient, &sentry.Config{
|
||||||
|
MinLevel: "warn",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wd.AddReceiver(sentryReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
return wd.New("", "").WithParams(rays.Host), nil
|
return wd.New("", "").WithParams(rays.Host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +75,7 @@ type RabbitMQConfig struct {
|
|||||||
Vhost string
|
Vhost string
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateRabbitMQChannel(config *RabbitMQConfig) (*amqp.Channel, error) {
|
func CreateRabbitMQClient(config *RabbitMQConfig) *cony.Client {
|
||||||
addr := fmt.Sprintf(
|
addr := fmt.Sprintf(
|
||||||
"amqp://%s:%s@%s:%d/%s",
|
"amqp://%s:%s@%s:%d/%s",
|
||||||
config.Username,
|
config.Username,
|
||||||
@@ -50,15 +85,7 @@ func CreateRabbitMQChannel(config *RabbitMQConfig) (*amqp.Channel, error) {
|
|||||||
url.PathEscape(config.Vhost),
|
url.PathEscape(config.Vhost),
|
||||||
)
|
)
|
||||||
|
|
||||||
rabbitConnection, err := amqp.Dial(addr)
|
client := cony.NewClient(cony.URL(addr), cony.Backoff(cony.DefaultBackoff))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rabbitChannel, err := rabbitConnection.Channel()
|
return client
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rabbitChannel, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var amqpWorkerCmd = &cobra.Command{
|
|||||||
Use: "amqp-worker",
|
Use: "amqp-worker",
|
||||||
Short: "Launches a worker which listens to events and processes them",
|
Short: "Launches a worker which listens to events and processes them",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"))
|
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"), viper.GetString("sentry.dsn"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
||||||
}
|
}
|
||||||
@@ -33,19 +33,14 @@ var amqpWorkerCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
logger.Info("Skins repository successfully initialized")
|
logger.Info("Skins repository successfully initialized")
|
||||||
|
|
||||||
logger.Info("Initializing AMQP connection")
|
logger.Info("Creating AMQP client")
|
||||||
amqpChannel, err := bootstrap.CreateRabbitMQChannel(&bootstrap.RabbitMQConfig{
|
amqpClient := bootstrap.CreateRabbitMQClient(&bootstrap.RabbitMQConfig{
|
||||||
Host: viper.GetString("amqp.host"),
|
Host: viper.GetString("amqp.host"),
|
||||||
Port: viper.GetInt("amqp.port"),
|
Port: viper.GetInt("amqp.port"),
|
||||||
Username: viper.GetString("amqp.username"),
|
Username: viper.GetString("amqp.username"),
|
||||||
Password: viper.GetString("amqp.password"),
|
Password: viper.GetString("amqp.password"),
|
||||||
Vhost: viper.GetString("amqp.vhost"),
|
Vhost: viper.GetString("amqp.vhost"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
logger.Emergency(fmt.Sprintf("Error on connecting AMQP: %+v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Info("AMQP connection successfully initialized")
|
|
||||||
|
|
||||||
accountsApi := (&accounts.Config{
|
accountsApi := (&accounts.Config{
|
||||||
Addr: viper.GetString("api.accounts.host"),
|
Addr: viper.GetString("api.accounts.host"),
|
||||||
@@ -56,7 +51,7 @@ var amqpWorkerCmd = &cobra.Command{
|
|||||||
|
|
||||||
services := &worker.Services{
|
services := &worker.Services{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Channel: amqpChannel,
|
AmqpClient: amqpClient,
|
||||||
SkinsRepo: skinsRepo,
|
SkinsRepo: skinsRepo,
|
||||||
AccountsAPI: accountsApi,
|
AccountsAPI: accountsApi,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func init() {
|
|||||||
func initConfig() {
|
func initConfig() {
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
viper.SetConfigFile(cfgFile)
|
viper.SetConfigFile(cfgFile)
|
||||||
|
} else {
|
||||||
|
viper.SetConfigName("config")
|
||||||
|
viper.AddConfigPath("/etc/minecraft-skinsystem")
|
||||||
|
viper.AddConfigPath(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var serveCmd = &cobra.Command{
|
|||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Runs the system server skins",
|
Short: "Runs the system server skins",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"))
|
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"), viper.GetString("sentry.dsn"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
23
cmd/version.go
Normal file
23
cmd/version.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"elyby/minecraft-skinsystem/bootstrap"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Show the Minecraft Skinsystem version information",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Printf("Version: %s\n", bootstrap.GetVersion())
|
||||||
|
fmt.Printf("Go version: %s\n", runtime.Version())
|
||||||
|
fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(versionCmd)
|
||||||
|
}
|
||||||
51
config.dist.yml
Normal file
51
config.dist.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Main server configuration. Actually you don't want to change it,
|
||||||
|
# but you able to change host or port, that will be used by serve command
|
||||||
|
server:
|
||||||
|
host: localhost
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
# Worker listen to AMQP events, so it should know how to connect to any
|
||||||
|
# AMQP provider (actually RabbitMQ). You should not escape any vhost
|
||||||
|
# characters, 'cause it will be done by application automatically
|
||||||
|
amqp:
|
||||||
|
host: localhost
|
||||||
|
port: 5672
|
||||||
|
username: amqp-user
|
||||||
|
password: amqp-password
|
||||||
|
vhost: /
|
||||||
|
|
||||||
|
# Both of web or worker depends on storage.
|
||||||
|
storage:
|
||||||
|
# For now app require Redis and don't support any other backends to store
|
||||||
|
# skins, but in the future we can have more backends. Poll size tune amount
|
||||||
|
# of connections to the redis. It's not recommended to set it less then 2
|
||||||
|
# because it will lead to panic on high load.
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
poolSize: 10
|
||||||
|
|
||||||
|
# Filesystem storage used to store capes. basePath specify absolute or relative
|
||||||
|
# path to storage and capesDirName specify which folder in this base path will
|
||||||
|
# be used to search capes.
|
||||||
|
filesystem:
|
||||||
|
basePath: data
|
||||||
|
capesDirName: capes
|
||||||
|
|
||||||
|
# Accounts Ely.by internal API will be used in cases, when by some reasons
|
||||||
|
# information about user will be unavailable in the app storage.
|
||||||
|
api:
|
||||||
|
accounts:
|
||||||
|
host: https://account.ely.by
|
||||||
|
id: app-id
|
||||||
|
secret: secret
|
||||||
|
scopes:
|
||||||
|
- internal_account_info
|
||||||
|
|
||||||
|
# StatsD can be used to collect metrics
|
||||||
|
# statsd:
|
||||||
|
# addr: localhost:3746
|
||||||
|
|
||||||
|
# Sentry can be used to collect app errors
|
||||||
|
# sentry:
|
||||||
|
# dsn: "https://public:private@your.sentry.io/1"
|
||||||
2
data/statsd/.gitignore
vendored
2
data/statsd/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
services:
|
|
||||||
redis:
|
|
||||||
image: redis:3.2-32bit
|
|
||||||
volumes:
|
|
||||||
- ./data/redis:/data
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:3.6
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: "ely-skinsystem-app"
|
|
||||||
RABBITMQ_DEFAULT_PASS: "ely-skinsystem-app-password"
|
|
||||||
RABBITMQ_DEFAULT_VHOST: "/ely"
|
|
||||||
|
|
||||||
statsd:
|
|
||||||
image: hopsoft/graphite-statsd
|
|
||||||
volumes:
|
|
||||||
- ./data/statsd:/opt/graphite/storage
|
|
||||||
- ./data/graphite-config:/opt/graphite/conf
|
|
||||||
- ./data/statsd-config/config.json:/opt/statsd/config.js
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build: .
|
|
||||||
image: registry.ely.by/elyby/skinsystem:latest
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
volumes:
|
|
||||||
- ./:/go/src/app
|
|
||||||
command: ["go", "run", "minecraft-skinsystem.go"]
|
|
||||||
links:
|
|
||||||
- redis
|
|
||||||
- rabbitmq
|
|
||||||
- statsd
|
|
||||||
environment:
|
|
||||||
ACCOUNTS_API_ID: ""
|
|
||||||
ACCOUNTS_API_SECRET: ""
|
|
||||||
STATSD_ADDR: ""
|
|
||||||
|
|
||||||
redis:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.base.yml
|
|
||||||
service: redis
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.base.yml
|
|
||||||
service: rabbitmq
|
|
||||||
|
|
||||||
statsd:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.base.yml
|
|
||||||
service: statsd
|
|
||||||
ports:
|
|
||||||
- "8123:80"
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: registry.ely.by/elyby/skinsystem:latest
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
links:
|
|
||||||
- redis
|
|
||||||
- rabbitmq
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
ACCOUNTS_API_ID: ""
|
|
||||||
ACCOUNTS_API_SECRET: ""
|
|
||||||
STATSD_ADDR: ""
|
|
||||||
|
|
||||||
redis:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.base.yml
|
|
||||||
service: redis
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.base.yml
|
|
||||||
service: rabbitmq
|
|
||||||
restart: always
|
|
||||||
13
docker/Dockerfile
Normal file
13
docker/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM alpine:3.6
|
||||||
|
|
||||||
|
RUN apk --update add ca-certificates \
|
||||||
|
&& update-ca-certificates \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
COPY docker/docker-entrypoint.sh /usr/local/bin/
|
||||||
|
COPY docker/config.dist.yml /usr/local/etc/minecraft-skinsystem/
|
||||||
|
|
||||||
|
COPY minecraft-skinsystem /usr/local/bin/
|
||||||
|
|
||||||
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
CMD ["serve"]
|
||||||
51
docker/config.dist.yml
Normal file
51
docker/config.dist.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Main server configuration. Actually you don't want to change it,
|
||||||
|
# but you able to change host or port, that will be used by serve command
|
||||||
|
server:
|
||||||
|
host: # leave host empty to allow Docker publish port
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
# Worker listen to AMQP events, so it should know how to connect to any
|
||||||
|
# AMQP provider (actually RabbitMQ). You should not escape any vhost
|
||||||
|
# characters, 'cause it will be done by application automatically
|
||||||
|
amqp:
|
||||||
|
host: rabbitmq
|
||||||
|
port: 5672
|
||||||
|
username: minecraft-skinsystem-app
|
||||||
|
password: minecraft-skinsystem-app-password
|
||||||
|
vhost: /
|
||||||
|
|
||||||
|
# Both of web or worker depends on storage.
|
||||||
|
storage:
|
||||||
|
# For now app require Redis and don't support any other backends to store
|
||||||
|
# skins, but in the future we can have more backends. Poll size tune amount
|
||||||
|
# of connections to the redis. It's not recommended to set it less then 2
|
||||||
|
# because it will lead to panic on high load.
|
||||||
|
redis:
|
||||||
|
host: redis
|
||||||
|
port: 6379
|
||||||
|
poolSize: 10
|
||||||
|
|
||||||
|
# Filesystem storage used to store capes. basePath specify absolute or relative
|
||||||
|
# path to storage and capesDirName specify which folder in this base path will
|
||||||
|
# be used to search capes.
|
||||||
|
filesystem:
|
||||||
|
basePath: /data
|
||||||
|
capesDirName: capes
|
||||||
|
|
||||||
|
# Accounts Ely.by internal API will be used in cases, when by some reasons
|
||||||
|
# information about user will be unavailable in the app storage.
|
||||||
|
api:
|
||||||
|
accounts:
|
||||||
|
host: https://account.ely.by
|
||||||
|
id: app-id
|
||||||
|
secret: secret
|
||||||
|
scopes:
|
||||||
|
- internal_account_info
|
||||||
|
|
||||||
|
# StatsD can be used to collect metrics
|
||||||
|
# statsd:
|
||||||
|
# addr: localhost:3746
|
||||||
|
|
||||||
|
# Sentry can be used to collect app errors
|
||||||
|
# sentry:
|
||||||
|
# dsn: https://public:private@your.sentry.io/1
|
||||||
46
docker/docker-compose.dev.yml
Normal file
46
docker/docker-compose.dev.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# This compose file contains necessary docker-compose config to quick start
|
||||||
|
# services required by app. Ports published to host.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. Clone this file as docker-compose.yml:
|
||||||
|
# cp docker/docker-compose.dev.yml docker-compose.yml
|
||||||
|
#
|
||||||
|
# 2. If necessary, then you can fix configuration to your environment.
|
||||||
|
# Then start all services:
|
||||||
|
# docker-compose up -d
|
||||||
|
#
|
||||||
|
# 3. Pass to the project configuration links to this services:
|
||||||
|
# amqp:
|
||||||
|
# host: localhost
|
||||||
|
# port: 5672
|
||||||
|
# username: ely
|
||||||
|
# password: ely
|
||||||
|
# vhost: /ely
|
||||||
|
#
|
||||||
|
# storage:
|
||||||
|
# redis:
|
||||||
|
# host: localhost
|
||||||
|
# port: 6379
|
||||||
|
# poolSize: 10
|
||||||
|
#
|
||||||
|
# 4. After job is done all services can be stopped:
|
||||||
|
# docker-compose stop
|
||||||
|
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:3.2-32bit
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- ./data/redis:/data
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:3.6-management-alpine
|
||||||
|
ports:
|
||||||
|
- "5672:5672"
|
||||||
|
- "15672:15672"
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: "ely"
|
||||||
|
RABBITMQ_DEFAULT_PASS: "ely"
|
||||||
|
RABBITMQ_DEFAULT_VHOST: "/ely"
|
||||||
36
docker/docker-compose.prod.yml
Normal file
36
docker/docker-compose.prod.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: registry.ely.by/elyby/skinsystem:latest
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ./data/capes:/data/capes
|
||||||
|
- ./config/minecraft-skinsystem:/etc/minecraft-skinsystem
|
||||||
|
|
||||||
|
worker:
|
||||||
|
image: registry.ely.by/elyby/skinsystem:latest
|
||||||
|
restart: always
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
- rabbitmq
|
||||||
|
command: ["amqp-worker"]
|
||||||
|
volumes:
|
||||||
|
- ./config/minecraft-skinsystem:/etc/minecraft-skinsystem
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:3.2-32bit # 32-bit version used to decrease memory usage
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./data/redis:/data
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:3.6-alpine
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: minecraft-skinsystem-app
|
||||||
|
RABBITMQ_DEFAULT_PASS: minecraft-skinsystem-app-password
|
||||||
|
RABBITMQ_DEFAULT_VHOST: /
|
||||||
15
docker/docker-entrypoint.sh
Executable file
15
docker/docker-entrypoint.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CONFIG="/etc/minecraft-skinsystem/config.yml"
|
||||||
|
|
||||||
|
if [ ! -f "$CONFIG" ]; then
|
||||||
|
mkdir -p $(dirname "${CONFIG}")
|
||||||
|
cp /usr/local/etc/minecraft-skinsystem/config.dist.yml "$CONFIG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "serve" ] || [ "$1" = "amqp-worker" ]; then
|
||||||
|
set -- minecraft-skinsystem "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
132
logger/receivers/sentry/receiver.go
Normal file
132
logger/receivers/sentry/receiver.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/mono83/slf"
|
||||||
|
"github.com/mono83/slf/filters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds information for filtered receiver
|
||||||
|
type Config struct {
|
||||||
|
MinLevel string
|
||||||
|
ParamsWhiteList []string
|
||||||
|
ParamsBlackList []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReceiver allows you to create a new receiver in the Sentry
|
||||||
|
// using the fastest and easiest way.
|
||||||
|
// The Config parameter can be passed as nil if you do not need additional filtration.
|
||||||
|
func NewReceiver(dsn string, cfg *Config) (slf.Receiver, error) {
|
||||||
|
client, err := raven.New(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewReceiverWithCustomRaven(client, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReceiverWithCustomRaven allows you to create a new receiver in the Sentry
|
||||||
|
// configuring raven.Client by yourself. This can be useful if you need to set
|
||||||
|
// additional parameters, such as release and environment, that will be sent
|
||||||
|
// with each Packet in the Sentry:
|
||||||
|
//
|
||||||
|
// client, err := raven.New("https://some:sentry@dsn.sentry.io/1")
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client.SetRelease("1.3.2")
|
||||||
|
// client.SetEnvironment("production")
|
||||||
|
// client.SetDefaultLoggerName("sentry-watchdog-receiver")
|
||||||
|
//
|
||||||
|
// sentryReceiver, err := sentry.NewReceiverWithCustomRaven(client, &sentry.Config{
|
||||||
|
// MinLevel: "warn",
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// The Config parameter allows you to add additional filtering, such as the minimum
|
||||||
|
// message level and the exclusion of private parameters. If you do not need additional
|
||||||
|
// filtering, nil can passed.
|
||||||
|
func NewReceiverWithCustomRaven(client *raven.Client, cfg *Config) (slf.Receiver, error) {
|
||||||
|
out, err := buildReceiverForClient(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolving level
|
||||||
|
level, ok := slf.ParseType(cfg.MinLevel)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Unknown level %s", cfg.MinLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.ParamsWhiteList) > 0 {
|
||||||
|
out.filter = slf.NewWhiteListParamsFilter(cfg.ParamsWhiteList)
|
||||||
|
} else {
|
||||||
|
out.filter = slf.NewBlackListParamsFilter(cfg.ParamsBlackList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.MinLogLevel(level, out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildReceiverForClient(client *raven.Client) (*sentryLogReceiver, error) {
|
||||||
|
return &sentryLogReceiver{target: client, filter: slf.NewBlackListParamsFilter(nil)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sentryLogReceiver struct {
|
||||||
|
target *raven.Client
|
||||||
|
filter slf.ParamsFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l sentryLogReceiver) Receive(p slf.Event) {
|
||||||
|
if !p.IsLog() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := raven.NewPacket(
|
||||||
|
slf.ReplacePlaceholders(p.Content, p.Params, false),
|
||||||
|
// First 5 means, that first N elements will be skipped before actual app trace
|
||||||
|
// This is needed to exclude watchdog calls from stack trace
|
||||||
|
raven.NewStacktrace(5, 5, []string{}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(p.Params) > 0 {
|
||||||
|
shownParams := l.filter(p.Params)
|
||||||
|
for _, param := range shownParams {
|
||||||
|
value := param.GetRaw()
|
||||||
|
if e, ok := value.(error); ok && e != nil {
|
||||||
|
value = e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.Extra[param.GetKey()] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.Level = convertType(p.Type)
|
||||||
|
pkt.Timestamp = raven.Timestamp(p.Time)
|
||||||
|
|
||||||
|
l.target.Capture(pkt, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertType(wdType byte) raven.Severity {
|
||||||
|
switch wdType {
|
||||||
|
case slf.TypeTrace:
|
||||||
|
case slf.TypeDebug:
|
||||||
|
return raven.DEBUG
|
||||||
|
case slf.TypeInfo:
|
||||||
|
return raven.INFO
|
||||||
|
case slf.TypeWarning:
|
||||||
|
return raven.WARNING
|
||||||
|
case slf.TypeError:
|
||||||
|
return raven.ERROR
|
||||||
|
case slf.TypeAlert:
|
||||||
|
case slf.TypeEmergency:
|
||||||
|
return raven.FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("Unknown wd type " + string(wdType))
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type UsernameChanged struct {
|
|
||||||
AccountId int `json:"accountId"`
|
|
||||||
OldUsername string `json:"oldUsername"`
|
|
||||||
NewUsername string `json:"newUsername"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SkinChanged struct {
|
|
||||||
AccountId int `json:"userId"`
|
|
||||||
Uuid string `json:"uuid"`
|
|
||||||
SkinId int `json:"skinId"`
|
|
||||||
OldSkinId int `json:"oldSkinId"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
Is1_8 bool `json:"is1_8"`
|
|
||||||
IsSlim bool `json:"isSlim"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
MojangTextures string `json:"mojangTextures"`
|
|
||||||
MojangSignature string `json:"mojangSignature"`
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package worker
|
package worker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
@@ -29,7 +30,7 @@ func TestServices_HandleChangeUsername(t *testing.T) {
|
|||||||
skinRepo.EXPECT().Save(resultModel)
|
skinRepo.EXPECT().Save(resultModel)
|
||||||
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
||||||
|
|
||||||
assert.True(services.HandleChangeUsername(&model.UsernameChanged{
|
assert.True(services.HandleChangeUsername(&UsernameChanged{
|
||||||
AccountId: 1,
|
AccountId: 1,
|
||||||
OldUsername: "mock_user",
|
OldUsername: "mock_user",
|
||||||
NewUsername: "new_username",
|
NewUsername: "new_username",
|
||||||
@@ -41,7 +42,7 @@ func TestServices_HandleChangeUsername(t *testing.T) {
|
|||||||
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.change_username_empty_old_username", int64(1))
|
wd.EXPECT().IncCounter("worker.change_username_empty_old_username", int64(1))
|
||||||
|
|
||||||
assert.True(services.HandleChangeUsername(&model.UsernameChanged{
|
assert.True(services.HandleChangeUsername(&UsernameChanged{
|
||||||
AccountId: 1,
|
AccountId: 1,
|
||||||
OldUsername: "",
|
OldUsername: "",
|
||||||
NewUsername: "new_mock",
|
NewUsername: "new_mock",
|
||||||
@@ -52,8 +53,23 @@ func TestServices_HandleChangeUsername(t *testing.T) {
|
|||||||
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock2"})
|
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock2"})
|
||||||
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.change_username_id_not_found", int64(1))
|
wd.EXPECT().IncCounter("worker.change_username_id_not_found", int64(1))
|
||||||
|
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
|
||||||
|
|
||||||
assert.True(services.HandleChangeUsername(&model.UsernameChanged{
|
assert.True(services.HandleChangeUsername(&UsernameChanged{
|
||||||
|
AccountId: 1,
|
||||||
|
OldUsername: "mock_user",
|
||||||
|
NewUsername: "new_mock2",
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Репозиторий вернул неожиданную ошибку
|
||||||
|
skinRepo.EXPECT().FindByUserId(1).Return(nil, errors.New("mock error"))
|
||||||
|
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock2"})
|
||||||
|
wd.EXPECT().IncCounter("worker.change_username", int64(1))
|
||||||
|
wd.EXPECT().IncCounter("worker.change_username_id_not_found", int64(1))
|
||||||
|
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
|
||||||
|
wd.EXPECT().Error("Unknown error when requesting a skin from the repository: :err", gomock.Any())
|
||||||
|
|
||||||
|
assert.True(services.HandleChangeUsername(&UsernameChanged{
|
||||||
AccountId: 1,
|
AccountId: 1,
|
||||||
OldUsername: "mock_user",
|
OldUsername: "mock_user",
|
||||||
NewUsername: "new_mock2",
|
NewUsername: "new_mock2",
|
||||||
@@ -68,7 +84,7 @@ func TestServices_HandleSkinChanged(t *testing.T) {
|
|||||||
|
|
||||||
services, skinRepo, accountsAPI, wd := setupMocks(ctrl)
|
services, skinRepo, accountsAPI, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
event := &model.SkinChanged{
|
event := &SkinChanged{
|
||||||
AccountId: 1,
|
AccountId: 1,
|
||||||
Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623",
|
Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623",
|
||||||
SkinId: 2,
|
SkinId: 2,
|
||||||
@@ -109,8 +125,8 @@ func TestServices_HandleSkinChanged(t *testing.T) {
|
|||||||
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.skin_changed_id_restored", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed_id_restored", int64(1))
|
||||||
wd.EXPECT().Warning(gomock.Any())
|
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
|
||||||
wd.EXPECT().Info(gomock.Any())
|
wd.EXPECT().Info("User info successfully restored.")
|
||||||
|
|
||||||
assert.True(services.HandleSkinChanged(event))
|
assert.True(services.HandleSkinChanged(event))
|
||||||
|
|
||||||
@@ -120,8 +136,20 @@ func TestServices_HandleSkinChanged(t *testing.T) {
|
|||||||
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
|
||||||
wd.EXPECT().IncCounter("worker.skin_changed_id_not_restored", int64(1))
|
wd.EXPECT().IncCounter("worker.skin_changed_id_not_restored", int64(1))
|
||||||
wd.EXPECT().Warning(gomock.Any())
|
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
|
||||||
wd.EXPECT().Error(gomock.Any())
|
wd.EXPECT().Error("Cannot restore user info for :accountId: :err", gomock.Any(), gomock.Any())
|
||||||
|
|
||||||
|
assert.True(services.HandleSkinChanged(event))
|
||||||
|
|
||||||
|
// Репозиторий скинов вернул неизвестную ошибку, и Ely.by Accounts internal API не знает о таком пользователе
|
||||||
|
skinRepo.EXPECT().FindByUserId(1).Return(nil, errors.New("mocked error"))
|
||||||
|
accountsAPI.EXPECT().AccountInfo("id", "1").Return(nil, &accounts.NotFoundResponse{})
|
||||||
|
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
|
||||||
|
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
|
||||||
|
wd.EXPECT().IncCounter("worker.skin_changed_id_not_restored", int64(1))
|
||||||
|
wd.EXPECT().Error("Unknown error when requesting a skin from the repository: :err", gomock.Any())
|
||||||
|
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
|
||||||
|
wd.EXPECT().Error("Cannot restore user info for :accountId: :err", gomock.Any(), gomock.Any())
|
||||||
|
|
||||||
assert.True(services.HandleSkinChanged(event))
|
assert.True(services.HandleSkinChanged(event))
|
||||||
}
|
}
|
||||||
|
|||||||
172
worker/worker.go
172
worker/worker.go
@@ -2,39 +2,69 @@ package worker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/assembla/cony"
|
||||||
"github.com/mono83/slf/wd"
|
"github.com/mono83/slf/wd"
|
||||||
"github.com/streadway/amqp"
|
"github.com/streadway/amqp"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
"elyby/minecraft-skinsystem/interfaces"
|
"elyby/minecraft-skinsystem/interfaces"
|
||||||
"elyby/minecraft-skinsystem/model"
|
"elyby/minecraft-skinsystem/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
Channel *amqp.Channel
|
AmqpClient *cony.Client
|
||||||
SkinsRepo interfaces.SkinsRepository
|
SkinsRepo interfaces.SkinsRepository
|
||||||
AccountsAPI interfaces.AccountsAPI
|
AccountsAPI interfaces.AccountsAPI
|
||||||
Logger wd.Watchdog
|
Logger wd.Watchdog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UsernameChanged struct {
|
||||||
|
AccountId int `json:"accountId"`
|
||||||
|
OldUsername string `json:"oldUsername"`
|
||||||
|
NewUsername string `json:"newUsername"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SkinChanged struct {
|
||||||
|
AccountId int `json:"userId"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
SkinId int `json:"skinId"`
|
||||||
|
OldSkinId int `json:"oldSkinId"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Is1_8 bool `json:"is1_8"`
|
||||||
|
IsSlim bool `json:"isSlim"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
MojangTextures string `json:"mojangTextures"`
|
||||||
|
MojangSignature string `json:"mojangSignature"`
|
||||||
|
}
|
||||||
|
|
||||||
const exchangeName string = "events"
|
const exchangeName string = "events"
|
||||||
const queueName string = "skinsystem-accounts-events"
|
const queueName string = "skinsystem-accounts-events"
|
||||||
|
|
||||||
func (service *Services) Run() error {
|
func (service *Services) Run() error {
|
||||||
deliveryChannel, err := setupConsume(service.Channel)
|
clientErrs, consumerErrs, deliveryChannel := setupClient(service.AmqpClient)
|
||||||
if err != nil {
|
shouldReturnError := true
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
forever := make(chan bool)
|
for service.AmqpClient.Loop() {
|
||||||
go func() {
|
select {
|
||||||
for d := range deliveryChannel {
|
case msg := <-deliveryChannel:
|
||||||
service.HandleDelivery(&d)
|
shouldReturnError = false
|
||||||
|
service.HandleDelivery(&msg)
|
||||||
|
case err := <-consumerErrs:
|
||||||
|
if shouldReturnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Logger.Error("Consume error: :err", wd.ErrParam(err))
|
||||||
|
case err := <-clientErrs:
|
||||||
|
if shouldReturnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Logger.Error("Client error: :err", wd.ErrParam(err))
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
<-forever
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -44,11 +74,11 @@ func (service *Services) HandleDelivery(delivery *amqp.Delivery) {
|
|||||||
var result bool = true
|
var result bool = true
|
||||||
switch delivery.RoutingKey {
|
switch delivery.RoutingKey {
|
||||||
case "accounts.username-changed":
|
case "accounts.username-changed":
|
||||||
var event *model.UsernameChanged
|
var event *UsernameChanged
|
||||||
json.Unmarshal(delivery.Body, &event)
|
json.Unmarshal(delivery.Body, &event)
|
||||||
result = service.HandleChangeUsername(event)
|
result = service.HandleChangeUsername(event)
|
||||||
case "accounts.skin-changed":
|
case "accounts.skin-changed":
|
||||||
var event *model.SkinChanged
|
var event *SkinChanged
|
||||||
json.Unmarshal(delivery.Body, &event)
|
json.Unmarshal(delivery.Body, &event)
|
||||||
result = service.HandleSkinChanged(event)
|
result = service.HandleSkinChanged(event)
|
||||||
default:
|
default:
|
||||||
@@ -64,7 +94,7 @@ func (service *Services) HandleDelivery(delivery *amqp.Delivery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *Services) HandleChangeUsername(event *model.UsernameChanged) bool {
|
func (service *Services) HandleChangeUsername(event *UsernameChanged) bool {
|
||||||
service.Logger.IncCounter("worker.change_username", 1)
|
service.Logger.IncCounter("worker.change_username", 1)
|
||||||
if event.OldUsername == "" {
|
if event.OldUsername == "" {
|
||||||
service.Logger.IncCounter("worker.change_username_empty_old_username", 1)
|
service.Logger.IncCounter("worker.change_username_empty_old_username", 1)
|
||||||
@@ -80,7 +110,11 @@ func (service *Services) HandleChangeUsername(event *model.UsernameChanged) bool
|
|||||||
|
|
||||||
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: если это не SkinNotFound, то нужно логгировать в Sentry
|
service.Logger.Info("Cannot find user id :accountId. Trying to search.", wd.IntParam("accountId", event.AccountId))
|
||||||
|
if _, isSkinNotFound := err.(*db.SkinNotFoundError); !isSkinNotFound {
|
||||||
|
service.Logger.Error("Unknown error when requesting a skin from the repository: :err", wd.ErrParam(err))
|
||||||
|
}
|
||||||
|
|
||||||
service.Logger.IncCounter("worker.change_username_id_not_found", 1)
|
service.Logger.IncCounter("worker.change_username_id_not_found", 1)
|
||||||
record = &model.Skin{
|
record = &model.Skin{
|
||||||
UserId: event.AccountId,
|
UserId: event.AccountId,
|
||||||
@@ -94,18 +128,26 @@ func (service *Services) HandleChangeUsername(event *model.UsernameChanged) bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: возможно стоит добавить проверку на совпадение id аккаунтов
|
// TODO: возможно стоит добавить проверку на совпадение id аккаунтов
|
||||||
func (service *Services) HandleSkinChanged(event *model.SkinChanged) bool {
|
func (service *Services) HandleSkinChanged(event *SkinChanged) bool {
|
||||||
service.Logger.IncCounter("worker.skin_changed", 1)
|
service.Logger.IncCounter("worker.skin_changed", 1)
|
||||||
var record *model.Skin
|
var record *model.Skin
|
||||||
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, isSkinNotFound := err.(*db.SkinNotFoundError); !isSkinNotFound {
|
||||||
|
service.Logger.Error("Unknown error when requesting a skin from the repository: :err", wd.ErrParam(err))
|
||||||
|
}
|
||||||
|
|
||||||
service.Logger.IncCounter("worker.skin_changed_id_not_found", 1)
|
service.Logger.IncCounter("worker.skin_changed_id_not_found", 1)
|
||||||
service.Logger.Warning("Cannot find user id. Trying to search.")
|
service.Logger.Info("Cannot find user id :accountId. Trying to search.", wd.IntParam("accountId", event.AccountId))
|
||||||
response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId))
|
response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.Logger.IncCounter("worker.skin_changed_id_not_restored", 1)
|
service.Logger.IncCounter("worker.skin_changed_id_not_restored", 1)
|
||||||
service.Logger.Error(fmt.Sprintf("Cannot restore user info. %+v\n", err))
|
service.Logger.Error(
|
||||||
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
"Cannot restore user info for :accountId: :err",
|
||||||
|
wd.IntParam("accountId", event.AccountId),
|
||||||
|
wd.ErrParam(err),
|
||||||
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,55 +174,47 @@ func (service *Services) HandleSkinChanged(event *model.SkinChanged) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupConsume(channel *amqp.Channel) (<-chan amqp.Delivery, error) {
|
func setupClient(client *cony.Client) (<-chan error, <-chan error, <-chan amqp.Delivery ) {
|
||||||
var err error
|
exchange := cony.Exchange{
|
||||||
err = channel.ExchangeDeclare(
|
Name: exchangeName,
|
||||||
exchangeName, // name
|
Kind: "topic",
|
||||||
"topic", // type
|
Durable: true,
|
||||||
true, // durable
|
AutoDelete: false,
|
||||||
false, // auto-deleted
|
}
|
||||||
false, // internal
|
|
||||||
false, // no-wait
|
queue := &cony.Queue{
|
||||||
nil, // arguments
|
Name: queueName,
|
||||||
|
Durable: true,
|
||||||
|
AutoDelete: false,
|
||||||
|
Exclusive: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
usernameEventBinding := cony.Binding{
|
||||||
|
Exchange: exchange,
|
||||||
|
Queue: queue,
|
||||||
|
Key: "accounts.username-changed",
|
||||||
|
}
|
||||||
|
|
||||||
|
skinEventBinding := cony.Binding{
|
||||||
|
Exchange: exchange,
|
||||||
|
Queue: queue,
|
||||||
|
Key: "accounts.skin-changed",
|
||||||
|
}
|
||||||
|
|
||||||
|
declarations := []cony.Declaration{
|
||||||
|
cony.DeclareExchange(exchange),
|
||||||
|
cony.DeclareQueue(queue),
|
||||||
|
cony.DeclareBinding(usernameEventBinding),
|
||||||
|
cony.DeclareBinding(skinEventBinding),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Declare(declarations)
|
||||||
|
|
||||||
|
consumer := cony.NewConsumer(queue,
|
||||||
|
cony.Qos(10),
|
||||||
|
cony.AutoTag(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
client.Consume(consumer)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = channel.QueueDeclare(
|
return client.Errors(), consumer.Errors(), consumer.Deliveries()
|
||||||
queueName, // name
|
|
||||||
true, // durable
|
|
||||||
false, // delete when usused
|
|
||||||
false, // exclusive
|
|
||||||
false, // no-wait
|
|
||||||
nil, // arguments
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = channel.QueueBind(queueName, "accounts.username-changed", exchangeName, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = channel.QueueBind(queueName, "accounts.skin-changed", exchangeName, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
deliveryChannel, err := channel.Consume(
|
|
||||||
queueName, // queue
|
|
||||||
"", // consumer
|
|
||||||
false, // auto-ack
|
|
||||||
false, // exclusive
|
|
||||||
false, // no-local
|
|
||||||
false, // no-wait
|
|
||||||
nil, // args
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return deliveryChannel, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user