Skip to content
Merged
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
47 changes: 29 additions & 18 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ name: tests
on: [push]

jobs:

build:
name: Build
runs-on: ubuntu-latest
services:
postgres:
image: postgres:9.6-alpine
image: postgres:12-alpine
env:
POSTGRES_DB: hammer
POSTGRES_DB: hammer_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
Expand All @@ -22,18 +21,30 @@ jobs:
--health-timeout 5s
--health-retries 5
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: go mod download
- name: Lint
run: make lint
- name: Build
env:
HAMMER_DATABASE_URL: 'postgres://test:test@localhost:5432/hammer?sslmode=disable'
run: make db-migrate && make test
- name: Set up Go 1.16
uses: actions/setup-go@v2
with:
go-version: 1.16
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Set cache
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Get dependencies
run: go mod download

- name: Lint
run: make lint

- name: Build
env:
HAMMER_TEST_DATABASE_URL: "postgres://test:test@localhost:5432/hammer_test?sslmode=disable"
run: make db-test-migrate && make test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
# Dependency directories (remove the comment below to include it)
# vendor/

# log files
*.log

# golang-migrate binaries
migrate.darwin-amd64
migrate.linux-amd64
Expand Down
20 changes: 12 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
FROM golang:1.14-buster AS build-env
#### development stage
FROM golang:1.16-buster AS build-env

# set envvar
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GO111MODULE='on'

# set workdir
WORKDIR /build
WORKDIR /code

# get project dependencies
COPY go.mod go.sum /code/
RUN go mod download

# copy files
COPY . .
COPY . /code

# build binary
RUN go mod download && \
go build -ldflags="-s -w" -o ./app ./cmd/hammer
# generate binary
RUN go build -ldflags="-s -w" -o ./app ./cmd/hammer

# final stage
FROM gcr.io/distroless/base:nonroot
COPY --from=build-env /build/app /
COPY --from=build-env /build/db/migrations /db/migrations
COPY --from=build-env /code/app /
COPY --from=build-env /code/db/migrations /db/migrations
# http server
EXPOSE 8000
# health check server
Expand Down
25 changes: 8 additions & 17 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,28 @@ build-protobuf:
lint:
if [ ! -f ./bin/golangci-lint ] ; \
then \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.27.0; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.38.0; \
fi;
./bin/golangci-lint run
./bin/golangci-lint run -E gosec

test:
go test -covermode=count -coverprofile=count.out -v ./...

download-golang-migrate-binary:
if [ ! -f ./migrate.$(PLATFORM)-amd64 ] ; \
then \
curl -sfL https://github.com/golang-migrate/migrate/releases/download/v4.11.0/migrate.$(PLATFORM)-amd64.tar.gz | tar -xvz; \
curl -sfL https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.$(PLATFORM)-amd64.tar.gz | tar -xvz; \
fi;

db-migrate: download-golang-migrate-binary
./migrate.$(PLATFORM)-amd64 -source file://db/migrations -database ${HAMMER_DATABASE_URL} up

db-test-migrate: download-golang-migrate-binary
./migrate.$(PLATFORM)-amd64 -source file://db/migrations -database ${HAMMER_TEST_DATABASE_URL} up

mock:
@rm -rf mocks
mockery -name TopicRepository
mockery -name SubscriptionRepository
mockery -name MessageRepository
mockery -name DeliveryRepository
mockery -name DeliveryAttemptRepository
mockery -name TxRepository
mockery -name TxFactoryRepository
mockery -name MigrationRepository
mockery -name TopicService
mockery -name SubscriptionService
mockery -name MessageService
mockery -name DeliveryService
mockery -name DeliveryAttemptService
mockery --all

run-worker:
go run cmd/hammer/main.go worker
Expand All @@ -49,4 +40,4 @@ run-server:
run-migrate:
go run cmd/hammer/main.go migrate

.PHONY: build-protobuf lint test download-golang-migrate-binary db-migrate mock run-worker run-server run-migrate
.PHONY: build-protobuf lint test download-golang-migrate-binary db-migrate db-test-migrate mock run-worker run-server run-migrate
97 changes: 60 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Simple webhook system written in golang.

## Features

