Showing preview only (325K chars total). Download the full file or copy to clipboard to get everything.
Repository: tsawler/go-microservices
Branch: main
Commit: 301cd369d529
Files: 97
Total size: 300.5 KB
Directory structure:
gitextract_w41isflk/
├── .gitignore
├── Caddyfile
├── LICENSE.md
├── Makefile
├── README.md
├── authentication-service/
│ ├── cmd/
│ │ └── api/
│ │ ├── discover.go
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ └── routes.go
│ ├── data/
│ │ └── models.go
│ ├── go.mod
│ ├── go.sum
│ └── users.sql
├── authentication-service.dockerfile
├── broker-service/
│ ├── Makefile
│ ├── cmd/
│ │ └── api/
│ │ ├── discover.go
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ └── routes.go
│ ├── event/
│ │ ├── emitter.go
│ │ └── event.go
│ ├── go.mod
│ ├── go.sum
│ └── logs/
│ ├── logs.pb.go
│ ├── logs.proto
│ └── logs_grpc.pb.go
├── broker-service.dockerfile
├── caddy.dockerfile
├── db-data/
│ ├── .gitignore
│ └── .gitkeep
├── docker-compose.yml
├── front-end/
│ ├── cmd/
│ │ └── web/
│ │ ├── main.go
│ │ └── templates/
│ │ ├── base.layout.gohtml
│ │ ├── footer.partial.gohtml
│ │ ├── header.partial.gohtml
│ │ └── test.page.gohtml
│ └── go.mod
├── front-end.dockerfile
├── ingress.yml
├── k8s/
│ ├── authentication.yml
│ ├── broker.yml
│ ├── listener.yml
│ ├── logger.yml
│ ├── mail.yml
│ ├── mailhog.yml
│ ├── mongo.yml
│ └── rabbitmq.yml
├── k8s.md
├── listener-service/
│ ├── go.mod
│ ├── go.sum
│ ├── lib/
│ │ └── event/
│ │ ├── consumer.go
│ │ └── event.go
│ └── main.go
├── listener-service.dockerfile
├── logger-service/
│ ├── Makefile
│ ├── cmd/
│ │ └── web/
│ │ ├── discover.go
│ │ ├── grpc.go
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ ├── middleware.go
│ │ ├── render.go
│ │ ├── routes.go
│ │ └── rpc.go
│ ├── data/
│ │ └── models.go
│ ├── go.mod
│ ├── go.sum
│ ├── logs/
│ │ ├── logs.pb.go
│ │ ├── logs.proto
│ │ ├── logs_grpc.pb.go
│ │ └── readme.md
│ └── templates/
│ ├── base.layout.gohtml
│ ├── dashboard.page.gohtml
│ ├── entry.page.gohtml
│ └── login.page.gohtml
├── logger-service.dockerfile
├── mail-service/
│ ├── cmd/
│ │ └── api/
│ │ ├── discover.go
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── mailer.go
│ │ ├── main.go
│ │ └── routes.go
│ ├── go.mod
│ ├── go.sum
│ └── templates/
│ ├── mail.html.tmpl
│ └── mail.plain.tmpl
├── mail-service.dockerfile
├── multistage-dockerfiles/
│ ├── authentication-service.dockerfile
│ ├── broker-service.dockerfile
│ ├── listener-service.dockerfile
│ ├── logger-service.dockerfile
│ └── mail-service.dockerfile
├── postgres.yml
├── swarm.md
└── swarm.yml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea
.vscode
coverage.out
frontApp
logServiceApp
mailerServiceApp
authApp
brokerApp
listener
frontEndLinux
================================================
FILE: Caddyfile
================================================
{
email you@gmail.com
}
(static) {
@static {
file
path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.json
}
header @static Cache-Control max-age=5184000
}
(security) {
header {
# enable HSTS
Strict-Transport-Security max-age=31536000;
# disable clients from sniffing the media type
X-Content-Type-Options nosniff
# keep referrer data off of HTTP connections
Referrer-Policy no-referrer-when-downgrade
}
}
localhost:80 {
encode zstd gzip
import static
reverse_proxy http://front-end:8081
}
backend:80 {
reverse_proxy http://broker-service:8080
}
================================================
FILE: LICENSE.md
================================================
# MIT License
### Copyright (c) 2022 Trevor Sawler
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.
================================================
FILE: Makefile
================================================
FRONT_END_BINARY=frontApp
LOGGER_BINARY=logServiceApp
BROKER_BINARY=brokerApp
AUTH_BINARY=authApp
LISTENER_BINARY=listener
MAIL_BINARY=mailerServiceApp
AUTH_VERSION=1.0.0
BROKER_VERSION=1.0.0
LISTENER_VERSION=1.0.2
MAIL_VERSION=1.0.0
LOGGER_VERSION=1.0.0
## up: starts all containers in the background without forcing build
up:
@echo "Starting docker images..."
docker-compose up -d
@echo "Docker images started!"
## down: stop docker compose
down:
@echo "Stopping docker images..."
docker-compose down
@echo "Docker stopped!"
## build_dockerfiles: builds all dockerfile images
build_dockerfiles: build_auth build_broker build_listener build_logger build_mail front_end_linux
@echo "Building dockerfiles..."
docker build -f front-end.dockerfile -t tsawler/front-end .
docker build -f authentication-service.dockerfile -t tsawler/authentication:${AUTH_VERSION} .
docker build -f broker-service.dockerfile -t tsawler/broker:1.0.0 .
docker build -f listener-service.dockerfile -t tsawler/listener:1.0.2 .
docker build -f mail-service.dockerfile -t tsawler/mail:1.0.0 .
docker build -f logger-service.dockerfile -t tsawler/logger:1.0.0 .
## push_dockerfiles: pushes tagged versions to docker hub
push_dockerfiles: build_dockerfiles
docker push tsawler/authentication:${AUTH_VERSION}
docker push tsawler/broker:${BROKER_VERSION}
docker push tsawler/listener:${LISTENER_VERSION}
docker push tsawler/mail:${MAIL_VERSION}
docker push tsawler/logger:${LOGGER_VERSION}
@echo "Done!"
## front_end_linux: builds linux executable for front end
front_end_linux:
@echo "Building linux version of front end..."
cd front-end && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o frontEndLinux ./cmd/web
@echo "Done!"
## swarm_up: starts the swarm
swarm_up:
@echo "Starting swarm..."
docker stack deploy -c swarm.yml myapp
## swarm_down: stops the swarm
swarm_down:
@echo "Stopping swarm..."
docker stack rm myapp
## build_auth: builds the authentication binary as a linux executable
build_auth:
@echo "Building authentication binary.."
cd authentication-service && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${AUTH_BINARY} ./cmd/api
@echo "Authentication binary built!"
## build_logger: builds the logger binary as a linux executable
build_logger:
@echo "Building logger binary..."
cd logger-service && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${LOGGER_BINARY} ./cmd/web
@echo "Logger binary built!"
## build_broker: builds the broker binary as a linux executable
build_broker:
@echo "Building broker binary..."
cd broker-service && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${BROKER_BINARY} ./cmd/api
@echo "Broker binary built!"
## build_listener: builds the listener binary as a linux executable
build_listener:
@echo "Building listener binary..."
cd listener-service && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${LISTENER_BINARY} .
@echo "Listener binary built!"
## build_mail: builds the mail binary as a linux executable
build_mail:
@echo "Building mailer binary..."
cd mail-service && env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${MAIL_BINARY} ./cmd/api
@echo "Mailer binary built!"
## up_build: stops docker-compose (if running), builds all projects and starts docker compose
up_build: build_auth build_broker build_listener build_logger build_mail
@echo "Stopping docker images (if running...)"
docker-compose down
@echo "Building (when required) and starting docker images..."
docker-compose up --build -d
@echo "Docker images built and started!"
## auth: stops authentication-service, removes docker image, builds service, and starts it
auth: build_auth
@echo "Building authentication-service docker image..."
- docker-compose stop authentication-service
- docker-compose rm -f authentication-service
docker-compose up --build -d authentication-service
docker-compose start authentication-service
@echo "authentication-service built and started!"
## broker: stops broker-service, removes docker image, builds service, and starts it
broker: build_broker
@echo "Building broker-service docker image..."
- docker-compose stop broker-service
- docker-compose rm -f broker-service
docker-compose up --build -d broker-service
docker-compose start broker-service
@echo "broker-service rebuilt and started!"
## logger: stops logger-service, removes docker image, builds service, and starts it
logger: build_logger
@echo "Building logger-service docker image..."
- docker-compose stop logger-service
- docker-compose rm -f logger-service
docker-compose up --build -d logger-service
docker-compose start logger-service
@echo "broker-service rebuilt and started!"
## mail: stops mail-service, removes docker image, builds service, and starts it
mail: build_mail
@echo "Building mail-service docker image..."
- docker-compose stop mail-service
- docker-compose rm -f mail-service
docker-compose up --build -d mail-service
docker-compose start mail-service
@echo "mail-service rebuilt and started!"
## listener: stops listener-service, removes docker image, builds service, and starts it
listener: build_listener
@echo "Building listener-service docker image..."
- docker-compose stop listener-service
- docker-compose rm -f listener-service
docker-compose up --build -d listener-service
docker-compose start listener-service
@echo "listener-service rebuilt and started!"
## start: starts the front end
start:
@echo "Starting front end"
cd front-end && go build -o ${FRONT_END_BINARY} ./cmd/web
cd front-end && ./${FRONT_END_BINARY} &
## stop: stop the front end
stop:
@echo "Stopping front end..."
@-pkill -SIGTERM -f "./${FRONT_END_BINARY}"
@echo "Stopped front end!"
## test: runs all tests
test:
@echo "Testing..."
go test -v ./...
## clean: runs go clean and deletes binaries
clean:
@echo "Cleaning..."
@cd broker-service && rm -f ${BROKER_BINARY}
@cd broker-service && go clean
@cd listener-service && rm -f ${LISTENER_BINARY}
@cd listener-service && go clean
@cd authentication-service && rm -f ${AUTH_BINARY}
@cd authentication-service && go clean
@cd mail-service && rm -f ${MAIL_BINARY}
@cd mail-service && go clean
@cd logger-service && rm -f ${LOGGER_BINARY}
@cd logger-service && go clean
@cd front-end && go clean
@cd front-end && rm -f ${FRONT_END_BINARY}
@echo "Cleaned!"
## help: displays help
help: Makefile
@echo " Choose a command:"
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
================================================
FILE: README.md
================================================
[](https://golang.org)
[](https://raw.githubusercontent.com/tsawler/goblender/master/LICENSE)
# Working with Microservices in Go
This is the source code for the Udemy course **Working with Microservices and Go**. This project
consists of a number of loosely coupled microservices, all written in Go:
- broker-service: an optional single entry point to connect to all services from one place (accepts JSON;
sends JSON, makes calls via gRPC, and pushes to RabbitMQ)
- authentication-service: authenticates users against a Postgres database (accepts JSON)
- logger-service: logs important events to a MongoDB database (accepts RPC, gRPC, and JSON)
- queue-listener-service: consumes messages from amqp (RabbitMQ) and initiates actions based on payload (sends via RPC)
- mail-service: sends email (accepts JSON)
All services (except the broker) register their access urls with etcd, and renew their leases automatically.
This allows us to implement a simple service discovery system, where all service URLs are accessible with
"service maps" in the Config type used to share application configuration in the broker service.
In addition to the microservices, the included `docker-compose.yml` at the root level of the project
starts the following services:
- Postgresql - used by the authentication service to store user accounts
- MongoDB - used by the logger service to save logs from all services
- etcd - used for service discovery
- mailhog - used as a fake mail server to work with the mail service
## Running the project
From the root level of the project, execute this command (this assumes that you have
[GNU make](https://www.gnu.org/software/make/) and a recent version
of [Docker](https://www.docker.com/products/docker-desktop) installed on your machine):
~~~
make up_build
~~~
If the code has not changed, subsequent runs can just be `make up`.
Then start the front end:
~~~
make start
~~~
Hit the front end with your web browser at `http://localhost:80`. You can also access a web
front end to the logger service by going to `http://localhost:8082` (or whatever port you
specify in the `docker-compose.yml file`).
To stop everything:
~~~
make stop
make down
~~~
While working on code, you can rebuild just the service you are working on by
executing
`make auth`
Where `auth` is one of the services:
- auth
- broker
- logger
- listener
- mail
All make commands:
~~~
tcs@Grendel go-microservices % make help
Choose a command:
up starts all containers in the background without forcing build
down stop docker compose
build_auth builds the authentication binary as a linux executable
build_logger builds the logger binary as a linux executable
build_broker builds the broker binary as a linux executable
build_listener builds the listener binary as a linux executable
build_mail builds the mail binary as a linux executable
up_build stops docker-compose (if running), builds all projects and starts docker compose
auth stops authentication-service, removes docker image, builds service, and starts it
broker stops broker-service, removes docker image, builds service, and starts it
logger stops logger-service, removes docker image, builds service, and starts it
mail stops mail-service, removes docker image, builds service, and starts it
listener stops listener-service, removes docker image, builds service, and starts it
start starts the front end
stop stop the front end
test runs all tests
clean runs go clean and deletes binaries
help displays help
~~~
================================================
FILE: authentication-service/cmd/api/discover.go
================================================
package main
//
//// registerService registers the correct entry for this service in etcd
//func (app *Config) registerService() {
// cli, _ := connectToEtcd()
// kv := clientv3.NewKV(cli)
//
// app.Etcd = cli
//
// lease := clientv3.NewLease(cli)
// grantResp, err := lease.Grant(context.TODO(), 10)
// if err != nil {
// log.Println("Error creating lease", err)
// }
//
// // insert something with the lease
// _, err = kv.Put(context.TODO(), fmt.Sprintf("/auth/%s", app.randomString(32)), "authentication-service", clientv3.WithLease(grantResp.ID))
// if err != nil {
// log.Println("Error inserting using lease", err)
// }
//
// // keep lease alive
// kalRes, err := lease.KeepAlive(context.TODO(), grantResp.ID)
// if err != nil {
// log.Println("Error with keepalive", err)
// }
// go app.listenToKeepAlive(kalRes)
//}
//
//// listenToKeepAlive just consumes channel responses from etcd's KeepAlive method
//func (app *Config) listenToKeepAlive(kalRes <-chan *clientv3.LeaseKeepAliveResponse) {
// defer func() {
// if r := recover(); r != nil {
// log.Println("Error", fmt.Sprintf("%v", r))
// }
// }()
//
// // the only reason this exists is to consume the response from etcd's KeepAlive, because
// // if we don't, unexpected behaviour is the result.
// for {
// _ = <-kalRes
// }
//}
//
//// connectToEtcd tries to connect to etcd, for up to 30 seconds
//func connectToEtcd() (*clientv3.Client, error) {
// var cli *clientv3.Client
// var counts = 0
//
// for {
// c, err := clientv3.New(clientv3.Config{Endpoints: []string{"etcd:2379"},
// DialTimeout: 5 * time.Second,
// })
// if err != nil {
// fmt.Println("etcd not ready...")
// counts++
// } else {
// fmt.Println()
// cli = c
// break
// }
//
// if counts > 15 {
// return nil, err
// }
// fmt.Println("Backing off for 2 seconds...")
// time.Sleep(2 * time.Second)
// continue
// }
// log.Println("Connected to etcd!")
// return cli, nil
//}
================================================
FILE: authentication-service/cmd/api/handlers.go
================================================
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
)
// Authenticate accepts a json payload and attempts to authenticate a user
func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
var requestPayload struct {
Email string `json:"email"`
Password string `json:"password"`
}
err := app.readJSON(w, r, &requestPayload)
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
// validate against database
user, err := app.Models.User.GetByEmail(requestPayload.Email)
if err != nil {
_ = app.errorJSON(w, errors.New("invalid credentials"), http.StatusUnauthorized)
return
}
valid, err := user.PasswordMatches(requestPayload.Password)
if err != nil || !valid {
_ = app.errorJSON(w, errors.New("invalid credentials"), http.StatusUnauthorized)
return
}
// log request
err = app.logRequest("authentication", fmt.Sprintf("%s logged in", user.Email))
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
}
payload := jsonResponse{
Error: false,
Message: fmt.Sprintf("Logged in user %s", requestPayload.Email),
//Data: User{
// ID: 1,
// FirstName: "Jack",
// LastName: "Smith",
// Email: "jack@smith.com",
// Active: 1,
//},
Data: user,
}
_ = app.writeJSON(w, http.StatusAccepted, payload)
}
func (app *Config) logRequest(name, data string) error {
var entry struct {
Name string `json:"name"`
Data string `json:"data"`
}
entry.Name = name
entry.Data = data
jsonData, _ := json.MarshalIndent(entry, "", "\t")
logServiceURL := "http://logger-service/log"
request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
_, err = client.Do(request)
if err != nil {
return err
}
return nil
}
================================================
FILE: authentication-service/cmd/api/helpers.go
================================================
package main
import (
"crypto/rand"
"encoding/json"
"errors"
"io"
"net/http"
)
const randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321_+"
// jsonResponse is the type used for sending JSON around
type jsonResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
// readJSON tries to read the body of a request and converts it into JSON
func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1048576 // one megabyte
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
err := dec.Decode(data)
if err != nil {
return err
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must have only a single json value")
}
return nil
}
// writeJSON takes a response status code and arbitrary data and writes a json response to the client
func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
out, err := json.Marshal(data)
if err != nil {
return err
}
if len(headers) > 0 {
for key, value := range headers[0] {
w.Header()[key] = value
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(out)
if err != nil {
return err
}
return nil
}
// errorJSON takes an error, and optionally a response status code, and generates and sends
// a json error response
func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
statusCode := http.StatusBadRequest
if len(status) > 0 {
statusCode = status[0]
}
var payload jsonResponse
payload.Error = true
payload.Message = err.Error()
return app.writeJSON(w, statusCode, payload)
}
// randomString returns a random string of letters of length n
func (app *Config) randomString(n int) string {
s, r := make([]rune, n), []rune(randomStringSource)
for i := range s {
p, _ := rand.Prime(rand.Reader, len(r))
x, y := p.Uint64(), uint64(len(r))
s[i] = r[x%y]
}
return string(s)
}
================================================
FILE: authentication-service/cmd/api/main.go
================================================
package main
import (
"authentication/data"
"database/sql"
"fmt"
_ "github.com/jackc/pgconn"
_ "github.com/jackc/pgx/v4"
_ "github.com/jackc/pgx/v4/stdlib"
"log"
"net/http"
"os"
"time"
)
const webPort = "80"
var counts int64
type Config struct {
DB *sql.DB
Models data.Models
//Etcd *clientv3.Client
}
func main() {
log.Println("---------------------------------------------")
log.Println("Attempting to connect to Postgres...")
// connect to the database
conn := connectToDB()
if conn == nil {
log.Panic("can't connect to postgres!")
}
app := Config{
DB: conn,
Models: data.New(conn),
}
//app.registerService()
//defer app.Etcd.Close()
srv := &http.Server{
Addr: fmt.Sprintf(":%s", webPort),
Handler: app.routes(),
}
log.Printf("Starting authentication end service on port %s\n", webPort)
err := srv.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
func connectToDB() *sql.DB {
// connect to postgres
dsn := os.Getenv("DSN")
for {
connection, err := openDB(dsn)
if err != nil {
log.Println("Postgres not ready...")
counts++
} else {
log.Println("Connected to database!")
return connection
}
if counts > 10 {
log.Println(err)
return nil
}
log.Println("Backing off for two seconds...")
time.Sleep(2 * time.Second)
continue
}
}
func openDB(dsn string) (*sql.DB, error) {
db, err := sql.Open("pgx", dsn)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}
================================================
FILE: authentication-service/cmd/api/routes.go
================================================
package main
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"net/http"
)
func (app *Config) routes() http.Handler {
mux := chi.NewRouter()
// specify who is allowed to connect to our API service
mux.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
mux.Use(middleware.Heartbeat("/ping"))
mux.Post("/authenticate", app.Authenticate)
return mux
}
================================================
FILE: authentication-service/data/models.go
================================================
package data
import (
"context"
"database/sql"
"errors"
"log"
"time"
"golang.org/x/crypto/bcrypt"
)
const dbTimeout = time.Second * 3
var db *sql.DB
// New is the function used to create an instance of the data package. It returns the type
// Model, which embeds all the types we want to be available to our application.
func New(dbPool *sql.DB) Models {
db = dbPool
return Models{
User: User{},
}
}
// Models is the type for this package. Note that any model that is included as a member
// in this type is available to us throughout the application, anywhere that the
// app variable is used, provided that the model is also added in the New function.
type Models struct {
User User
}
// User is the structure which holds one user from the database.
type User struct {
ID int `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Password string `json:"-"`
Active int `json:"active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// GetAll returns a slice of all users, sorted by last name
func (u *User) GetAll() ([]*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
from users order by last_name`
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
var user User
err := rows.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
log.Println("Error scanning", err)
return nil, err
}
users = append(users, &user)
}
return users, nil
}
// GetByEmail returns one user by email
func (u *User) GetByEmail(email string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
var user User
row := db.QueryRowContext(ctx, query, email)
err := row.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
return nil, err
}
return &user, nil
}
// GetOne returns one user by id
func (u *User) GetOne(id int) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
var user User
row := db.QueryRowContext(ctx, query, id)
err := row.Scan(
&user.ID,
&user.Email,
&user.FirstName,
&user.LastName,
&user.Password,
&user.Active,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
return nil, err
}
return &user, nil
}
// Update updates one user in the database, using the information
// stored in the receiver u
func (u *User) Update() error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
stmt := `update users set
email = $1,
first_name = $2,
last_name = $3,
user_active = $4,
updated_at = $5
where id = $6
`
_, err := db.ExecContext(ctx, stmt,
u.Email,
u.FirstName,
u.LastName,
u.Active,
time.Now(),
u.ID,
)
if err != nil {
return err
}
return nil
}
// Delete deletes one user from the database, by User.ID
func (u *User) Delete() error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
stmt := `delete from users where id = $1`
_, err := db.ExecContext(ctx, stmt, u.ID)
if err != nil {
return err
}
return nil
}
// DeleteByID deletes one user from the database, by ID
func (u *User) DeleteByID(id int) error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
stmt := `delete from users where id = $1`
_, err := db.ExecContext(ctx, stmt, id)
if err != nil {
return err
}
return nil
}
// Insert inserts a new user into the database, and returns the ID of the newly inserted row
func (u *User) Insert(user User) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
if err != nil {
return 0, err
}
var newID int
stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
values ($1, $2, $3, $4, $5, $6, $7) returning id`
err = db.QueryRowContext(ctx, stmt,
user.Email,
user.FirstName,
user.LastName,
hashedPassword,
user.Active,
time.Now(),
time.Now(),
).Scan(&newID)
if err != nil {
return 0, err
}
return newID, nil
}
// ResetPassword is the method we will use to change a user's password.
func (u *User) ResetPassword(password string) error {
ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
defer cancel()
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return err
}
stmt := `update users set password = $1 where id = $2`
_, err = db.ExecContext(ctx, stmt, hashedPassword, u.ID)
if err != nil {
return err
}
return nil
}
// PasswordMatches uses Go's bcrypt package to compare a user supplied password
// with the hash we have stored for a given user in the database. If the password
// and hash match, we return true; otherwise, we return false.
func (u *User) PasswordMatches(plainText string) (bool, error) {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(plainText))
if err != nil {
switch {
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
// invalid password
return false, nil
default:
return false, err
}
}
return true, nil
}
================================================
FILE: authentication-service/go.mod
================================================
module authentication
go 1.18
require (
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/jackc/pgconn v1.14.0
github.com/jackc/pgx/v4 v4.18.1
go.etcd.io/etcd/client/v3 v3.5.2
golang.org/x/crypto v0.6.0
)
require (
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20220328150716-24ca77f39d1f // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
================================================
FILE: authentication-service/go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220328150716-24ca77f39d1f h1:9ug+SpnUXKl5LogY3yp9GHWUjUDCnFv4NjiP7yxS6Q4=
google.golang.org/genproto v0.0.0-20220328150716-24ca77f39d1f/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
================================================
FILE: authentication-service/users.sql
================================================
--
-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
CREATE SEQUENCE public.user_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.user_id_seq OWNER TO postgres;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: users; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.users (
id integer DEFAULT nextval('public.user_id_seq'::regclass) NOT NULL,
email character varying(255),
first_name character varying(255),
last_name character varying(255),
password character varying(60),
user_active integer DEFAULT 0,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
ALTER TABLE public.users OWNER TO postgres;
--
-- Name: user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
--
SELECT pg_catalog.setval('public.user_id_seq', 1, true);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
INSERT INTO "public"."users"("email","first_name","last_name","password","user_active","created_at","updated_at")
VALUES
(E'admin@example.com',E'Admin',E'User',E'$2a$12$1zGLuYDDNvATh4RA4avbKuheAMpb1svexSzrQm7up.bnpwQHs0jNe',1,E'2022-03-14 00:00:00',E'2022-03-14 00:00:00');
================================================
FILE: authentication-service.dockerfile
================================================
FROM alpine:latest
RUN mkdir /app
COPY authentication-service/authApp /app
# Run the server executable
CMD [ "/app/authApp" ]
================================================
FILE: broker-service/Makefile
================================================
gen:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs/logs.proto
================================================
FILE: broker-service/cmd/api/discover.go
================================================
package main
import (
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
"time"
)
//func (app *Config) getServiceURLs() {
// kv := clientv3.NewKV(app.Etcd)
// app.MailServiceURLs = make(map[string]string)
// app.LogServiceURLs = make(map[string]string)
// app.AuthServiceURLs = make(map[string]string)
//
// prefixes := []string{"/mail/", "/logger/", "/auth/"}
//
// // range through all the services we want to discover
// for _, curPrefix := range prefixes {
// getResp, err := kv.Get(context.TODO(), curPrefix, clientv3.WithPrefix())
// if err != nil {
// log.Println(err)
// }
//
// for _, k := range getResp.Kvs {
// //log.Println("Key", string(k.Key))
// //log.Println("Adding", string(k.Value), "to", curPrefix, "service map; key was", string(k.Key))
// switch curPrefix {
// case "/mail/":
// app.MailServiceURLs[string(k.Value)] = ""
// case "/logger/":
// app.LogServiceURLs[string(k.Value)] = ""
// case "/auth/":
// app.AuthServiceURLs[string(k.Value)] = ""
// }
// }
// }
//}
// watchEtcd runs in the background, looking for changes in etcd. When it finds changes
// hosts, it updates the appropriate map in the *Config receiver.
//func (app *Config) watchEtcd() {
// for {
// // watch for service changes
// watchKey := app.Etcd.Watch(context.Background(), "/mail/", clientv3.WithPrefix())
// for resp := range watchKey {
// for _, item := range resp.Events {
// // get our values as strings so that we can work with them
// eventType := item.Type.String()
// key := string(item.Kv.Key)
// value := string(item.Kv.Value)
// var deleteURL = false
// if strings.Contains(eventType, "DELETE") {
// deleteURL = true
// }
//
// // add to or remove from service maps (using url as key, and empty string as value)
// switch {
// case strings.HasPrefix(key, "mail"):
// // mail
// if deleteURL {
// log.Println("Removing", value, "from mail service map")
// delete(app.MailServiceURLs, key)
// } else {
// log.Println("Adding", value, "to mail service map")
// app.MailServiceURLs[value] = ""
// }
//
// case strings.HasPrefix(key, "logger"):
// // logger
// if deleteURL {
// delete(app.LogServiceURLs, key)
// } else {
// app.LogServiceURLs[value] = ""
// }
//
// case strings.HasPrefix(key, "auth"):
// // authentication
// if deleteURL {
// delete(app.AuthServiceURLs, key)
// } else {
// app.AuthServiceURLs[value] = ""
// }
// }
// }
// }
// }
//}
// GetServiceURL will get a service's url from those listed as available in etcd
//func (app *Config) GetServiceURL(serviceType string) string {
// var serviceURL string
//
// // get service URL from etcd
// switch serviceType {
// case "mail":
// serviceURL = getUrlFromMap(app.MailServiceURLs)
// case "logger":
// serviceURL = getUrlFromMap(app.LogServiceURLs)
// case "auth":
// serviceURL = getUrlFromMap(app.AuthServiceURLs)
// }
//
// return serviceURL
//}
// getUrlFromMap returns a random value from available urls in
// service maps. Since maps are never guaranteed to be in the same order,
// grabbing the first value is sufficient for our purposes.
func getUrlFromMap(m map[string]string) string {
var u string
for k := range m {
u = k
break
}
return u
}
// connectToEtcd tries to connect to etcd, for up to 30 seconds
func connectToEtcd() (*clientv3.Client, error) {
var cli *clientv3.Client
var counts = 0
for {
c, err := clientv3.New(clientv3.Config{Endpoints: []string{"etcd:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
fmt.Println("etcd not ready...")
counts++
} else {
fmt.Println()
cli = c
break
}
if counts > 15 {
return nil, err
}
fmt.Println("Backing off for 2 seconds...")
time.Sleep(2 * time.Second)
continue
}
fmt.Println("Connected to etcd!")
return cli, nil
}
================================================
FILE: broker-service/cmd/api/handlers.go
================================================
package main
import (
"broker/event"
"broker/logs"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"net/http"
"net/rpc"
"time"
)
const loggerGRPCAddress = "logger-service:50001"
// Payload is the type for data we push into RabbitMQ
type Payload struct {
Name string `json:"name"`
Data any `json:"data"`
}
// RequestPayload is the type describing the data that we received
// from the user's browser. We embed a custom type for each of the
// possible payloads (mail, auth, and log).
type RequestPayload struct {
Action string `json:"action"`
Mail MailPayload `json:"mail,omitempty"`
Auth AuthPayload `json:"auth,omitempty"`
Log LogPayload `json:"log,omitempty"`
}
// AuthPayload is the type embedded in RequestPayload for auth
type AuthPayload struct {
Email string `json:"email"`
Password string `json:"password"`
}
// LogPayload is the type embedded in RequestPayload for logging
type LogPayload struct {
Name string `json:"name"`
Data string `json:"data"`
}
// MailPayload is the type embedded in RequestPayload for sending email
type MailPayload struct {
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
Message string `json:"message"`
}
// Broker is a simple test handler for the broker
func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
err := app.pushToQueue("broker_hit", r.RemoteAddr)
if err != nil {
log.Println(err)
}
var payload jsonResponse
payload.Message = "Received request"
out, _ := json.MarshalIndent(payload, "", "\t")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
_, _ = w.Write(out)
}
// HandleSubmission handles a JSON payload that describes an action to take,
// processes it, and sends it where it needs to go
func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Request) {
var requestPayload RequestPayload
err := app.readJSON(w, r, &requestPayload)
if err != nil {
_ = app.errorJSON(w, err)
return
}
switch requestPayload.Action {
case "mail":
app.sendMail(w, requestPayload.Mail)
case "auth":
app.authenticate(w, requestPayload.Auth)
case "log":
app.logItemViaRPC(w, requestPayload.Log)
default:
_ = app.errorJSON(w, errors.New("unknown action"))
}
}
func (app *Config) logViaJSON(w http.ResponseWriter, entry LogPayload) {
jsonData, _ := json.MarshalIndent(entry, "", "\t")
logServiceURL := "http://logger-service/log"
request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
defer response.Body.Close()
// make sure we get back the right status code
if response.StatusCode != http.StatusAccepted {
_ = app.errorJSON(w, errors.New("error calling logger service"), http.StatusBadRequest)
return
}
// send json back to our end user
var payload jsonResponse
payload.Error = false
payload.Message = "Logged!"
_ = app.writeJSON(w, http.StatusAccepted, payload)
}
// sendMail sends an email through the mail-service. It receives a json payload
// of type requestPayload, with MailPayload embedded.
func (app *Config) sendMail(w http.ResponseWriter, msg MailPayload) {
jsonData, _ := json.MarshalIndent(msg, "", "\t")
// call the mail service; we need a request, so let's build one, and populate
// its body with the jsonData we just created. First we get the correct server
// to call from our service map.
//mailServiceURL := fmt.Sprintf("http://%s/send", app.GetServiceURL("mail"))
mailServiceURL := fmt.Sprintf("http://%s/send", "mail-service")
// now post to the mail service
request, err := http.NewRequest("POST", mailServiceURL, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
defer response.Body.Close()
// make sure we get back the right status code
if response.StatusCode != http.StatusAccepted {
_ = app.errorJSON(w, errors.New("error calling mail service"), http.StatusBadRequest)
return
}
// send json back to our end user
var payload jsonResponse
payload.Error = false
payload.Message = "Message sent to " + msg.To
out, _ := json.MarshalIndent(payload, "", "\t")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
_, _ = w.Write(out)
}
// authenticate tries to log a user in through the authentication-service. It receives a json payload
// of type requestPayload, with AuthPayload embedded.
func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
// create json we'll send to the authentication-service
jsonData, _ := json.MarshalIndent(a, "", "\t")
// call the authentication-service; we need a request, so let's build one, and populate
// its body with the jsonData we just created. First we get the correct url for our
// auth service from our service map.
//authServiceURL := fmt.Sprintf("http://%s/authenticate", app.GetServiceURL("auth"))
authServiceURL := fmt.Sprintf("http://%s/authenticate", "authentication-service")
// now build the request and set header
request, err := http.NewRequest("POST", authServiceURL, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
// call the service
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
defer response.Body.Close()
// make sure we get back the right status code
if response.StatusCode == http.StatusUnauthorized {
_ = app.errorJSON(w, errors.New("invalid credentials"), http.StatusUnauthorized)
return
} else if response.StatusCode != http.StatusAccepted {
_ = app.errorJSON(w, errors.New("error calling auth service"), http.StatusBadRequest)
return
}
// create variable we'll read the response.Body from the authentication-service into
var jsonFromService jsonResponse
// decode the json we get from the authentication-service into our variable
err = json.NewDecoder(response.Body).Decode(&jsonFromService)
if err != nil {
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
// did not authenticate successfully
if jsonFromService.Error {
// log it
_ = app.pushToQueue("authentication", fmt.Sprintf("invalid login for %s", a.Email))
// send error JSON back
_ = app.errorJSON(w, err, http.StatusUnauthorized)
return
}
// valid login, so send it to the logger service via RabbitMQ
_ = app.pushToQueue("authentication", fmt.Sprintf("valid login for %s", a.Email))
// send json back to our end user, with user info embedded
var payload jsonResponse
payload.Error = false
payload.Message = "Authenticated!"
payload.Data = jsonFromService.Data
_ = app.writeJSON(w, http.StatusAccepted, payload)
}
// logItem logs an event using the logger-service. It makes the call by pushing the data to RabbitMQ.
func (app *Config) logItem(w http.ResponseWriter, l LogPayload) {
err := app.pushToQueue(l.Name, l.Data)
if err != nil {
log.Println(err)
_ = app.errorJSON(w, err)
}
// send json back to our end user
var payload jsonResponse
payload.Error = false
payload.Message = "logged"
_ = app.writeJSON(w, http.StatusAccepted, payload)
}
type RPCPayload struct {
Name string
Data string
}
func (app *Config) logItemViaRPC(w http.ResponseWriter, l LogPayload) {
client, err := rpc.Dial("tcp", "logger-service:5001")
if err != nil {
app.errorJSON(w, err)
return
}
rpcPayload := RPCPayload{
Name: l.Name,
Data: l.Data,
}
var result string
err = client.Call("RPCServer.LogInfo", rpcPayload, &result)
if err != nil {
app.errorJSON(w, err)
return
}
payload := jsonResponse{
Error: false,
Message: result,
}
app.writeJSON(w, http.StatusAccepted, payload)
}
// pushToQueue pushes a message into RabbitMQ
func (app *Config) pushToQueue(name, msg string) error {
emitter, err := event.NewEventEmitter(app.Rabbit)
if err != nil {
log.Println(err)
return err
}
payload := Payload{
Name: name,
Data: msg,
}
j, _ := json.MarshalIndent(&payload, "", " ")
err = emitter.Push(string(j), "log.INFO")
if err != nil {
return err
}
return nil
}
// LogViaGRPC takes a JSON payload and logs it using gRPC as the transport
func (app *Config) LogViaGRPC(w http.ResponseWriter, r *http.Request) {
var requestPayload RequestPayload
err := app.readJSON(w, r, &requestPayload)
if err != nil {
_ = app.errorJSON(w, err)
return
}
conn, err := grpc.Dial(loggerGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
if err != nil {
_ = app.errorJSON(w, err)
return
}
defer conn.Close()
c := logs.NewLogServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = c.WriteLog(ctx, &logs.LogRequest{
LogEntry: &logs.Log{
Name: requestPayload.Log.Name,
Data: requestPayload.Log.Data,
},
})
if err != nil {
_ = app.errorJSON(w, err)
return
}
var payload jsonResponse
payload.Error = false
payload.Message = "logged"
_ = app.writeJSON(w, http.StatusAccepted, payload)
}
================================================
FILE: broker-service/cmd/api/helpers.go
================================================
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
)
type jsonResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
// readJSON tries to read the body of a request and converts it into JSON
func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1048576 // one megabyte
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
err := dec.Decode(data)
if err != nil {
return err
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must have only a single json value")
}
return nil
}
// writeJSON takes a response status code and arbitrary data and writes a json response to the client
func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
out, err := json.Marshal(data)
if err != nil {
return err
}
if len(headers) > 0 {
for key, value := range headers[0] {
w.Header()[key] = value
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(out)
if err != nil {
return err
}
return nil
}
// errorJSON takes an error, and optionally a response status code, and generates and sends
// a json error response
func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
statusCode := http.StatusBadRequest
if len(status) > 0 {
statusCode = status[0]
}
var payload jsonResponse
payload.Error = true
payload.Message = err.Error()
return app.writeJSON(w, statusCode, payload)
}
================================================
FILE: broker-service/cmd/api/main.go
================================================
package main
import (
"errors"
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
clientv3 "go.etcd.io/etcd/client/v3"
"log"
"net/http"
"os"
"time"
)
// webPort the port that we listen on for api calls
const webPort = "80"
// Config is the type we'll use as a receiver to share application
// configuration around our app.
type Config struct {
Rabbit *amqp.Connection
Etcd *clientv3.Client
LogServiceURLs map[string]string
//MailServiceURLs map[string]string
//AuthServiceURLs map[string]string
}
func main() {
// don't continue until rabbitmq is ready
rabbitConn, err := connectToRabbit()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer rabbitConn.Close()
// don't continue until etcd is ready
//etcConn, err := connectToEtcd()
//if err != nil {
// fmt.Println(err)
// os.Exit(1)
//}
//defer etcConn.Close()
app := Config{
Rabbit: rabbitConn,
//Etcd: etcConn,
}
// get service urls
//app.getServiceURLs()
// watch service urls
//go app.watchEtcd()
log.Println("Starting broker service on port", webPort)
// define the http server
srv := &http.Server{
Addr: fmt.Sprintf(":%s", webPort),
Handler: app.routes(),
}
// start the server
err = srv.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
// connectToRabbit tries to connect to RabbitMQ, for up to 30 seconds
func connectToRabbit() (*amqp.Connection, error) {
var rabbitConn *amqp.Connection
var counts int64
var rabbitURL = os.Getenv("RABBIT_URL")
for {
connection, err := amqp.Dial(rabbitURL)
if err != nil {
fmt.Println("rabbitmq not ready...")
counts++
} else {
fmt.Println()
rabbitConn = connection
break
}
if counts > 15 {
fmt.Println(err)
return nil, errors.New("cannot connect to rabbit")
}
fmt.Println("Backing off for 2 seconds...")
time.Sleep(2 * time.Second)
continue
}
fmt.Println("Connected to RabbitMQ!")
return rabbitConn, nil
}
================================================
FILE: broker-service/cmd/api/routes.go
================================================
package main
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"net/http"
)
func (app *Config) routes() http.Handler {
mux := chi.NewRouter()
// specify who is allowed to connect to our API service
mux.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"https://*", "http://*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
// a heartbeat route, to ensure things are up
mux.Use(middleware.Heartbeat("/ping"))
// this route is just to ensure things work, and is never
// used after that
mux.Get("/", app.Broker)
mux.Post("/", app.Broker)
// grpc route
mux.Post("/log-grpc", app.LogViaGRPC)
// a route for everything
mux.Post("/handle", app.HandleSubmission)
return mux
}
================================================
FILE: broker-service/event/emitter.go
================================================
package event
import (
"context"
"log"
amqp "github.com/rabbitmq/amqp091-go"
)
// Emitter for publishing AMQP events
type Emitter struct {
connection *amqp.Connection
}
func (e *Emitter) setup() error {
channel, err := e.connection.Channel()
if err != nil {
panic(err)
}
defer channel.Close()
return declareExchange(channel)
}
// Push (Publish) a specified message to the AMQP exchange
func (e *Emitter) Push(event string, severity string) error {
channel, err := e.connection.Channel()
if err != nil {
return err
}
defer channel.Close()
log.Println("Pushing to", getExchangeName())
err = channel.PublishWithContext(
context.Background(),
getExchangeName(),
severity,
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(event),
},
)
if err != nil {
log.Println(err)
return err
}
log.Printf("Sending message: %s -> %s", event, getExchangeName())
return nil
}
// NewEventEmitter returns a new event.Emitter object
// ensuring that the object is initialised, without error
func NewEventEmitter(conn *amqp.Connection) (Emitter, error) {
emitter := Emitter{
connection: conn,
}
err := emitter.setup()
if err != nil {
return Emitter{}, err
}
return emitter, nil
}
================================================
FILE: broker-service/event/event.go
================================================
package event
import (
amqp "github.com/rabbitmq/amqp091-go"
)
func getExchangeName() string {
return "logs_topic"
}
func declareExchange(ch *amqp.Channel) error {
return ch.ExchangeDeclare(
getExchangeName(), // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
}
================================================
FILE: broker-service/go.mod
================================================
module broker
go 1.18
require (
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/rabbitmq/amqp091-go v1.7.0
go.etcd.io/etcd/client/v3 v3.5.7
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
)
require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 // indirect
)
================================================
FILE: broker-service/go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rabbitmq/amqp091-go v1.3.0 h1:A/QuHiNw7LMCJsxx9iZn5lrIz6OrhIn7Dfk5/1YatWM=
github.com/rabbitmq/amqp091-go v1.3.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rabbitmq/amqp091-go v1.3.2 h1:zezKg1S58+q/9Ej7DIqFL6TP6NGMyGPb4ykEm4n94cY=
github.com/rabbitmq/amqp091-go v1.3.2/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rabbitmq/amqp091-go v1.7.0 h1:V5CF5qPem5OGSnEo8BoSbsDGwejg6VUJsKEdneaoTUo=
github.com/rabbitmq/amqp091-go v1.7.0/go.mod h1:wfClAtY0C7bOHxd3GjmF26jEHn+rR/0B3+YV+Vn9/NI=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb h1:0m9wktIpOxGw+SSKmydXWB3Z3GTfcPP6+q75HCQa6HI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220328150716-24ca77f39d1f h1:9ug+SpnUXKl5LogY3yp9GHWUjUDCnFv4NjiP7yxS6Q4=
google.golang.org/genproto v0.0.0-20220328150716-24ca77f39d1f/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 h1:/cadn7taPtPlCgiWNetEPsle7jgnlad2R7gR5MXB6dM=
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
================================================
FILE: broker-service/logs/logs.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.4
// source: logs/logs.proto
package logs
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Log struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *Log) Reset() {
*x = Log{}
if protoimpl.UnsafeEnabled {
mi := &file_logs_logs_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Log) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Log) ProtoMessage() {}
func (x *Log) ProtoReflect() protoreflect.Message {
mi := &file_logs_logs_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Log.ProtoReflect.Descriptor instead.
func (*Log) Descriptor() ([]byte, []int) {
return file_logs_logs_proto_rawDescGZIP(), []int{0}
}
func (x *Log) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Log) GetData() string {
if x != nil {
return x.Data
}
return ""
}
type LogRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
LogEntry *Log `protobuf:"bytes,1,opt,name=logEntry,proto3" json:"logEntry,omitempty"`
}
func (x *LogRequest) Reset() {
*x = LogRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_logs_logs_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LogRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LogRequest) ProtoMessage() {}
func (x *LogRequest) ProtoReflect() protoreflect.Message {
mi := &file_logs_logs_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LogRequest.ProtoReflect.Descriptor instead.
func (*LogRequest) Descriptor() ([]byte, []int) {
return file_logs_logs_proto_rawDescGZIP(), []int{1}
}
func (x *LogRequest) GetLogEntry() *Log {
if x != nil {
return x.LogEntry
}
return nil
}
type LogResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
}
func (x *LogResponse) Reset() {
*x = LogResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_logs_logs_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LogResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LogResponse) ProtoMessage() {}
func (x *LogResponse) ProtoReflect() protoreflect.Message {
mi := &file_logs_logs_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LogResponse.ProtoReflect.Descriptor instead.
func (*LogResponse) Descriptor() ([]byte, []int) {
return file_logs_logs_proto_rawDescGZIP(), []int{2}
}
func (x *LogResponse) GetResult() string {
if x != nil {
return x.Result
}
return ""
}
var File_logs_logs_proto protoreflect.FileDescriptor
var file_logs_logs_proto_rawDesc = []byte{
0x0a, 0x0f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f,
0x67, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x25, 0x0a, 0x0b, 0x4c,
0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x32, 0x3f, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x31, 0x0a, 0x08, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x12, 0x10, 0x2e, 0x6c,
0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11,
0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x42, 0x07, 0x5a, 0x05, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_logs_logs_proto_rawDescOnce sync.Once
file_logs_logs_proto_rawDescData = file_logs_logs_proto_rawDesc
)
func file_logs_logs_proto_rawDescGZIP() []byte {
file_logs_logs_proto_rawDescOnce.Do(func() {
file_logs_logs_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_logs_proto_rawDescData)
})
return file_logs_logs_proto_rawDescData
}
var file_logs_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_logs_logs_proto_goTypes = []interface{}{
(*Log)(nil), // 0: logs.Log
(*LogRequest)(nil), // 1: logs.LogRequest
(*LogResponse)(nil), // 2: logs.LogResponse
}
var file_logs_logs_proto_depIdxs = []int32{
0, // 0: logs.LogRequest.logEntry:type_name -> logs.Log
1, // 1: logs.LogService.WriteLog:input_type -> logs.LogRequest
2, // 2: logs.LogService.WriteLog:output_type -> logs.LogResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_logs_logs_proto_init() }
func file_logs_logs_proto_init() {
if File_logs_logs_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_logs_logs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Log); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_logs_logs_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LogRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_logs_logs_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LogResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_logs_logs_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_logs_logs_proto_goTypes,
DependencyIndexes: file_logs_logs_proto_depIdxs,
MessageInfos: file_logs_logs_proto_msgTypes,
}.Build()
File_logs_logs_proto = out.File
file_logs_logs_proto_rawDesc = nil
file_logs_logs_proto_goTypes = nil
file_logs_logs_proto_depIdxs = nil
}
================================================
FILE: broker-service/logs/logs.proto
================================================
syntax = "proto3";
package logs;
option go_package = "/logs";
message Log{
string name = 1;
string data =2;
}
message LogRequest {
Log logEntry = 1;
}
message LogResponse{
string result = 1;
}
service LogService{
rpc WriteLog(LogRequest) returns (LogResponse) {}
}
================================================
FILE: broker-service/logs/logs_grpc.pb.go
================================================
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.19.4
// source: logs/logs.proto
package logs
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// LogServiceClient is the client API for LogService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type LogServiceClient interface {
WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
}
type logServiceClient struct {
cc grpc.ClientConnInterface
}
func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
return &logServiceClient{cc}
}
func (c *logServiceClient) WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
out := new(LogResponse)
err := c.cc.Invoke(ctx, "/logs.LogService/WriteLog", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LogServiceServer is the server API for LogService service.
// All implementations must embed UnimplementedLogServiceServer
// for forward compatibility
type LogServiceServer interface {
WriteLog(context.Context, *LogRequest) (*LogResponse, error)
mustEmbedUnimplementedLogServiceServer()
}
// UnimplementedLogServiceServer must be embedded to have forward compatible implementations.
type UnimplementedLogServiceServer struct {
}
func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogRequest) (*LogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method WriteLog not implemented")
}
func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceServer() {}
// UnsafeLogServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to LogServiceServer will
// result in compilation errors.
type UnsafeLogServiceServer interface {
mustEmbedUnimplementedLogServiceServer()
}
func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceServer) {
s.RegisterService(&LogService_ServiceDesc, srv)
}
func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LogRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LogServiceServer).WriteLog(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/logs.LogService/WriteLog",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LogServiceServer).WriteLog(ctx, req.(*LogRequest))
}
return interceptor(ctx, in, info, handler)
}
// LogService_ServiceDesc is the grpc.ServiceDesc for LogService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var LogService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "logs.LogService",
HandlerType: (*LogServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "WriteLog",
Handler: _LogService_WriteLog_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "logs/logs.proto",
}
================================================
FILE: broker-service.dockerfile
================================================
# The base go-image
FROM alpine:latest
RUN mkdir /app
COPY broker-service/brokerApp /app
# Run the server executable
CMD [ "/app/brokerApp" ]
================================================
FILE: caddy.dockerfile
================================================
FROM caddy:2.4.6-alpine
COPY Caddyfile /etc/caddy/Caddyfile
================================================
FILE: db-data/.gitignore
================================================
*
!*/
!.gitignore
!.gitkeep
================================================
FILE: db-data/.gitkeep
================================================
================================================
FILE: docker-compose.yml
================================================
version: '3'
services:
# # broker-service - main entry point; we call this from the front end
# broker-service:
# build:
# context: .
# dockerfile: ./broker-service.dockerfile
# restart: always
# ports:
# - "8080:80"
# deploy:
# mode: replicated
# replicas: 1
# environment:
# RABBIT_URL: "amqp://guest:guest@rabbitmq"
#
# # listener-service - watches rabbitmq for messages
# listener-service:
# build:
# context: .
# dockerfile: ./listener-service.dockerfile
# deploy:
# mode: replicated
# replicas: 1
# environment:
# RABBIT_URL: "amqp://guest:guest@rabbitmq"
#
# # authentication-service - handles user auth
# authentication-service:
# build:
# context: .
# dockerfile: ./authentication-service.dockerfile
# restart: always
# ports:
# - "8081:80"
# deploy:
# mode: replicated
# replicas: 1
# environment:
# DSN: "host=postgres port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5"
#
# # logger-service: a service to store logs
# logger-service:
# build:
# context: .
# dockerfile: ./logger-service.dockerfile
# restart: always
# ports:
# - "8082:80"
# deploy:
# mode: replicated
# replicas: 1
# volumes:
# - ./logger-service/templates/:/app/templates
#
# # mail-service - handles sending mail
# mail-service:
# build:
# context: .
# dockerfile: ./mail-service.dockerfile
# restart: always
# deploy:
# mode: replicated
# replicas: 1
# environment:
# MAIL_DOMAIN: localhost
# MAIL_HOST: mailhog
# MAIL_PORT: 1025
# MAIL_ENCRYPTION: none
# MAIL_USERNAME: ""
# MAIL_PASSWORD: ""
# FROM_NAME: "John Smith"
# FROM_ADDRESS: john.smith@example.com
#
# # rabbitmq: the rabbitmq server
# rabbitmq:
# image: 'rabbitmq:3.9-alpine'
# ports:
# - "5672:5672"
# deploy:
# mode: replicated
# replicas: 1
# volumes:
# - ./db-data/rabbitmq/:/var/lib/rabbitmq/
#
# # mailhog: a fake smtp server with a web interface
# mailhog:
# image: 'mailhog/mailhog:latest'
# ports:
# - "1025:1025"
# - "8025:8025"
# deploy:
# mode: replicated
# replicas: 1
#
# mongo: start MongoDB and ensure that data is stored to a mounted volume
mongo:
image: 'mongo:4.2.17-bionic'
ports:
- "27017:27017"
# restart: always
deploy:
mode: replicated
replicas: 1
environment:
MONGO_INITDB_DATABASE: logs
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- ./db-data/mongo/:/data/db
# postgres: start Postgres, and ensure that data is stored to a mounted volume
postgres:
image: 'postgres:14.2'
ports:
- "5432:5432"
restart: always
deploy:
mode: replicated
replicas: 1
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: users
volumes:
- ./db-data/postgres/:/var/lib/postgresql/data/
# etcd: start etcd server
# etcd:
# image: docker.io/bitnami/etcd:3
# environment:
# - ALLOW_NONE_AUTHENTICATION=yes
# deploy:
# mode: replicated
# replicas: 1
# volumes:
# - ./db-data/etcd/:/bitnami/etcd
================================================
FILE: front-end/cmd/web/main.go
================================================
package main
import (
"embed"
"fmt"
"html/template"
"log"
"net/http"
)
func main() {
// the handler to display our page
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
render(w, "test.page.gohtml")
})
// start the web server
fmt.Println("Starting front end service on port 80")
err := http.ListenAndServe(":80", nil)
if err != nil {
log.Panic(err)
}
}
//go:embed templates
var templateFS embed.FS
// render generates a page of html from our template files
func render(w http.ResponseWriter, t string) {
// all the required templates for any page
partials := []string{
"templates/base.layout.gohtml",
"templates/header.partial.gohtml",
"templates/footer.partial.gohtml",
}
// append the template we received as a parameter
var templateSlice []string
templateSlice = append(templateSlice, fmt.Sprintf("templates/%s", t))
for _, x := range partials {
templateSlice = append(templateSlice, x)
}
// parse the templates
//tmpl, err := template.ParseFiles(templateSlice...)
tmpl, err := template.ParseFS(templateFS, templateSlice...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// execute the template
if err := tmpl.Execute(w, nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
================================================
FILE: front-end/cmd/web/templates/base.layout.gohtml
================================================
{{define "base" }}
<!doctype html>
<html lang="en">
{{template "header" .}}
<body>
{{block "content" .}}
{{end}}
{{block "js" .}}
{{end}}
{{template "footer" .}}
</body>
</html>
{{end}}
================================================
FILE: front-end/cmd/web/templates/footer.partial.gohtml
================================================
{{define "footer"}}
<div class="container">
<div class="row">
<div class="col text-center">
<hr>
<small class="text-muted">Copyright © GoCode.ca</small>
</div>
</div>
</div>
{{end}}
================================================
FILE: front-end/cmd/web/templates/header.partial.gohtml
================================================
{{define "header"}}
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Microservices in Go</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
{{end}}
================================================
FILE: front-end/cmd/web/templates/test.page.gohtml
================================================
{{template "base" .}}
{{define "content" }}
<div class="container">
<div class="row">
<div class="col">
<h1 class="mt-5">Test Go microservices</h1>
<hr>
<a id="brokerBtn" class="btn btn-outline-secondary" href="javascript:void(0)">Test broker</a>
<a id="authBrokerBtn" class="btn btn-outline-secondary" href="javascript:void(0)">Test auth</a>
<a id="mailBtn" class="btn btn-outline-secondary" href="javascript:void(0)">Test email</a>
<a id="logBtn" class="btn btn-outline-secondary" href="javascript:void(0)">Test log</a>
<a id="logGBtn" class="btn btn-outline-secondary" href="javascript:void(0)">Test gRPC log</a>
<div id="output" class="mt-5" style="outline: 1px solid silver; padding: 2em;">
<span class="text-muted">Output shows here...</span>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h4 class="mt-5">Sent</h4>
<div class="mt-1" style="outline: 1px solid silver; padding: 2em;">
<pre id="payload"><span class="text-muted">Nothing sent yet...</span></pre>
</div>
</div>
<div class="col">
<h4 class="mt-5">Received</h4>
<div class="mt-1" style="outline: 1px solid silver; padding: 2em;">
<pre id="received"><span class="text-muted">Nothing received yet...</span></pre>
</div>
</div>
</div>
</div>
{{end}}
{{define "js"}}
<script>
let brokerBtn = document.getElementById("brokerBtn");
let authBrokerBtn = document.getElementById("authBrokerBtn");
let output = document.getElementById("output");
let mailBtn = document.getElementById("mailBtn");
let logBtn = document.getElementById("logBtn");
let logGBtn = document.getElementById("logGBtn");
let sent = document.getElementById("payload")
let received = document.getElementById("received")
brokerBtn.addEventListener("click", function () {
started();
const body = {
method: 'POST',
}
// send request to main entrypoint for microservices
fetch("http:\/\/localhost:8080", body)
.then((response) => response.json())
.then((data) => {
sent.innerHTML = "empty post request";
received.innerHTML = JSON.stringify(data, undefined, 4);
if (data.error) {
console.log(data.error)
} else {
output.innerHTML += `<br><strong>Response from broker service</strong>: ${data.message}`;
}
ended();
})
.catch((error) => {
output.innerHTML += "<br><br><strong>Error: " + error;
})
})
mailBtn.addEventListener("click", function () {
started();
const payload = {
action: "mail",
mail: {
from: "me@here.com",
to: "you@there.com",
subject: "Test Email",
message: "Hello, world! This is my test message."
}
}
const headers = new Headers();
headers.append("Content-Type", "application/json");
const body = {
method: 'POST',
body: JSON.stringify(payload),
headers: headers,
}
// send request to main entrypoint for microservices
fetch("http:\/\/localhost:8080/handle", body)
.then((response) => response.json())
.then((data) => {
sent.innerHTML = JSON.stringify(payload, undefined, 4);
received.innerHTML = JSON.stringify(data, undefined, 4);
if (data.error) {
console.log(data.error)
output.innerHTML += "<br><br><strong>Error: " + data.message;
} else {
output.innerHTML += `<br><strong>Response from mail service</strong>: ${data.message}`;
}
ended();
})
.catch((error) => {
output.innerHTML += "<br><br><strong>Error: " + error;
})
})
authBrokerBtn.addEventListener("click", function () {
started();
const payload = {
action: "auth",
auth: {
email: "trevor.sawler@gmail.com",
password: "verysecret",
}
}
const headers = new Headers();
headers.append("Content-Type", "application/json");
const body = {
method: 'POST',
body: JSON.stringify(payload),
headers: headers,
}
fetch("http:\/\/localhost:8080/handle", body)
.then(response => response.json())
.then(data => {
sent.innerHTML = JSON.stringify(payload, undefined, 4);
received.innerHTML = JSON.stringify(data, undefined, 4);
if (data.error) {
output.innerHTML += `<br><strong>Error:</strong> ${data.message}`;
} else {
output.innerHTML += `<br><strong>Response from auth service:</strong> ${data.message}`;
output.innerHTML += `<br>Authenticated user ${data.data.first_name}`;
}
ended();
})
.catch(error => {
output.innerHTML += "<br><br><strong>Error: " + error;
})
})
logBtn.addEventListener("click", function () {
started();
const payload = {
action: "log",
log: {
name: "event",
data: "Some kind of data",
}
}
const headers = new Headers();
headers.append("Content-Type", "application/json");
const body = {
method: 'POST',
body: JSON.stringify(payload),
headers: headers,
}
fetch(`http://localhost:8080/handle`, body)
.then(response => response.json())
.then(data => {
sent.innerHTML = JSON.stringify(payload, undefined, 4);
received.innerHTML = JSON.stringify(data, undefined, 4);
if (data.error) {
output.innerHTML += `<br><strong>Error:</strong> ${data.message}`;
} else {
output.innerHTML += `<br><strong>Response from log service:</strong> ${data.message}`;
}
ended();
})
.catch(error => {
output.innerHTML += "<br><br><strong>Error: " + error;
})
})
logGBtn.addEventListener("click", function () {
started();
const payload = {
action: "log",
log: {
name: "event",
data: "Some kind of gRPC data",
}
}
const headers = new Headers();
headers.append("Content-Type", "application/json");
const body = {
method: 'POST',
body: JSON.stringify(payload),
headers: headers,
}
fetch(`http://localhost:8080/log-grpc`, body)
.then(response => response.json())
.then(data => {
sent.innerHTML = JSON.stringify(payload, undefined, 4);
received.innerHTML = JSON.stringify(data, undefined, 4);
if (data.error) {
output.innerHTML += `<br><strong>Error:</strong> ${data.message}`;
} else {
output.innerHTML += `<br><strong>Response from log service:</strong> ${data.message}`;
}
ended();
})
.catch(error => {
output.innerHTML += "<br><br><strong>Error: " + error;
})
})
function ended() {
let now = new Date();
output.innerHTML += `<br><strong class="text-danger">Ended:</strong> ${now}...<br>`;
}
function started() {
let now = new Date();
output.innerHTML = `<strong class="text-success">Started:</strong> ${now}...<br><em>Sending request...</em>`;
received.innerHTML = `<span class="text-muted">Nothing received yet...</span>`;
}
</script>
{{end}}
================================================
FILE: front-end/go.mod
================================================
module frontend
go 1.18
================================================
FILE: front-end.dockerfile
================================================
FROM alpine:latest
RUN mkdir /app
COPY front-end/frontEndLinux /app
# Run the server executable
CMD [ "/app/frontEndLinux" ]
================================================
FILE: ingress.yml
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: broker-service.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: broker-service
port:
number: 8080
================================================
FILE: k8s/authentication.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentication-service
spec:
replicas: 1
selector:
matchLabels:
app: authentication-service
template:
metadata:
labels:
app: authentication-service
spec:
containers:
- name: authentication-service
image: "tsawler/authentication-service:1.0.0"
env:
- name: DSN
value: "host=host.minikube.internal port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5"
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: authentication-service
spec:
selector:
app: authentication-service
ports:
- protocol: TCP
port: 80
targetPort: 80
================================================
FILE: k8s/broker.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: broker-service
spec:
replicas: 1
selector:
matchLabels:
app: broker-service
template:
metadata:
labels:
app: broker-service
spec:
containers:
- name: broker-service
image: "tsawler/broker-service:1.0.1"
env:
- name: RABBIT_URL
value: "amqp://guest:guest@rabbitmq"
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: broker-service
spec:
selector:
app: broker-service
ports:
- protocol: TCP
port: 80
name: web-port
targetPort: 80
================================================
FILE: k8s/listener.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: listener
spec:
replicas: 1
selector:
matchLabels:
app: listener
template:
metadata:
labels:
app: listener
spec:
containers:
- name: listener
image: "tsawler/listener-service:1.0.0"
env:
- name: RABBIT_URL
value: "amqp://guest:guest@rabbitmq"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: listener
spec:
selector:
app: listener
ports:
- protocol: TCP
port: 80
targetPort: 80
================================================
FILE: k8s/logger.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: logger-service
spec:
replicas: 2
selector:
matchLabels:
app: logger-service
template:
metadata:
labels:
app: logger-service
spec:
containers:
- name: logger
image: "tsawler/logger-service:1.0.1"
env:
- name: RABBIT_URL
value: "amqp://guest:guest@rabbitmq"
ports:
- containerPort: 80
- containerPort: 5001
- containerPort: 50001
---
apiVersion: v1
kind: Service
metadata:
name: logger-service
spec:
selector:
app: logger-service
ports:
- protocol: TCP
port: 80
name: web-port
targetPort: 80
- protocol: TCP
port: 5001
name: rpc-port
targetPort: 5001
- protocol:
port: 50001
name: grpc-port
targetPort: 50001
================================================
FILE: k8s/mail.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: mail-service
spec:
replicas: 1
selector:
matchLabels:
app: mail-service
template:
metadata:
labels:
app: mail-service
spec:
containers:
- name: mail-service
image: "tsawler/mail-service:1.0.0"
env:
- name: MAIL_DOMAIN
value: "localhost"
- name: MAIL_HOST
value: "mailhog"
- name: MAIL_PORT
value: "1025"
- name: MAIL_ENCRYPTION
value: "localhost"
- name: MAIL_USERNAME
value: ""
- name: MAIL_PASSWORD
value: ""
- name: FROM_NAME
value: "John Smith"
- name: FROM_ADDRESS
value: "john.smith@example.com"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: mail-service
spec:
selector:
app: mail-service
ports:
- protocol: TCP
port: 80
targetPort: 80
================================================
FILE: k8s/mailhog.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailhog
spec:
replicas: 1
selector:
matchLabels:
app: mailhog
template:
metadata:
labels:
app: mailhog
spec:
containers:
- name: mailhog
image: "mailhog/mailhog:latest"
ports:
- containerPort: 1025
- containerPort: 8025
---
apiVersion: v1
kind: Service
metadata:
name: mailhog
spec:
selector:
app: mailhog
ports:
- protocol: TCP
name: smtp-port
port: 1025
targetPort: 1025
- protocol: TCP
name: web-port
port: 8025
targetPort: 8025
================================================
FILE: k8s/mongo.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: "mongo:4.2.17-bionic"
env:
- name: MONGO_INITDB_DATABASE
value: "logs"
- name: MONGO_INITDB_ROOT_USERNAME
value: "admin"
- name: MONGO_INITDB_ROOT_PASSWORD
value: "password"
ports:
- containerPort: 27017
---
apiVersion: v1
kind: Service
metadata:
name: mongo
spec:
selector:
app: mongo
ports:
- protocol: TCP
name: main-port
port: 27017
targetPort: 27017
================================================
FILE: k8s/rabbitmq.yml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: "rabbitmq:3.9-alpine"
ports:
- containerPort: 5672
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
spec:
selector:
app: rabbitmq
ports:
- protocol: TCP
port: 5672
targetPort: 5672
================================================
FILE: k8s.md
================================================
# K8S
```
minikube start --nodes=2
minikube status
docker ps
kubectl get nodes
kubectl get pods -A
kubectl get pods
kubectl apply -f deployment.yml # or kubectl apply -f <folder>
kubectl get pods
kubectl get svc
kubectl get deployments
minikube dashboard
```
# hit the app
```
kubectl expose deployment broker --type=LoadBalancer --port=8080 --target-port=80
minikube tunnel
```
# stop service/app
```
kubectl delete deployments <deployment>
kubectl delete services <service>
```
# stop minikube
```
minikube stop
```
================================================
FILE: listener-service/go.mod
================================================
module github.com/tsawler/go-rabbit
go 1.18
require github.com/rabbitmq/amqp091-go v1.7.0
================================================
FILE: listener-service/go.sum
================================================
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rabbitmq/amqp091-go v1.3.0 h1:A/QuHiNw7LMCJsxx9iZn5lrIz6OrhIn7Dfk5/1YatWM=
github.com/rabbitmq/amqp091-go v1.3.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rabbitmq/amqp091-go v1.3.2 h1:zezKg1S58+q/9Ej7DIqFL6TP6NGMyGPb4ykEm4n94cY=
github.com/rabbitmq/amqp091-go v1.3.2/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rabbitmq/amqp091-go v1.7.0 h1:V5CF5qPem5OGSnEo8BoSbsDGwejg6VUJsKEdneaoTUo=
github.com/rabbitmq/amqp091-go v1.7.0/go.mod h1:wfClAtY0C7bOHxd3GjmF26jEHn+rR/0B3+YV+Vn9/NI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: listener-service/lib/event/consumer.go
================================================
package event
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"net/rpc"
amqp "github.com/rabbitmq/amqp091-go"
)
// Consumer is the type used for receiving AMPQ events
type Consumer struct {
conn *amqp.Connection
queueName string
}
// NewConsumer returns a new Consumer
func NewConsumer(conn *amqp.Connection) (Consumer, error) {
consumer := Consumer{
conn: conn,
}
err := consumer.setup()
if err != nil {
return Consumer{}, err
}
return consumer, nil
}
// setup opens a channel and declares the exchange
func (consumer *Consumer) setup() error {
channel, err := consumer.conn.Channel()
if err != nil {
return err
}
return declareExchange(channel)
}
// Payload is the type used for pushing events to RabbitMQ
type Payload struct {
Name string `json:"name"`
Data string `json:"data"`
}
// Listen will listen for all new queue publications
func (consumer *Consumer) Listen(topics []string) error {
ch, err := consumer.conn.Channel()
if err != nil {
return err
}
defer ch.Close()
q, err := declareRandomQueue(ch)
if err != nil {
return err
}
for _, s := range topics {
err = ch.QueueBind(
q.Name,
s,
getExchangeName(),
false,
nil,
)
if err != nil {
log.Println(err)
return err
}
}
messages, err := ch.Consume(q.Name, "", true, false, false, false, nil)
if err != nil {
return err
}
forever := make(chan bool)
go func() {
for d := range messages {
// get the JSON payload and unmarshal it into a variable
var payload Payload
_ = json.Unmarshal(d.Body, &payload)
// do something with the payload
go handlePayload(payload)
}
}()
log.Printf("[*] Waiting for message [Exchange, Queue][%s, %s].", getExchangeName(), q.Name)
<-forever
return nil
}
// handlePayload takes an action based on the name of an event in the queue
func handlePayload(payload Payload) {
// logic to process payload goes in here
switch payload.Name {
case "broker_hit":
// just a test to make sure everything works
res, err := rpcPushToLogger("LogInfo", payload)
if err != nil {
log.Println(err)
}
fmt.Println("Response from RPC:", res)
case "auth", "authentication":
// we are trying to authenticate someone
err := authenticate(payload)
if err != nil {
log.Println(err)
}
// you can have as many cases as you want here, but naturally you'll have to write the logic
// to connect to a given microservice
default:
// log whatever we get
res, err := rpcPushToLogger("LogInfo", payload)
if err != nil {
log.Println(err)
}
fmt.Println("Response from RPC:", res)
}
}
// rpcPushToLogger pushes data to the logger-service via RPC, where
// it gets stored into a mongo database
func rpcPushToLogger(function string, data any) (string, error) {
c, err := rpc.Dial("tcp", "logger-service:5001")
if err != nil {
log.Println(err)
return "", err
}
fmt.Println("Connected via rpc...")
var result string
err = c.Call("RPCServer."+function, data, &result)
if err != nil {
return "", err
}
return result, nil
}
// authenticate is a stub that we'll never actually use, but it is here
// as we get used to how to interact with services
func authenticate(payload Payload) error {
// TODO actually authenticate via JSON
log.Printf("Got payload of %v", payload)
return nil
}
func logEvent(entry Payload) error {
jsonData, _ := json.MarshalIndent(entry, "", "\t")
logServiceURL := "http://logger-service/log"
request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusAccepted {
return err
}
return nil
}
================================================
FILE: listener-service/lib/event/event.go
================================================
package event
import (
amqp "github.com/rabbitmq/amqp091-go"
)
func getExchangeName() string {
return "logs_topic"
}
// declareRandomQueue just creates a random queue
func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) {
return ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
}
// declareExchange creates an exchange, with a name
func declareExchange(ch *amqp.Channel) error {
return ch.ExchangeDeclare(
getExchangeName(), // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
}
================================================
FILE: listener-service/main.go
================================================
package main
import (
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/tsawler/go-rabbit/lib/event"
"log"
"math"
"os"
"time"
)
func main() {
// try to connect to RabbitMQ
rabbitConn, err := connect()
if err != nil {
log.Println(err)
os.Exit(1)
}
defer rabbitConn.Close()
// start listening for messages
log.Println("Listening for and consuming RabbitMQ messages...")
// create a new consumer
consumer, err := event.NewConsumer(rabbitConn)
if err != nil {
panic(err)
}
// consumer.Listen watches the queue and consumes events for all the provided topics.
err = consumer.Listen([]string{"log.INFO", "log.WARNING", "log.ERROR"})
if err != nil {
log.Println(err)
}
}
// connect tries to connect to RabbitMQ, and delays between attempts.
// If we can't connect after 5 tries (with increasing delays), return an error
func connect() (*amqp.Connection, error) {
var counts int64
var backOff = 1 * time.Second
var connection *amqp.Connection
var rabbitURL = os.Getenv("RABBIT_URL")
// don't continue until rabbitmq is ready
for {
c, err := amqp.Dial(rabbitURL)
if err != nil {
fmt.Println("RabbitMQ not yet ready...")
counts++
} else {
// we have a connection to rabbitmq, so set connection = c and break out of
// this loop
connection = c
fmt.Println()
break
}
if counts > 5 {
// if we can't connect after five tries, something is wrong...
fmt.Println(err)
return nil, err
}
fmt.Printf("Backing off for %d seconds...\n", int(math.Pow(float64(counts), 2)))
backOff = time.Duration(math.Pow(float64(counts), 2)) * time.Second
time.Sleep(backOff)
continue
}
return connection, nil
}
================================================
FILE: listener-service.dockerfile
================================================
FROM alpine:latest
RUN mkdir /app
COPY listener-service/listener /app
# Run the server executable
CMD [ "/app/listener" ]
================================================
FILE: logger-service/Makefile
================================================
gen:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs/logs.proto
================================================
FILE: logger-service/cmd/web/discover.go
================================================
package main
//// registerService registers the correct entry for this service in etcd
//func (app *Config) registerService() {
// // get a connection to etcd
// cli, _ := connectToEtcd()
// kv := clientv3.NewKV(cli)
//
// app.Etcd = cli
//
// // create a lease that lasts 10 seconds
// lease := clientv3.NewLease(cli)
// grantResp, err := lease.Grant(context.TODO(), 10)
// if err != nil {
// log.Println("Error creating lease", err)
// }
//
// // insert something with the lease
// _, err = kv.Put(context.TODO(), fmt.Sprintf("/logger/%s", app.randomString(32)), "logger-service", clientv3.WithLease(grantResp.ID))
// if err != nil {
// log.Println("Error inserting using lease", err)
// }
//
// // keep lease alive
// kalRes, err := lease.KeepAlive(context.TODO(), grantResp.ID)
// if err != nil {
// log.Println("Error with keepalive", err)
// }
// go app.listenToKeepAlive(kalRes)
//}
//
//// listenToKeepAlive consumes the responses from etcd, or the lease will not work as expected.
//// We don't have to do anything with them, but we have to consume them.
//func (app *Config) listenToKeepAlive(kalRes <-chan *clientv3.LeaseKeepAliveResponse) {
// defer func() {
// if r := recover(); r != nil {
// log.Println("Error", fmt.Sprintf("%v", r))
// }
// }()
//
// for {
// _ = <-kalRes
// }
//}
//
//// connectToEtcd tries to connect to etcd, for up to 30 seconds
//func connectToEtcd() (*clientv3.Client, error) {
// var cli *clientv3.Client
// var counts = 0
//
// for {
// c, err := clientv3.New(clientv3.Config{Endpoints: []string{"etcd:2379"},
// DialTimeout: 5 * time.Second,
// })
// if err != nil {
// fmt.Println("etcd not ready...")
// counts++
// } else {
// fmt.Println()
// cli = c
// break
// }
//
// if counts > 15 {
// return nil, err
// }
// fmt.Println("Backing off for 2 seconds...")
// time.Sleep(2 * time.Second)
// continue
// }
// log.Println("Connected to etcd!")
// return cli, nil
//}
================================================
FILE: logger-service/cmd/web/grpc.go
================================================
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
"log-service/data"
"log-service/logs"
"net"
)
// LogServer is type used for writing to the log via gRPC. Note that we embed the
// data.Models type, so we have access to Mongo.
type LogServer struct {
logs.UnimplementedLogServiceServer
Models data.Models
}
// WriteLog writes the log after receiving a call from a gRPC client. This function
// must exist, and is defined in logs/logs.proto, in the "service LogService" bit
// at the end of the file.
func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
input := req.GetLogEntry()
log.Println("Log:", input.Name, input.Data)
// write the log
logEntry := data.LogEntry{
Name: input.Name,
Data: input.Data,
}
err := l.Models.LogEntry.Insert(logEntry)
if err != nil {
res := &logs.LogResponse{Result: "failed:"}
return res, err
}
// return response
res := &logs.LogResponse{Result: "logged!"}
return res, nil
}
// gRPCListen starts the gRPC server
func (app *Config) gRPCListen() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", gRpcPort))
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
// register the service, handing it models (so we can write to the database)
logs.RegisterLogServiceServer(s, &LogServer{Models: app.Models})
log.Printf("gRPC server started at port %s", gRpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
================================================
FILE: logger-service/cmd/web/handlers.go
================================================
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/go-chi/chi/v5"
"io"
"log"
"net/http"
"time"
)
// authServiceURL is the url to the authentication service. Since we're using
// Docker, we specify the appropriate entry from docker-compose.yml
const authServiceURL = "http://authentication-service/authenticate"
// JSONPayload is the type for JSON posted to this API
type JSONPayload struct {
Name string `json:"name"`
Data string `json:"data"`
}
// WriteLog is the handler to accept a post request consisting of json payload,
// and then write it to Mongo
func (app *Config) WriteLog(w http.ResponseWriter, r *http.Request) {
// read json into var
var requestPayload JSONPayload
_ = app.readJSON(w, r, &requestPayload)
// insert the data
err := app.logEvent(requestPayload.Name, requestPayload.Data)
if err != nil {
log.Println(err)
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
// create the response we'll send back as JSON
resp := jsonResponse{
Error: false,
Message: "logged",
}
// write the response back as JSON
_ = app.writeJSON(w, http.StatusAccepted, resp)
}
// Logout logs the user out and redirects them to the login page
func (app *Config) Logout(w http.ResponseWriter, r *http.Request) {
// log the event
_ = app.logEvent("authentication", fmt.Sprintf("%s logged out of the logger service", app.Session.GetString(r.Context(), "email")))
// clean up session
_ = app.Session.Destroy(r.Context())
_ = app.Session.RenewToken(r.Context())
// redirect to login page
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
// LoginPage displays the login page
func (app *Config) LoginPage(w http.ResponseWriter, r *http.Request) {
app.render(w, r, "login.page.gohtml", nil)
}
// LoginPagePost handles user login. Note that it calls the authentication microservice
func (app *Config) LoginPagePost(w http.ResponseWriter, r *http.Request) {
// it's always good to regenerate the session on login/logout
_ = app.Session.RenewToken(r.Context())
// parse the posted form data
err := r.ParseForm()
if err != nil {
log.Println(err)
}
// get email and password from form post
email := r.Form.Get("email")
password := r.Form.Get("password")
// create a variable we'll post to the auth service as JSON
var requestPayload struct {
Email string `json:"email"`
Password string `json:"password"`
}
requestPayload.Email = email
requestPayload.Password = password
// create json we'll send to the authentication-service
jsonData, _ := json.MarshalIndent(requestPayload, "", "\t")
// call the authentication-service; we need a request, so let's build one, and populate
// its body with the jsonData we just created
request, err := http.NewRequest("POST", authServiceURL, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
c := &http.Client{}
response, err := c.Do(request)
if err != nil {
log.Println(err)
_ = app.errorJSON(w, err, http.StatusBadRequest)
return
}
defer response.Body.Close()
// make sure we get back the right status code
if response.StatusCode == http.StatusUnauthorized {
log.Println("wrong status code", response.StatusCode)
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
} else if response.StatusCode != http.StatusAccepted {
log.Println("did not get status accepted")
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Read the body of the response
body, err := io.ReadAll(response.Body)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
// define a type that matches the JSON we're getting from the response body
type userPayload struct {
Error bool `json:"error"`
Message string `json:"message"`
Data struct {
ID int `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Active int `json:"active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} `json:"data"`
}
// declare a variable we can unmarshal the JSON into
var user userPayload
// since we received the request from a remote host with http.Client,
// we need to build a new request with the body we received and pass it to
// app.readJSON
req, _ := http.NewRequest("POST", "/", bytes.NewReader(body))
// read the JSON
err = app.readJSON(w, req, &user)
if err != nil {
log.Println(err)
app.clientError(w, http.StatusBadRequest)
return
}
// log the event
_ = app.logEvent("authentication", fmt.Sprintf("%s logged into the logger service", user.Data.Email))
// set up session & log user in
app.Session.Put(r.Context(), "userID", user.Data.ID)
app.Session.Put(r.Context(), "email", user.Data.Email)
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
}
// Dashboard displays the dashboard page
func (app *Config) Dashboard(w http.ResponseWriter, r *http.Request) {
// get the list of all log entries from mongo
logs, err := app.Models.LogEntry.All()
if err != nil {
log.Println("Error getting all log entries")
app.clientError(w, http.StatusBadRequest)
}
templateData := make(map[string]any)
templateData["logs"] = logs
app.render(w, r, "dashboard.page.gohtml", &TemplateData{
Data: templateData,
})
}
// DisplayOne is the handler to display a single log entry
func (app *Config) DisplayOne(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
entry, err := app.Models.LogEntry.GetOne(id)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
templateData := make(map[string]any)
templateData["entry"] = entry
app.render(w, r, "entry.page.gohtml", &TemplateData{
Data: templateData,
})
}
// DeleteAll drops everything in the collection and redirects to the same page
func (app *Config) DeleteAll(w http.ResponseWriter, r *http.Request) {
err := app.Models.LogEntry.DropCollection()
if err != nil {
log.Println(err)
}
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
}
// UpdateTimeStamp just demos how to update a document
func (app *Config) UpdateTimeStamp(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
entry, err := app.Models.LogEntry.GetOne(id)
if err != nil {
log.Println("Error getting record:", err)
app.clientError(w, http.StatusBadRequest)
return
}
res, err := entry.Update()
if err != nil {
log.Println("Error updating", err)
app.clientError(w, http.StatusBadRequest)
return
}
log.Println("Result in handler:", res.ModifiedCount)
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
}
================================================
FILE: logger-service/cmd/web/helpers.go
================================================
package main
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"log-service/data"
"net/http"
"runtime/debug"
"time"
)
const randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321_+"
type jsonResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
// readJSON tries to read the body of a request and converts it into JSON
func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
maxBytes := 1048576 // one megabyte
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
err := dec.Decode(data)
if err != nil {
return err
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must have only a single json value")
}
return nil
}
// writeJSON takes a response status code and arbitrary data and writes a json response to the client
func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
out, err := json.Marshal(data)
if err != nil {
return err
}
if len(headers) > 0 {
for key, value := range headers[0] {
w.Header()[key] = value
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(out)
if err != nil {
return err
}
return nil
}
// errorJSON takes an error, and optionally a response status code, and generates and sends
// a json error response
func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
statusCode := http.StatusBadRequest
if len(status) > 0 {
statusCode = status[0]
}
var payload jsonResponse
payload.Error = true
payload.Message = err.Error()
return app.writeJSON(w, statusCode, payload)
}
// isAuthenticated checks to see if a user is authenticated by looking for userID in session
func (app *Config) isAuthenticated(r *http.Request) bool {
exists := app.Session.Exists(r.Context(), "userID")
return exists
}
// clientError just fires back a client error when something goes wrong (bad request)
func (app *Config) clientError(w http.ResponseWriter, status int) {
log.Println("Client error with status of", status)
http.Error(w, http.StatusText(status), status)
}
// serverError just fires back error 500 when something goes wrong
func (app *Config) serverError(w http.ResponseWriter, err error) {
trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
log.Println(trace)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
// logEvent saves an event to the logs collection in Mongo
func (app *Config) logEvent(name, content string) error {
event := data.LogEntry{
Name: name,
Data: content,
CreatedAt: time.Now(),
}
return app.Models.LogEntry.Insert(event)
}
// randomString returns a random string of letters of length n
func (app *Config) randomString(n int) string {
s, r := make([]rune, n), []rune(randomStringSource)
for i := range s {
p, _ := rand.Prime(rand.Reader, len(r))
x, y := p.Uint64(), uint64(len(r))
s[i] = r[x%y]
}
return string(s)
}
================================================
FILE: logger-service/cmd/web/main.go
================================================
package main
import (
"context"
"fmt"
"github.com/alexedwards/scs/v2"
clientv3 "go.etcd.io/etcd/client/v3"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"log-service/data"
"net"
"net/http"
"net/rpc"
"time"
)
var client *mongo.Client
const (
webPort = "80"
rpcPort = "5001"
mongoURL = "mongodb://mongo:27017"
gRpcPort = "50001"
)
type Config struct {
Session *scs.SessionManager
Models data.Models
Etcd *clientv3.Client
}
func main() {
// Connect to Mongo and get a client.
mongoClient, err := connectToMongo()
client = mongoClient
// We'll use this context to disconnect from mongo, since it needs one.
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// close connection to Mongo when application exits
defer func() {
if err = client.Disconnect(ctx); err != nil {
panic(err)
}
}()
// Set up application configuration with session and our Models type,
// which allows us to interact with Mongo.
session := scs.New()
session.Lifetime = 24 * time.Hour
session.Cookie.Persist = true
session.Cookie.SameSite = http.SameSiteLaxMode
session.Cookie.Secure = false
app := Config{
Session: session,
Models: data.New(client),
}
// connect to etcd and register service
//app.registerService()
//defer app.Etcd.Close()
// Start webserver in its own GoRoutine
go app.serve()
// Start the gRPC server in its own GoRoutine
go app.gRPCListen()
// Register the RPC server.
err = rpc.Register(new(RPCServer))
if err != nil {
return
}
// Listen for RPC connections on port rpcPort
log.Println("Starting RPC Server on port", rpcPort)
listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", rpcPort))
if err != nil {
fmt.Println(err)
return
}
defer listen.Close()
// this loop executes forever, waiting for connections
for {
rpcConn, err := listen.Accept()
if err != nil {
continue
}
log.Println("Working...")
go rpc.ServeConn(rpcConn)
}
}
// serve starts the web server.
func (app *Config) serve() {
srv := &http.Server{
Addr: fmt.Sprintf(":%s", webPort),
Handler: app.routes(),
}
fmt.Println("--------------------------------------")
fmt.Println("Starting logging web service on port", webPort)
err := srv.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
// Connect opens a connection to the Mongo database and returns a client.
func connectToMongo() (*mongo.Client, error) {
// create connect options
clientOptions := options.Client().ApplyURI(mongoURL)
clientOptions.SetAuth(options.Credential{
Username: "admin",
Password: "password",
})
// Connect to the MongoDB and return Client instance
c, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
fmt.Println("mongo.Connect() ERROR:", err)
return nil, err
}
return c, nil
}
================================================
FILE: logger-service/cmd/web/middleware.go
================================================
package main
import "net/http"
// SessionLoad loads and saves the session on every request
func (app *Config) SessionLoad(next http.Handler) http.Handler {
return app.Session.LoadAndSave(next)
}
// Auth requires that users be logged in for any route that uses this middleware
func (app *Config) Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !app.isAuthenticated(r) {
app.Session.Put(r.Context(), "error", "Log in first!")
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
})
}
================================================
FILE: logger-service/cmd/web/render.go
================================================
package main
import (
"fmt"
"html/template"
"net/http"
)
type TemplateData struct {
Data map[string]any
IsAuthenticated int
}
// addDefaultData adds whatever is specified in this function to all templates as
// data that can be accessed directly.
func (app *Config) addDefaultData(td *TemplateData, r *http.Request) *TemplateData {
if app.Session.Exists(r.Context(), "userID") {
td.IsAuthenticated = 1
}
return td
}
func (app *Config) render(w http.ResponseWriter, r *http.Request, t string, td *TemplateData) {
// we only have one partial, which is actually a layout, but since all of our pages
// require the layout, we need to include it when we call ParseFiles, below.
// If you have other partials you use in your templates, add them to this slice.
partials := []string{
"./templates/base.layout.gohtml",
}
var templateSlice []string
templateSlice = append(templateSlice, fmt.Sprintf("./templates/%s", t))
for _, x := range partials {
templateSlice = append(templateSlice, x)
}
tmpl, err := template.ParseFiles(templateSlice...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if td == nil {
td = &TemplateData{}
}
td = app.addDefaultData(td, r)
if err := tmpl.Execute(w, td); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
================================================
FILE: logger-service/cmd/web/routes.go
================================================
package main
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"net/http"
)
func (app *Config) routes() http.Handler {
mux := chi.NewRouter()
mux.Use(middleware.Reco
gitextract_w41isflk/ ├── .gitignore ├── Caddyfile ├── LICENSE.md ├── Makefile ├── README.md ├── authentication-service/ │ ├── cmd/ │ │ └── api/ │ │ ├── discover.go │ │ ├── handlers.go │ │ ├── helpers.go │ │ ├── main.go │ │ └── routes.go │ ├── data/ │ │ └── models.go │ ├── go.mod │ ├── go.sum │ └── users.sql ├── authentication-service.dockerfile ├── broker-service/ │ ├── Makefile │ ├── cmd/ │ │ └── api/ │ │ ├── discover.go │ │ ├── handlers.go │ │ ├── helpers.go │ │ ├── main.go │ │ └── routes.go │ ├── event/ │ │ ├── emitter.go │ │ └── event.go │ ├── go.mod │ ├── go.sum │ └── logs/ │ ├── logs.pb.go │ ├── logs.proto │ └── logs_grpc.pb.go ├── broker-service.dockerfile ├── caddy.dockerfile ├── db-data/ │ ├── .gitignore │ └── .gitkeep ├── docker-compose.yml ├── front-end/ │ ├── cmd/ │ │ └── web/ │ │ ├── main.go │ │ └── templates/ │ │ ├── base.layout.gohtml │ │ ├── footer.partial.gohtml │ │ ├── header.partial.gohtml │ │ └── test.page.gohtml │ └── go.mod ├── front-end.dockerfile ├── ingress.yml ├── k8s/ │ ├── authentication.yml │ ├── broker.yml │ ├── listener.yml │ ├── logger.yml │ ├── mail.yml │ ├── mailhog.yml │ ├── mongo.yml │ └── rabbitmq.yml ├── k8s.md ├── listener-service/ │ ├── go.mod │ ├── go.sum │ ├── lib/ │ │ └── event/ │ │ ├── consumer.go │ │ └── event.go │ └── main.go ├── listener-service.dockerfile ├── logger-service/ │ ├── Makefile │ ├── cmd/ │ │ └── web/ │ │ ├── discover.go │ │ ├── grpc.go │ │ ├── handlers.go │ │ ├── helpers.go │ │ ├── main.go │ │ ├── middleware.go │ │ ├── render.go │ │ ├── routes.go │ │ └── rpc.go │ ├── data/ │ │ └── models.go │ ├── go.mod │ ├── go.sum │ ├── logs/ │ │ ├── logs.pb.go │ │ ├── logs.proto │ │ ├── logs_grpc.pb.go │ │ └── readme.md │ └── templates/ │ ├── base.layout.gohtml │ ├── dashboard.page.gohtml │ ├── entry.page.gohtml │ └── login.page.gohtml ├── logger-service.dockerfile ├── mail-service/ │ ├── cmd/ │ │ └── api/ │ │ ├── discover.go │ │ ├── handlers.go │ │ ├── helpers.go │ │ ├── mailer.go │ │ ├── main.go │ │ └── routes.go │ ├── go.mod │ ├── go.sum │ └── templates/ │ ├── mail.html.tmpl │ └── mail.plain.tmpl ├── mail-service.dockerfile ├── multistage-dockerfiles/ │ ├── authentication-service.dockerfile │ ├── broker-service.dockerfile │ ├── listener-service.dockerfile │ ├── logger-service.dockerfile │ └── mail-service.dockerfile ├── postgres.yml ├── swarm.md └── swarm.yml
SYMBOL INDEX (227 symbols across 36 files)
FILE: authentication-service/cmd/api/handlers.go
method Authenticate (line 12) | func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
method logRequest (line 59) | func (app *Config) logRequest(name, data string) error {
FILE: authentication-service/cmd/api/helpers.go
constant randomStringSource (line 11) | randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX...
type jsonResponse (line 14) | type jsonResponse struct
method readJSON (line 21) | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data...
method writeJSON (line 40) | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any...
method errorJSON (line 64) | func (app *Config) errorJSON(w http.ResponseWriter, err error, status .....
method randomString (line 79) | func (app *Config) randomString(n int) string {
FILE: authentication-service/cmd/api/main.go
constant webPort (line 16) | webPort = "80"
type Config (line 20) | type Config struct
function main (line 26) | func main() {
function connectToDB (line 56) | func connectToDB() *sql.DB {
function openDB (line 81) | func openDB(dsn string) (*sql.DB, error) {
FILE: authentication-service/cmd/api/routes.go
method routes (line 10) | func (app *Config) routes() http.Handler {
FILE: authentication-service/data/models.go
constant dbTimeout (line 13) | dbTimeout = time.Second * 3
function New (line 19) | func New(dbPool *sql.DB) Models {
type Models (line 30) | type Models struct
type User (line 35) | type User struct
method GetAll (line 47) | func (u *User) GetAll() ([]*User, error) {
method GetByEmail (line 86) | func (u *User) GetByEmail(email string) (*User, error) {
method GetOne (line 114) | func (u *User) GetOne(id int) (*User, error) {
method Update (line 143) | func (u *User) Update() error {
method Delete (line 173) | func (u *User) Delete() error {
method DeleteByID (line 188) | func (u *User) DeleteByID(id int) error {
method Insert (line 203) | func (u *User) Insert(user User) (int, error) {
method ResetPassword (line 234) | func (u *User) ResetPassword(password string) error {
method PasswordMatches (line 255) | func (u *User) PasswordMatches(plainText string) (bool, error) {
FILE: authentication-service/users.sql
type public (line 25) | CREATE TABLE public.users (
FILE: broker-service/cmd/api/discover.go
function getUrlFromMap (line 109) | func getUrlFromMap(m map[string]string) string {
function connectToEtcd (line 119) | func connectToEtcd() (*clientv3.Client, error) {
FILE: broker-service/cmd/api/handlers.go
constant loggerGRPCAddress (line 19) | loggerGRPCAddress = "logger-service:50001"
type Payload (line 22) | type Payload struct
type RequestPayload (line 30) | type RequestPayload struct
type AuthPayload (line 38) | type AuthPayload struct
type LogPayload (line 44) | type LogPayload struct
type MailPayload (line 50) | type MailPayload struct
method Broker (line 58) | func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
method HandleSubmission (line 75) | func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Reque...
method logViaJSON (line 96) | func (app *Config) logViaJSON(w http.ResponseWriter, entry LogPayload) {
method sendMail (line 127) | func (app *Config) sendMail(w http.ResponseWriter, msg MailPayload) {
method authenticate (line 168) | func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
method logItem (line 232) | func (app *Config) logItem(w http.ResponseWriter, l LogPayload) {
type RPCPayload (line 247) | type RPCPayload struct
method logItemViaRPC (line 252) | func (app *Config) logItemViaRPC(w http.ResponseWriter, l LogPayload) {
method pushToQueue (line 280) | func (app *Config) pushToQueue(name, msg string) error {
method LogViaGRPC (line 301) | func (app *Config) LogViaGRPC(w http.ResponseWriter, r *http.Request) {
FILE: broker-service/cmd/api/helpers.go
type jsonResponse (line 10) | type jsonResponse struct
method readJSON (line 17) | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data...
method writeJSON (line 36) | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any...
method errorJSON (line 60) | func (app *Config) errorJSON(w http.ResponseWriter, err error, status .....
FILE: broker-service/cmd/api/main.go
constant webPort (line 15) | webPort = "80"
type Config (line 19) | type Config struct
function main (line 27) | func main() {
function connectToRabbit (line 71) | func connectToRabbit() (*amqp.Connection, error) {
FILE: broker-service/cmd/api/routes.go
method routes (line 10) | func (app *Config) routes() http.Handler {
FILE: broker-service/event/emitter.go
type Emitter (line 11) | type Emitter struct
method setup (line 15) | func (e *Emitter) setup() error {
method Push (line 26) | func (e *Emitter) Push(event string, severity string) error {
function NewEventEmitter (line 58) | func NewEventEmitter(conn *amqp.Connection) (Emitter, error) {
FILE: broker-service/event/event.go
function getExchangeName (line 7) | func getExchangeName() string {
function declareExchange (line 11) | func declareExchange(ch *amqp.Channel) error {
FILE: broker-service/logs/logs.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type Log (line 23) | type Log struct
method Reset (line 32) | func (x *Log) Reset() {
method String (line 41) | func (x *Log) String() string {
method ProtoMessage (line 45) | func (*Log) ProtoMessage() {}
method ProtoReflect (line 47) | func (x *Log) ProtoReflect() protoreflect.Message {
method Descriptor (line 60) | func (*Log) Descriptor() ([]byte, []int) {
method GetName (line 64) | func (x *Log) GetName() string {
method GetData (line 71) | func (x *Log) GetData() string {
type LogRequest (line 78) | type LogRequest struct
method Reset (line 86) | func (x *LogRequest) Reset() {
method String (line 95) | func (x *LogRequest) String() string {
method ProtoMessage (line 99) | func (*LogRequest) ProtoMessage() {}
method ProtoReflect (line 101) | func (x *LogRequest) ProtoReflect() protoreflect.Message {
method Descriptor (line 114) | func (*LogRequest) Descriptor() ([]byte, []int) {
method GetLogEntry (line 118) | func (x *LogRequest) GetLogEntry() *Log {
type LogResponse (line 125) | type LogResponse struct
method Reset (line 133) | func (x *LogResponse) Reset() {
method String (line 142) | func (x *LogResponse) String() string {
method ProtoMessage (line 146) | func (*LogResponse) ProtoMessage() {}
method ProtoReflect (line 148) | func (x *LogResponse) ProtoReflect() protoreflect.Message {
method Descriptor (line 161) | func (*LogResponse) Descriptor() ([]byte, []int) {
method GetResult (line 165) | func (x *LogResponse) GetResult() string {
function file_logs_logs_proto_rawDescGZIP (line 198) | func file_logs_logs_proto_rawDescGZIP() []byte {
function init (line 222) | func init() { file_logs_logs_proto_init() }
function file_logs_logs_proto_init (line 223) | func file_logs_logs_proto_init() {
FILE: broker-service/logs/logs_grpc.pb.go
constant _ (line 19) | _ = grpc.SupportPackageIsVersion7
type LogServiceClient (line 24) | type LogServiceClient interface
type logServiceClient (line 28) | type logServiceClient struct
method WriteLog (line 36) | func (c *logServiceClient) WriteLog(ctx context.Context, in *LogReques...
function NewLogServiceClient (line 32) | func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
type LogServiceServer (line 48) | type LogServiceServer interface
type UnimplementedLogServiceServer (line 54) | type UnimplementedLogServiceServer struct
method WriteLog (line 57) | func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogReq...
method mustEmbedUnimplementedLogServiceServer (line 60) | func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceS...
type UnsafeLogServiceServer (line 65) | type UnsafeLogServiceServer interface
function RegisterLogServiceServer (line 69) | func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceSer...
function _LogService_WriteLog_Handler (line 73) | func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, ...
FILE: front-end/cmd/web/main.go
function main (line 11) | func main() {
function render (line 29) | func render(w http.ResponseWriter, t string) {
FILE: listener-service/lib/event/consumer.go
type Consumer (line 15) | type Consumer struct
method setup (line 34) | func (consumer *Consumer) setup() error {
method Listen (line 49) | func (consumer *Consumer) Listen(topics []string) error {
function NewConsumer (line 21) | func NewConsumer(conn *amqp.Connection) (Consumer, error) {
type Payload (line 43) | type Payload struct
function handlePayload (line 99) | func handlePayload(payload Payload) {
function rpcPushToLogger (line 132) | func rpcPushToLogger(function string, data any) (string, error) {
function authenticate (line 151) | func authenticate(payload Payload) error {
function logEvent (line 157) | func logEvent(entry Payload) error {
FILE: listener-service/lib/event/event.go
function getExchangeName (line 7) | func getExchangeName() string {
function declareRandomQueue (line 12) | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) {
function declareExchange (line 24) | func declareExchange(ch *amqp.Channel) error {
FILE: listener-service/main.go
function main (line 13) | func main() {
function connect (line 40) | func connect() (*amqp.Connection, error) {
FILE: logger-service/cmd/web/grpc.go
type LogServer (line 15) | type LogServer struct
method WriteLog (line 23) | func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest...
method gRPCListen (line 46) | func (app *Config) gRPCListen() {
FILE: logger-service/cmd/web/handlers.go
constant authServiceURL (line 16) | authServiceURL = "http://authentication-service/authenticate"
type JSONPayload (line 19) | type JSONPayload struct
method WriteLog (line 26) | func (app *Config) WriteLog(w http.ResponseWriter, r *http.Request) {
method Logout (line 50) | func (app *Config) Logout(w http.ResponseWriter, r *http.Request) {
method LoginPage (line 63) | func (app *Config) LoginPage(w http.ResponseWriter, r *http.Request) {
method LoginPagePost (line 68) | func (app *Config) LoginPagePost(w http.ResponseWriter, r *http.Request) {
method Dashboard (line 167) | func (app *Config) Dashboard(w http.ResponseWriter, r *http.Request) {
method DisplayOne (line 184) | func (app *Config) DisplayOne(w http.ResponseWriter, r *http.Request) {
method DeleteAll (line 202) | func (app *Config) DeleteAll(w http.ResponseWriter, r *http.Request) {
method UpdateTimeStamp (line 212) | func (app *Config) UpdateTimeStamp(w http.ResponseWriter, r *http.Reques...
FILE: logger-service/cmd/web/helpers.go
constant randomStringSource (line 16) | randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX...
type jsonResponse (line 18) | type jsonResponse struct
method readJSON (line 25) | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data...
method writeJSON (line 44) | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any...
method errorJSON (line 68) | func (app *Config) errorJSON(w http.ResponseWriter, err error, status .....
method isAuthenticated (line 83) | func (app *Config) isAuthenticated(r *http.Request) bool {
method clientError (line 89) | func (app *Config) clientError(w http.ResponseWriter, status int) {
method serverError (line 95) | func (app *Config) serverError(w http.ResponseWriter, err error) {
method logEvent (line 102) | func (app *Config) logEvent(name, content string) error {
method randomString (line 112) | func (app *Config) randomString(n int) string {
FILE: logger-service/cmd/web/main.go
constant webPort (line 21) | webPort = "80"
constant rpcPort (line 22) | rpcPort = "5001"
constant mongoURL (line 23) | mongoURL = "mongodb://mongo:27017"
constant gRpcPort (line 24) | gRpcPort = "50001"
type Config (line 27) | type Config struct
method serve (line 99) | func (app *Config) serve() {
function main (line 33) | func main() {
function connectToMongo (line 114) | func connectToMongo() (*mongo.Client, error) {
FILE: logger-service/cmd/web/middleware.go
method SessionLoad (line 6) | func (app *Config) SessionLoad(next http.Handler) http.Handler {
method Auth (line 11) | func (app *Config) Auth(next http.Handler) http.Handler {
FILE: logger-service/cmd/web/render.go
type TemplateData (line 9) | type TemplateData struct
method addDefaultData (line 16) | func (app *Config) addDefaultData(td *TemplateData, r *http.Request) *Te...
method render (line 23) | func (app *Config) render(w http.ResponseWriter, r *http.Request, t stri...
FILE: logger-service/cmd/web/routes.go
method routes (line 10) | func (app *Config) routes() http.Handler {
method apiRouter (line 22) | func (app *Config) apiRouter() http.Handler {
method webRouter (line 40) | func (app *Config) webRouter() http.Handler {
FILE: logger-service/cmd/web/rpc.go
type RPCServer (line 12) | type RPCServer struct
method LogInfo (line 21) | func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
type RPCPayload (line 15) | type RPCPayload struct
FILE: logger-service/data/models.go
function New (line 18) | func New(mongo *mongo.Client) Models {
type Models (line 29) | type Models struct
type LogEntry (line 37) | type LogEntry struct
method Insert (line 46) | func (l *LogEntry) Insert(entry LogEntry) error {
method All (line 64) | func (l *LogEntry) All() ([]*LogEntry, error) {
method GetOne (line 99) | func (l *LogEntry) GetOne(id string) (*LogEntry, error) {
method DropCollection (line 121) | func (l *LogEntry) DropCollection() error {
method Update (line 135) | func (l *LogEntry) Update() (*mongo.UpdateResult, error) {
FILE: logger-service/logs/logs.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type Log (line 23) | type Log struct
method Reset (line 32) | func (x *Log) Reset() {
method String (line 41) | func (x *Log) String() string {
method ProtoMessage (line 45) | func (*Log) ProtoMessage() {}
method ProtoReflect (line 47) | func (x *Log) ProtoReflect() protoreflect.Message {
method Descriptor (line 60) | func (*Log) Descriptor() ([]byte, []int) {
method GetName (line 64) | func (x *Log) GetName() string {
method GetData (line 71) | func (x *Log) GetData() string {
type LogRequest (line 78) | type LogRequest struct
method Reset (line 86) | func (x *LogRequest) Reset() {
method String (line 95) | func (x *LogRequest) String() string {
method ProtoMessage (line 99) | func (*LogRequest) ProtoMessage() {}
method ProtoReflect (line 101) | func (x *LogRequest) ProtoReflect() protoreflect.Message {
method Descriptor (line 114) | func (*LogRequest) Descriptor() ([]byte, []int) {
method GetLogEntry (line 118) | func (x *LogRequest) GetLogEntry() *Log {
type LogResponse (line 125) | type LogResponse struct
method Reset (line 133) | func (x *LogResponse) Reset() {
method String (line 142) | func (x *LogResponse) String() string {
method ProtoMessage (line 146) | func (*LogResponse) ProtoMessage() {}
method ProtoReflect (line 148) | func (x *LogResponse) ProtoReflect() protoreflect.Message {
method Descriptor (line 161) | func (*LogResponse) Descriptor() ([]byte, []int) {
method GetResult (line 165) | func (x *LogResponse) GetResult() string {
function file_logs_proto_rawDescGZIP (line 197) | func file_logs_proto_rawDescGZIP() []byte {
function init (line 221) | func init() { file_logs_proto_init() }
function file_logs_proto_init (line 222) | func file_logs_proto_init() {
FILE: logger-service/logs/logs_grpc.pb.go
constant _ (line 19) | _ = grpc.SupportPackageIsVersion7
type LogServiceClient (line 24) | type LogServiceClient interface
type logServiceClient (line 28) | type logServiceClient struct
method WriteLog (line 36) | func (c *logServiceClient) WriteLog(ctx context.Context, in *LogReques...
function NewLogServiceClient (line 32) | func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
type LogServiceServer (line 48) | type LogServiceServer interface
type UnimplementedLogServiceServer (line 54) | type UnimplementedLogServiceServer struct
method WriteLog (line 57) | func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogReq...
method mustEmbedUnimplementedLogServiceServer (line 60) | func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceS...
type UnsafeLogServiceServer (line 65) | type UnsafeLogServiceServer interface
function RegisterLogServiceServer (line 69) | func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceSer...
function _LogService_WriteLog_Handler (line 73) | func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, ...
FILE: mail-service/cmd/api/discover.go
method registerService (line 12) | func (app *Config) registerService() {
method listenToKeepAlive (line 38) | func (app *Config) listenToKeepAlive(kalRes <-chan *clientv3.LeaseKeepAl...
function connectToEtcd (line 51) | func connectToEtcd() (*clientv3.Client, error) {
FILE: mail-service/cmd/api/handlers.go
method SendMail (line 9) | func (app *Config) SendMail(w http.ResponseWriter, r *http.Request) {
FILE: mail-service/cmd/api/helpers.go
constant randomStringSource (line 11) | randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX...
type jsonResponse (line 13) | type jsonResponse struct
method readJSON (line 20) | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data...
method writeJSON (line 39) | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any...
method errorJSON (line 63) | func (app *Config) errorJSON(w http.ResponseWriter, err error, status .....
method randomString (line 78) | func (app *Config) randomString(n int) string {
FILE: mail-service/cmd/api/mailer.go
type Mail (line 12) | type Mail struct
method SendSMTPMessage (line 36) | func (m *Mail) SendSMTPMessage(msg Message) error {
method buildHTMLMessage (line 99) | func (m *Mail) buildHTMLMessage(msg Message) (string, error) {
method buildPlainTextMessage (line 121) | func (m *Mail) buildPlainTextMessage(msg Message) (string, error) {
method getEncryption (line 139) | func (m *Mail) getEncryption(e string) mail.Encryption {
method inlineCSS (line 153) | func (m *Mail) inlineCSS(s string) (string, error) {
type Message (line 24) | type Message struct
FILE: mail-service/cmd/api/main.go
constant webPort (line 12) | webPort = "80"
type Config (line 15) | type Config struct
function main (line 20) | func main() {
function createMail (line 47) | func createMail() Mail {
FILE: mail-service/cmd/api/routes.go
method routes (line 10) | func (app *Config) routes() http.Handler {
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (327K chars).
[
{
"path": ".gitignore",
"chars": 107,
"preview": ".idea\n.vscode\ncoverage.out\nfrontApp\nlogServiceApp\nmailerServiceApp\nauthApp\nbrokerApp\nlistener\nfrontEndLinux"
},
{
"path": "Caddyfile",
"chars": 592,
"preview": "{\n email you@gmail.com\n}\n\n(static) {\n\t@static {\n\t\tfile\n\t\tpath *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.wof"
},
{
"path": "LICENSE.md",
"chars": 1076,
"preview": "# MIT License\n\n### Copyright (c) 2022 Trevor Sawler\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "Makefile",
"chars": 6479,
"preview": "FRONT_END_BINARY=frontApp\nLOGGER_BINARY=logServiceApp\nBROKER_BINARY=brokerApp\nAUTH_BINARY=authApp\nLISTENER_BINARY=listen"
},
{
"path": "README.md",
"chars": 3835,
"preview": "[](https://golang.org)\n[ register"
},
{
"path": "authentication-service/cmd/api/handlers.go",
"chars": 1861,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Authenticate accepts a json payload "
},
{
"path": "authentication-service/cmd/api/helpers.go",
"chars": 2093,
"preview": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n)\n\nconst randomStringSource = \"abcdef"
},
{
"path": "authentication-service/cmd/api/main.go",
"chars": 1527,
"preview": "package main\n\nimport (\n\t\"authentication/data\"\n\t\"database/sql\"\n\t\"fmt\"\n\t_ \"github.com/jackc/pgconn\"\n\t_ \"github.com/jackc/p"
},
{
"path": "authentication-service/cmd/api/routes.go",
"chars": 685,
"preview": "package main\n\nimport (\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/cors\"\n\t\"ne"
},
{
"path": "authentication-service/data/models.go",
"chars": 5980,
"preview": "package data\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"log\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\nconst dbTim"
},
{
"path": "authentication-service/go.mod",
"chars": 1247,
"preview": "module authentication\n\ngo 1.18\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.0.8\n\tgithub.com/go-chi/cors v1.2.1\n\tgithub.com/ja"
},
{
"path": "authentication-service/go.sum",
"chars": 45047,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "authentication-service/users.sql",
"chars": 1389,
"preview": "\n\n--\n-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres\n--\n\nCREATE SEQUENCE public.user_id_seq\n ST"
},
{
"path": "authentication-service.dockerfile",
"chars": 127,
"preview": "FROM alpine:latest\nRUN mkdir /app\n\nCOPY authentication-service/authApp /app\n\n# Run the server executable\nCMD [ \"/app/aut"
},
{
"path": "broker-service/Makefile",
"chars": 123,
"preview": "gen:\n\tprotoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs/logs.pro"
},
{
"path": "broker-service/cmd/api/discover.go",
"chars": 3889,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"time\"\n)\n\n//func (app *Config) getServiceURLs() {\n/"
},
{
"path": "broker-service/cmd/api/handlers.go",
"chars": 9405,
"preview": "package main\n\nimport (\n\t\"broker/event\"\n\t\"broker/logs\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"google.gol"
},
{
"path": "broker-service/cmd/api/helpers.go",
"chars": 1623,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype jsonResponse struct {\n\tError bool `json:"
},
{
"path": "broker-service/cmd/api/main.go",
"chars": 1933,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"l"
},
{
"path": "broker-service/cmd/api/routes.go",
"chars": 966,
"preview": "package main\n\nimport (\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/cors\"\n\t\"ne"
},
{
"path": "broker-service/event/emitter.go",
"chars": 1255,
"preview": "package event\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\n// Emitter for publishing AMQP even"
},
{
"path": "broker-service/event/event.go",
"chars": 428,
"preview": "package event\n\nimport (\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\nfunc getExchangeName() string {\n\treturn \"logs_topic\"\n}"
},
{
"path": "broker-service/go.mod",
"chars": 846,
"preview": "module broker\n\ngo 1.18\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.0.8\n\tgithub.com/go-chi/cors v1.2.1\n\tgithub.com/rabbitmq/a"
},
{
"path": "broker-service/go.sum",
"chars": 33212,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "broker-service/logs/logs.pb.go",
"chars": 8235,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.27.1\n// \tprotoc v3.19.4\n// sou"
},
{
"path": "broker-service/logs/logs.proto",
"chars": 281,
"preview": "syntax = \"proto3\";\n\npackage logs;\n\noption go_package = \"/logs\";\n\nmessage Log{\n string name = 1;\n string data =2;\n}\n\nme"
},
{
"path": "broker-service/logs/logs_grpc.pb.go",
"chars": 3570,
"preview": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.2.0\n// - protoc "
},
{
"path": "broker-service.dockerfile",
"chars": 143,
"preview": "# The base go-image\nFROM alpine:latest\nRUN mkdir /app\n\nCOPY broker-service/brokerApp /app\n\n# Run the server executable\nC"
},
{
"path": "caddy.dockerfile",
"chars": 61,
"preview": "FROM caddy:2.4.6-alpine\n\nCOPY Caddyfile /etc/caddy/Caddyfile\n"
},
{
"path": "db-data/.gitignore",
"chars": 27,
"preview": "*\n!*/\n!.gitignore\n!.gitkeep"
},
{
"path": "db-data/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "docker-compose.yml",
"chars": 3343,
"preview": "version: '3'\n\nservices:\n\n# # broker-service - main entry point; we call this from the front end\n# broker-service:\n# "
},
{
"path": "front-end/cmd/web/main.go",
"chars": 1319,
"preview": "package main\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\t// the handler to display o"
},
{
"path": "front-end/cmd/web/templates/base.layout.gohtml",
"chars": 238,
"preview": "{{define \"base\" }}\n <!doctype html>\n <html lang=\"en\">\n\n {{template \"header\" .}}\n\n <body>\n\n {{block \"conte"
},
{
"path": "front-end/cmd/web/templates/footer.partial.gohtml",
"chars": 266,
"preview": "{{define \"footer\"}}\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col text-center\">\n "
},
{
"path": "front-end/cmd/web/templates/header.partial.gohtml",
"chars": 557,
"preview": "{{define \"header\"}}\n\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\"\n content=\"widt"
},
{
"path": "front-end/cmd/web/templates/test.page.gohtml",
"chars": 9167,
"preview": "{{template \"base\" .}}\n\n{{define \"content\" }}\n <div class=\"container\">\n <div class=\"row\">\n <div clas"
},
{
"path": "front-end/go.mod",
"chars": 25,
"preview": "module frontend\n\ngo 1.18\n"
},
{
"path": "front-end.dockerfile",
"chars": 126,
"preview": "FROM alpine:latest\nRUN mkdir /app\n\nCOPY front-end/frontEndLinux /app\n\n# Run the server executable\nCMD [ \"/app/frontEndLi"
},
{
"path": "ingress.yml",
"chars": 407,
"preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: example-ingress\n annotations:\n nginx.ingress.kubern"
},
{
"path": "k8s/authentication.yml",
"chars": 866,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: authentication-service\nspec:\n replicas: 1\n selector:\n matchL"
},
{
"path": "k8s/broker.yml",
"chars": 738,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: broker-service\nspec:\n replicas: 1\n selector:\n matchLabels:\n "
},
{
"path": "k8s/listener.yml",
"chars": 592,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: listener\nspec:\n replicas: 1\n selector:\n matchLabels:\n a"
},
{
"path": "k8s/logger.yml",
"chars": 865,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: logger-service\nspec:\n replicas: 2\n selector:\n matchLabels:\n "
},
{
"path": "k8s/mail.yml",
"chars": 1014,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: mail-service\nspec:\n replicas: 1\n selector:\n matchLabels:\n "
},
{
"path": "k8s/mailhog.yml",
"chars": 628,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: mailhog\nspec:\n replicas: 1\n selector:\n matchLabels:\n ap"
},
{
"path": "k8s/mongo.yml",
"chars": 730,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: mongo\nspec:\n replicas: 1\n selector:\n matchLabels:\n app:"
},
{
"path": "k8s/rabbitmq.yml",
"chars": 497,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: rabbitmq\nspec:\n replicas: 1\n selector:\n matchLabels:\n a"
},
{
"path": "k8s.md",
"chars": 523,
"preview": "# K8S\n\n```\nminikube start --nodes=2\n\nminikube status\ndocker ps\nkubectl get nodes\nkubectl get pods -A\nkubectl get pods\nku"
},
{
"path": "listener-service/go.mod",
"chars": 92,
"preview": "module github.com/tsawler/go-rabbit\n\ngo 1.18\n\nrequire github.com/rabbitmq/amqp091-go v1.7.0\n"
},
{
"path": "listener-service/go.sum",
"chars": 4570,
"preview": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1"
},
{
"path": "listener-service/lib/event/consumer.go",
"chars": 3832,
"preview": "package event\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/rpc\"\n\n\tamqp \"github.com/rabbitmq/amqp0"
},
{
"path": "listener-service/lib/event/event.go",
"chars": 757,
"preview": "package event\n\nimport (\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n)\n\nfunc getExchangeName() string {\n\treturn \"logs_topic\"\n}"
},
{
"path": "listener-service/main.go",
"chars": 1676,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n\t\"github.com/tsawler/go-rabbit/lib/event\"\n\t\"log\"\n\t\""
},
{
"path": "listener-service.dockerfile",
"chars": 123,
"preview": "FROM alpine:latest\nRUN mkdir /app\n\nCOPY listener-service/listener /app\n\n# Run the server executable\nCMD [ \"/app/listener"
},
{
"path": "logger-service/Makefile",
"chars": 123,
"preview": "gen:\n\tprotoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs/logs.pro"
},
{
"path": "logger-service/cmd/web/discover.go",
"chars": 1948,
"preview": "package main\n\n//// registerService registers the correct entry for this service in etcd\n//func (app *Config) registerSer"
},
{
"path": "logger-service/cmd/web/grpc.go",
"chars": 1515,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"google.golang.org/grpc\"\n\t\"log\"\n\t\"log-service/data\"\n\t\"log-service/logs\"\n\t\"net\""
},
{
"path": "logger-service/cmd/web/handlers.go",
"chars": 6629,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/go-chi/chi/v5\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\n"
},
{
"path": "logger-service/cmd/web/helpers.go",
"chars": 3144,
"preview": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"log-service/data\"\n\t\"net/http\"\n\t\"r"
},
{
"path": "logger-service/cmd/web/main.go",
"chars": 2855,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/alexedwards/scs/v2\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.mon"
},
{
"path": "logger-service/cmd/web/middleware.go",
"chars": 596,
"preview": "package main\n\nimport \"net/http\"\n\n// SessionLoad loads and saves the session on every request\nfunc (app *Config) SessionL"
},
{
"path": "logger-service/cmd/web/render.go",
"chars": 1352,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n)\n\ntype TemplateData struct {\n\tData map[string]any"
},
{
"path": "logger-service/cmd/web/routes.go",
"chars": 1444,
"preview": "package main\n\nimport (\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/cors\"\n\t\"ne"
},
{
"path": "logger-service/cmd/web/rpc.go",
"chars": 871,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"log-service/data\"\n\t\"time\"\n)\n\n// RPCServer is the type for our RPC Server. Met"
},
{
"path": "logger-service/data/models.go",
"chars": 4418,
"preview": "package data\n\nimport (\n\t\"context\"\n\t\"go.mongodb.org/mongo-driver/bson\"\n\t\"go.mongodb.org/mongo-driver/bson/primitive\"\n\t\"go"
},
{
"path": "logger-service/go.mod",
"chars": 1410,
"preview": "module log-service\n\ngo 1.18\n\nrequire (\n\tgithub.com/alexedwards/scs/v2 v2.5.0\n\tgithub.com/go-chi/chi/v5 v5.0.8\n\tgithub.co"
},
{
"path": "logger-service/go.sum",
"chars": 37314,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "logger-service/logs/logs.pb.go",
"chars": 8019,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.27.1\n// \tprotoc v3.19.4\n// sou"
},
{
"path": "logger-service/logs/logs.proto",
"chars": 282,
"preview": "syntax = \"proto3\";\n\npackage logs;\n\noption go_package = \"/logs\";\n\nmessage Log{\n string name = 1;\n string data =2;\n}\n\nme"
},
{
"path": "logger-service/logs/logs_grpc.pb.go",
"chars": 3560,
"preview": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.2.0\n// - protoc "
},
{
"path": "logger-service/logs/readme.md",
"chars": 372,
"preview": "## gRPC stuff\n\nInstall binaries:\n\n```\ngo install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27\ngo install google.go"
},
{
"path": "logger-service/templates/base.layout.gohtml",
"chars": 2243,
"preview": "{{define \"base\" }}\n <!doctype html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name"
},
{
"path": "logger-service/templates/dashboard.page.gohtml",
"chars": 2787,
"preview": "{{template \"base\" .}}\n\n{{define \"content\"}}\n {{$logs := index .Data \"logs\"}}\n <div class=\"container\">\n <div"
},
{
"path": "logger-service/templates/entry.page.gohtml",
"chars": 674,
"preview": "{{template \"base\" .}}\n\n{{define \"content\"}}\n {{$entry := index .Data \"entry\"}}\n <div class=\"container\">\n <d"
},
{
"path": "logger-service/templates/login.page.gohtml",
"chars": 1603,
"preview": "{{template \"base\" .}}\n\n{{define \"content\"}}\n <div class=\"container\">\n <div class=\"row\">\n <div class"
},
{
"path": "logger-service.dockerfile",
"chars": 195,
"preview": "FROM alpine:latest\nRUN mkdir /app\nRUN mkdir /templates\n\nCOPY logger-service/logServiceApp /app\nCOPY logger-service/templ"
},
{
"path": "mail-service/cmd/api/discover.go",
"chars": 1648,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"log\"\n\t\"time\"\n)\n\n// registerService regi"
},
{
"path": "mail-service/cmd/api/handlers.go",
"chars": 906,
"preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\n// SendMail receives a json payload for a message, and sends it\nfunc (app *"
},
{
"path": "mail-service/cmd/api/helpers.go",
"chars": 2036,
"preview": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n)\n\nconst randomStringSource = \"abcdef"
},
{
"path": "mail-service/cmd/api/mailer.go",
"chars": 3732,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"github.com/vanng822/go-premailer/premailer\"\n\tmail \"github.com/xhit/go-simple-mail/v2\"\n"
},
{
"path": "mail-service/cmd/api/main.go",
"chars": 1324,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n)\n\nconst webPort"
},
{
"path": "mail-service/cmd/api/routes.go",
"chars": 692,
"preview": "package main\n\nimport (\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/cors\"\n\t\"ne"
},
{
"path": "mail-service/go.mod",
"chars": 1380,
"preview": "module mail-service\n\ngo 1.18\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.0.7\n\tgithub.com/go-chi/cors v1.2.0\n\tgithub.com/vann"
},
{
"path": "mail-service/go.sum",
"chars": 32086,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "mail-service/templates/mail.html.tmpl",
"chars": 315,
"preview": "{{define \"body\"}}\n <!doctype html>\n <html lang=\"en\">\n\n <head>\n <meta name=\"viewport\" content=\"width=devi"
},
{
"path": "mail-service/templates/mail.plain.tmpl",
"chars": 42,
"preview": "{{define \"body\"}}\n {{.message}}\n{{end}}"
},
{
"path": "mail-service.dockerfile",
"chars": 195,
"preview": "FROM alpine:latest\nRUN mkdir /app\nRUN mkdir /templates\n\nCOPY mail-service/mailerServiceApp /app\nCOPY mail-service/templa"
},
{
"path": "multistage-dockerfiles/authentication-service.dockerfile",
"chars": 485,
"preview": "# The base go-image\nFROM golang:1.18-alpine as builder\n\n# create a directory for the app\nRUN mkdir /app\n\n# copy all file"
},
{
"path": "multistage-dockerfiles/broker-service.dockerfile",
"chars": 493,
"preview": "# The base go-image\nFROM golang:1.18-alpine as builder\n\n# create a directory for the app\nRUN mkdir /app\n\n# copy all file"
},
{
"path": "multistage-dockerfiles/listener-service.dockerfile",
"chars": 485,
"preview": "# The base go-image\nFROM golang:1.18-alpine as builder\n \n# create a directory for the app\nRUN mkdir /app\n \n# copy all fi"
},
{
"path": "multistage-dockerfiles/logger-service.dockerfile",
"chars": 578,
"preview": "# The base go-image\nFROM golang:1.18-alpine as builder\n\n# create a directory for the app\nRUN mkdir /app\n\n# copy all file"
},
{
"path": "multistage-dockerfiles/mail-service.dockerfile",
"chars": 578,
"preview": "# The base go-image\nFROM golang:1.18-alpine as builder\n\n# create a directory for the app\nRUN mkdir /app\n\n# copy all file"
},
{
"path": "postgres.yml",
"chars": 690,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: postgres\nspec:\n replicas: 1\n selector:\n matchLabels:\n a"
},
{
"path": "swarm.md",
"chars": 658,
"preview": "# Docker swarm\n\n\n## Build images:\n```bash\ndocker build -f front-end.dockerfile -t tsawler/front-end:tag1 .\ndocker push t"
},
{
"path": "swarm.yml",
"chars": 3085,
"preview": "version: '3'\n\nservices:\n\n caddy:\n image: tsawler/micro-caddy:1.0.0\n deploy:\n mode: replicated\n replicas"
}
]
About this extraction
This page contains the full source code of the tsawler/go-microservices GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (300.5 KB), approximately 128.6k tokens, and a symbol index with 227 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.