- GRPC + GPRC Gateway (http2 or http1, you choose your destination).
- GRPC + Rest API.
- Topics and Subscriptions scheme similar to google pubsub, the message is published in a topic and this topic has several subscriptions sending the same notification to different systems.
- Payload sent follows the JSON Event Format for CloudEvents - Version 1.0 standard.
- Control the maximum amount of delivery attempts and delay between these attempts.
- Locks control of worker deliveries using https://github.com/allisson/go-pglock.
- Locks control of worker deliveries using PostgreSQL SELECT FOR UPDATE SKIP LOCKED.
- Simplicity, it does the minimum necessary, it will not have authentication/permission scheme among other things, the idea is to use it internally in the cloud and not leave exposed.

## Quickstart
Expand All @@ -31,7 +31,7 @@ To run the server it is necessary to have a database available from postgresql,

```bash
docker run --env HAMMER_DATABASE_URL='postgres://user:pass@localhost:5432/hammer?sslmode=disable' allisson/hammer migrate # run database migrations
docker run -p 4001:4001 -p 8000:8000 -p 9000:9000 -p 50051:50051 --env HAMMER_DATABASE_URL='postgres://user:pass@localhost:5432/hammer?sslmode=disable' allisson/hammer server # run grpc/http/metrics server
docker run -p 4001:4001 -p 8000:8000 -p 9000:9000 -p 50051:50051 --env HAMMER_DATABASE_URL='postgres://user:pass@localhost:5432/hammer?sslmode=disable' allisson/hammer server # run grpc/http/metrics servers
```

#### Local
Expand All @@ -52,19 +52,19 @@ We are using curl in the examples below.
curl -X POST 'http://localhost:8000/v1/topics' \
--header 'Content-Type: application/json' \
--data-raw '{
"topic": {
"id": "topic",
"name": "Topic"
}
"topic": {
"id": "topic",
"name": "Topic"
}
}'
```

```javascript
{
"id": "topic",
"name": "Topic",
"created_at": "2020-05-17T18:04:49.949875Z",
"updated_at": "2020-05-17T18:04:49.949875Z"
"created_at": "2021-03-18T11:08:49.678732Z",
"updated_at": "2021-03-18T11:08:49.678732Z"
}
```

Expand Down Expand Up @@ -99,8 +99,8 @@ curl -X POST 'http://localhost:8000/v1/subscriptions' \
"max_delivery_attempts": 5,
"delivery_attempt_delay": 60,
"delivery_attempt_timeout": 5,
"created_at": "2020-05-17T18:05:54.102493Z",
"updated_at": "2020-05-17T18:05:54.102493Z"
"created_at": "2021-03-18T11:10:05.855296Z",
"updated_at": "2021-03-18T11:10:05.855296Z"
}
```

Expand All @@ -120,11 +120,11 @@ curl -X POST 'http://localhost:8000/v1/messages' \

```javascript
{
"id": "01E8HX1CYHKN2R4TQVG507NYVS",
"id": "01F12GF6VAXGNHVXM4YT37N75A",
"topic_id": "topic",
"content_type": "application/json",
"data": "eyJuYW1lIjogIkFsbGlzc29uIn0=",
"created_at": "2020-05-17T18:06:19.601962Z"
"created_at": "2021-03-18T11:10:29.738632Z"
}
```

Expand All @@ -136,17 +136,17 @@ The system will send a post request and the server must respond with the followi

```bash
docker run --env HAMMER_DATABASE_URL='postgres://user:pass@localhost:5432/hammer?sslmode=disable' allisson/hammer worker
{"level":"info","ts":1589738659.759326,"caller":"hammer/main.go:266","msg":"worker-started"}
{"level":"info","ts":1589738780.93929,"caller":"service/worker.go:77","msg":"delivery-made","delivery_id":"01E8HX1CYM0RFZDMKJHSPFF50J","delivery_attempt_id":"01E8HX1D6PYFB1HJFG0S7WEKBK","response_status_code":200,"execution_duration":1061}
{"level":"info","ts":1616065862.332101,"caller":"service/worker.go:67","msg":"worker-started"}
{"level":"info","ts":1616065863.104438,"caller":"service/worker.go:36","msg":"worker-delivery-attempt-created","id":"01F12GG6NWZR03MW1MFMQDWVVF","delivery_id":"01F12GF6VM4YSX5GW8TM4781EZ","response_status_code":200,"execution_duration":749,"success":true}
```

#### Local

```bash
make run-worker
go run cmd/worker/main.go
{"level":"info","ts":1589738659.759326,"caller":"hammer/main.go:266","msg":"worker-started"}
{"level":"info","ts":1589738780.93929,"caller":"service/worker.go:77","msg":"delivery-made","delivery_id":"01E8HX1CYM0RFZDMKJHSPFF50J","delivery_attempt_id":"01E8HX1D6PYFB1HJFG0S7WEKBK","response_status_code":200,"execution_duration":1061}
{"level":"info","ts":1616065862.332101,"caller":"service/worker.go:67","msg":"worker-started"}
{"level":"info","ts":1616065863.104438,"caller":"service/worker.go:36","msg":"worker-delivery-attempt-created","id":"01F12GG6NWZR03MW1MFMQDWVVF","delivery_id":"01F12GF6VM4YSX5GW8TM4781EZ","response_status_code":200,"execution_duration":749,"success":true}
```

Submitted payload (Compatible with JSON Event Format for CloudEvents - Version 1.0):
Expand All @@ -155,13 +155,13 @@ Submitted payload (Compatible with JSON Event Format for CloudEvents - Version 1
{
"data_base64": "eyJuYW1lIjogIkFsbGlzc29uIn0=",
"datacontenttype": "application/json",
"id": "01E8HX1CYM0RFZDMKJHSPFF50J",
"messageid": "01E8HX1CYHKN2R4TQVG507NYVS",
"id": "01F12GF6VM4YSX5GW8TM4781EZ",
"messageid": "01F12GF6VAXGNHVXM4YT37N75A",
"secrettoken": "my-super-secret-token",
"source": "/v1/messages/01E8HX1CYHKN2R4TQVG507NYVS",
"source": "/v1/messages/01F12GF6VAXGNHVXM4YT37N75A",
"specversion": "1.0",
"subscriptionid": "httpbin-post",
"time": "2020-05-17T15:06:19.604225-03:00",
"time": "2021-03-18T11:10:29.748978Z",
"topicid": "topic",
"type": "hammer.message.created"
}
Expand All @@ -170,27 +170,27 @@ Submitted payload (Compatible with JSON Event Format for CloudEvents - Version 1
### Get delivery data

```bash
curl -X GET http://localhost:8000/v1/deliveries/01E8HX1CYM0RFZDMKJHSPFF50J
curl -X GET http://localhost:8000/v1/deliveries/01F12GF6VM4YSX5GW8TM4781EZ
```

```javascript
{
"id": "01E8HX1CYM0RFZDMKJHSPFF50J",
"id": "01F12GF6VM4YSX5GW8TM4781EZ",
"topic_id": "topic",
"subscription_id": "httpbin-post",
"message_id": "01E8HX1CYHKN2R4TQVG507NYVS",
"message_id": "01F12GF6VAXGNHVXM4YT37N75A",
"content_type": "application/json",
"data": "eyJuYW1lIjogIkFsbGlzc29uIn0=",
"url": "https://httpbin.org/post",
"secret_token": "my-super-secret-token",
"max_delivery_attempts": 5,
"delivery_attempt_delay": 60,
"delivery_attempt_timeout": 5,
"scheduled_at": "2020-05-17T18:06:19.604225Z",
"scheduled_at": "2021-03-18T11:10:29.748978Z",
"delivery_attempts": 1,
"status": "completed",
"created_at": "2020-05-17T18:06:19.604225Z",
"updated_at": "2020-05-17T18:06:20.935601Z"
"created_at": "2021-03-18T11:10:29.748978Z",
"updated_at": "2021-03-18T11:11:03.098648Z"
}
```

Expand All @@ -199,45 +199,68 @@ curl -X GET http://localhost:8000/v1/deliveries/01E8HX1CYM0RFZDMKJHSPFF50J
The execution_duration are in milliseconds.

```bash
curl -X GET http://localhost:8000/v1/delivery-attempts/01E8HX1D6PYFB1HJFG0S7WEKBK
curl -X GET http://localhost:8000/v1/delivery-attempts/01F12GG6NWZR03MW1MFMQDWVVF
```

```javascript
{
"id": "01E8HX1D6PYFB1HJFG0S7WEKBK",
"delivery_id": "01E8HX1CYM0RFZDMKJHSPFF50J",
"request": "POST /post HTTP/1.1\r\nHost: httpbin.org\r\nContent-Type: application/json\r\n\r\n{\"specversion\":\"1.0\",\"type\":\"hammer.message.created\",\"source\":\"/v1/messages/01E8HX1CYHKN2R4TQVG507NYVS\",\"id\":\"01E8HX1CYM0RFZDMKJHSPFF50J\",\"time\":\"2020-05-17T15:06:19.604225-03:00\",\"secrettoken\":\"my-super-secret-token\",\"messageid\":\"01E8HX1CYHKN2R4TQVG507NYVS\",\"subscriptionid\":\"httpbin-post\",\"topicid\":\"topic\",\"datacontenttype\":\"application/json\",\"data_base64\":\"eyJuYW1lIjogIkFsbGlzc29uIn0=\"}",
"response": "HTTP/2.0 200 OK\r\nContent-Length: 1308\r\nAccess-Control-Allow-Credentials: true\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json\r\nDate: Sun, 17 May 2020 18:06:20 GMT\r\nServer: gunicorn/19.9.0\r\n\r\n{\n \"args\": {}, \n \"data\": \"{\\\"specversion\\\":\\\"1.0\\\",\\\"type\\\":\\\"hammer.message.created\\\",\\\"source\\\":\\\"/v1/messages/01E8HX1CYHKN2R4TQVG507NYVS\\\",\\\"id\\\":\\\"01E8HX1CYM0RFZDMKJHSPFF50J\\\",\\\"time\\\":\\\"2020-05-17T15:06:19.604225-03:00\\\",\\\"secrettoken\\\":\\\"my-super-secret-token\\\",\\\"messageid\\\":\\\"01E8HX1CYHKN2R4TQVG507NYVS\\\",\\\"subscriptionid\\\":\\\"httpbin-post\\\",\\\"topicid\\\":\\\"topic\\\",\\\"datacontenttype\\\":\\\"application/json\\\",\\\"data_base64\\\":\\\"eyJuYW1lIjogIkFsbGlzc29uIn0=\\\"}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Accept-Encoding\": \"gzip\", \n \"Content-Length\": \"391\", \n \"Content-Type\": \"application/json\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"Go-http-client/2.0\", \n \"X-Amzn-Trace-Id\": \"Root=1-5ec17d1c-2614cd69fd899c64176e4e01\"\n }, \n \"json\": {\n \"data_base64\": \"eyJuYW1lIjogIkFsbGlzc29uIn0=\", \n \"datacontenttype\": \"application/json\", \n \"id\": \"01E8HX1CYM0RFZDMKJHSPFF50J\", \n \"messageid\": \"01E8HX1CYHKN2R4TQVG507NYVS\", \n \"secrettoken\": \"my-super-secret-token\", \n \"source\": \"/v1/messages/01E8HX1CYHKN2R4TQVG507NYVS\", \n \"specversion\": \"1.0\", \n \"subscriptionid\": \"httpbin-post\", \n \"time\": \"2020-05-17T15:06:19.604225-03:00\", \n \"topicid\": \"topic\", \n \"type\": \"hammer.message.created\"\n }, \n \"origin\": \"177.37.153.46\", \n \"url\": \"https://httpbin.org/post\"\n}\n",
"id": "01F12GG6NWZR03MW1MFMQDWVVF",
"delivery_id": "01F12GF6VM4YSX5GW8TM4781EZ",
"request": "POST /post HTTP/1.1\r\nHost: httpbin.org\r\nContent-Type: application/json\r\n\r\n{\"specversion\":\"1.0\",\"type\":\"hammer.message.created\",\"source\":\"/v1/messages/01F12GF6VAXGNHVXM4YT37N75A\",\"id\":\"01F12GF6VM4YSX5GW8TM4781EZ\",\"time\":\"2021-03-18T11:10:29.748978Z\",\"secrettoken\":\"my-super-secret-token\",\"messageid\":\"01F12GF6VAXGNHVXM4YT37N75A\",\"subscriptionid\":\"httpbin-post\",\"topicid\":\"topic\",\"datacontenttype\":\"application/json\",\"data_base64\":\"eyJuYW1lIjogIkFsbGlzc29uIn0=\"}",
"response": "HTTP/2.0 200 OK\r\nContent-Length: 1298\r\nAccess-Control-Allow-Credentials: true\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json\r\nDate: Thu, 18 Mar 2021 11:11:03 GMT\r\nServer: gunicorn/19.9.0\r\n\r\n{\n \"args\": {}, \n \"data\": \"{\\\"specversion\\\":\\\"1.0\\\",\\\"type\\\":\\\"hammer.message.created\\\",\\\"source\\\":\\\"/v1/messages/01F12GF6VAXGNHVXM4YT37N75A\\\",\\\"id\\\":\\\"01F12GF6VM4YSX5GW8TM4781EZ\\\",\\\"time\\\":\\\"2021-03-18T11:10:29.748978Z\\\",\\\"secrettoken\\\":\\\"my-super-secret-token\\\",\\\"messageid\\\":\\\"01F12GF6VAXGNHVXM4YT37N75A\\\",\\\"subscriptionid\\\":\\\"httpbin-post\\\",\\\"topicid\\\":\\\"topic\\\",\\\"datacontenttype\\\":\\\"application/json\\\",\\\"data_base64\\\":\\\"eyJuYW1lIjogIkFsbGlzc29uIn0=\\\"}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Accept-Encoding\": \"gzip\", \n \"Content-Length\": \"386\", \n \"Content-Type\": \"application/json\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"Go-http-client/2.0\", \n \"X-Amzn-Trace-Id\": \"Root=1-60533547-501c866f62e44ea3736dbc0c\"\n }, \n \"json\": {\n \"data_base64\": \"eyJuYW1lIjogIkFsbGlzc29uIn0=\", \n \"datacontenttype\": \"application/json\", \n \"id\": \"01F12GF6VM4YSX5GW8TM4781EZ\", \n \"messageid\": \"01F12GF6VAXGNHVXM4YT37N75A\", \n \"secrettoken\": \"my-super-secret-token\", \n \"source\": \"/v1/messages/01F12GF6VAXGNHVXM4YT37N75A\", \n \"specversion\": \"1.0\", \n \"subscriptionid\": \"httpbin-post\", \n \"time\": \"2021-03-18T11:10:29.748978Z\", \n \"topicid\": \"topic\", \n \"type\": \"hammer.message.created\"\n }, \n \"origin\": \"191.33.94.128\", \n \"url\": \"https://httpbin.org/post\"\n}\n",
"response_status_code": 200,
"execution_duration": 1061,
"execution_duration": 749,
"success": true,
"created_at": "2020-05-17T18:06:20.925086Z"
"created_at": "2021-03-18T11:11:03.091808Z"
}
```

## Environment variables

All environment variables is defined on file local.env.

## How to build docker image

```
docker build -f Dockerfile -t hammer .
```

## Disable REST API
## REST API

Default port: 8000

```bash
curl --location --request GET 'http://localhost:8000/v1/topics'
```

To disable the rest api, set the environment variable **HAMMER_REST_API_ENABLED** to false.

```bash
export HAMMER_REST_API_ENABLED='false'
```

## Disable Prometheus metrics
## Prometheus metrics

Default port: 4001

```bash
curl --location --request GET 'http://localhost:4001/metrics'
```

To disable prometheus metrics, set the environment variable **HAMMER_METRICS_ENABLED** to false.

```bash
export HAMMER_METRICS_ENABLED='false'
```

## Disable health check
## Health check

Default port: 9000

```bash
curl --location --request GET 'http://localhost:9000/liveness'
curl --location --request GET 'http://localhost:9000/readiness'
```

To disable health check, set the environment variable **HAMMER_HEALTH_CHECK_ENABLED** to false.

Expand Down
Loading