1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-07-03 00:58:13 +02:00

Merge branch 'master' of gitlab.com:geeks-accelerator/oss/saas-starter-kit into issue8/datadog-lambda-func

This commit is contained in:
Lee Brown
2019-08-16 13:36:04 -08:00
26 changed files with 370 additions and 103 deletions

View File

@ -87,8 +87,8 @@ webapp:deploy:dev:
SERVICE: 'web-app' SERVICE: 'web-app'
ENABLE_HTTPS: 1 ENABLE_HTTPS: 1
ENABLE_ELB: 0 ENABLE_ELB: 0
PRIMARY_HOST: 'eproc.tech' PRIMARY_HOST: 'example.saasstartupkit.com'
HOST_NAMES: 'www.eproc.tech,dev.eproc.tech' HOST_NAMES: 'example.saasstartupkit.com,dev.example.saasstartupkit.com'
S3_BUCKET_PRIVATE: 'saas-starter-kit-private' S3_BUCKET_PRIVATE: 'saas-starter-kit-private'
S3_BUCKET_PUBLIC: 'saas-starter-kit-public' S3_BUCKET_PUBLIC: 'saas-starter-kit-public'
S3_BUCKET_PUBLIC_CLOUDFRONT: 'true' S3_BUCKET_PUBLIC_CLOUDFRONT: 'true'
@ -96,7 +96,7 @@ webapp:deploy:dev:
STATIC_FILES_IMG_RESIZE: 'true' STATIC_FILES_IMG_RESIZE: 'true'
AWS_USE_ROLE: 'true' AWS_USE_ROLE: 'true'
EMAIL_SENDER: 'lee+saas-starter-kit@geeksinthewoods.com' EMAIL_SENDER: 'lee+saas-starter-kit@geeksinthewoods.com'
WEB_API_BASE_URL: https://api.eproc.tech WEB_API_BASE_URL: https://api.example.saasstartupkit.com
webapi:build:dev: webapi:build:dev:
<<: *build_tmpl <<: *build_tmpl
@ -128,8 +128,8 @@ webapi:deploy:dev:
SERVICE: 'web-api' SERVICE: 'web-api'
ENABLE_HTTPS: 1 ENABLE_HTTPS: 1
ENABLE_ELB: 0 ENABLE_ELB: 0
PRIMARY_HOST: 'api.eproc.tech' PRIMARY_HOST: 'api.example.saasstartupkit.com'
HOST_NAMES: 'api.dev.eproc.tech' HOST_NAMES: 'api.dev.example.saasstartupkit.com'
S3_BUCKET_PRIVATE: 'saas-starter-kit-private' S3_BUCKET_PRIVATE: 'saas-starter-kit-private'
S3_BUCKET_PUBLIC: 'saas-starter-kit-public' S3_BUCKET_PUBLIC: 'saas-starter-kit-public'
S3_BUCKET_PUBLIC_CLOUDFRONT: 'false' S3_BUCKET_PUBLIC_CLOUDFRONT: 'false'
@ -137,7 +137,7 @@ webapi:deploy:dev:
STATIC_FILES_IMG_RESIZE: 'false' STATIC_FILES_IMG_RESIZE: 'false'
AWS_USE_ROLE: 'true' AWS_USE_ROLE: 'true'
EMAIL_SENDER: 'lee+saas-starter-kit@geeksinthewoods.com' EMAIL_SENDER: 'lee+saas-starter-kit@geeksinthewoods.com'
WEB_APP_BASE_URL: https://eproc.tech WEB_APP_BASE_URL: https://example.saasstartupkit.com
#ddlogscollector:deploy:stage: #ddlogscollector:deploy:stage:
# <<: *deploy_stage_tmpl # <<: *deploy_stage_tmpl

142
README.md
View File

@ -62,7 +62,7 @@ delivered to clients.
a knowledge of a completely different expertise - DevOps. This project provides a complete continuous build pipeline that a knowledge of a completely different expertise - DevOps. This project provides a complete continuous build pipeline that
will push the code to production with minimal effort using serverless deployments to AWS Fargate with GitLab CI/CD. will push the code to production with minimal effort using serverless deployments to AWS Fargate with GitLab CI/CD.
5. Observability - Ensure the code is running as expected in a remote environment. This project implements Datadog to 5. Observability - Ensure the code is running as expected in a remote environment. This project implements Datadog to
facilitate exposing metrics, logs and request tracing to obversabe and validate your services are stable and responsive facilitate exposing metrics, logs and request tracing to obverse and validate your services are stable and responsive
for your clients (hopefully paying clients). for your clients (hopefully paying clients).
@ -71,7 +71,7 @@ facilitate exposing metrics, logs and request tracing to obversabe and validate
The example project is a complete starter kit for building SasS with GoLang. It provides two example services: The example project is a complete starter kit for building SasS with GoLang. It provides two example services:
* Web App - Responsive web application to provide service to clients. Includes user signup and user authentication for * Web App - Responsive web application to provide service to clients. Includes user signup and user authentication for
direct client interaction via their web browsers. direct client interaction via their web browsers.
* Web API - REST API with JWT authentication that renders results as JSON. This allows clients and other third-pary companies to develop deep * Web API - REST API with JWT authentication that renders results as JSON. This allows clients and other third-party companies to develop deep
integrations with the project. integrations with the project.
The example project also provides these tools: The example project also provides these tools:
@ -106,7 +106,7 @@ Accordingly, the project architecture is illustrated with the following diagram.
With SaaS, a client subscribes to an online service you provide them. The example project provides functionality for With SaaS, a client subscribes to an online service you provide them. The example project provides functionality for
clients to subscribe and then once subscribed they can interact with your software service. clients to subscribe and then once subscribed they can interact with your software service.
The initial contributors to this project are building this saas-starter-kit based on their years of experience building enterprise B2B SaaS. Particularily, this saas-starter-kit is based on their most recent experience building the The initial contributors to this project are building this saas-starter-kit based on their years of experience building enterprise B2B SaaS. Particularly, this saas-starter-kit is based on their most recent experience building the
B2B SaaS for [standard operating procedure software](https://keeni.space) (written entirely in Golang). Please refer to the Keeni.Space website, B2B SaaS for [standard operating procedure software](https://keeni.space) (written entirely in Golang). Please refer to the Keeni.Space website,
its [SOP software pricing](https://keeni.space/pricing) and its signup process. The SaaS web app is then available at its [SOP software pricing](https://keeni.space/pricing) and its signup process. The SaaS web app is then available at
[app.keeni.space](https://app.keeni.space). They plan on leveraging this experience and build it into a simplified set [app.keeni.space](https://app.keeni.space). They plan on leveraging this experience and build it into a simplified set
@ -175,7 +175,7 @@ $ git clone git@gitlab.com:geeks-accelerator/oss/saas-starter-kit.git
$ cd saas-starter-kit/ $ cd saas-starter-kit/
``` ```
If you have Go Modules enabled, you should be able compile the project locally. If you have Go Modulels disabled, see If you have Go Modules enabled, you should be able compile the project locally. If you have Go Modules disabled, see
the next section. the next section.
@ -269,39 +269,133 @@ builds locally, update `docker-compose.yaml` to define a volume.
### Re-starting a specific Go service for development ### Re-starting a specific Go service for development
When writing code in an iterative fashion, it is nice to be able to restart a specific service so it will run updated When writing code in an iterative fashion, it is nice to have your change automatically rebuilt. This project uses
Go code. This decreases the overhead of stopping all services with `docker-compose down` and then re-starting all the [github.com/gravityblast/fresh](https://github.com/gravityblast/fresh) to recompile your services that will include most
services again with 'docker-compose up'. changes.
Fresh is a command line tool that builds and (re)starts your web application everytime you save a Go or template file.
The (Fresh configuration file](https://gitlab.com/geeks-accelerator/oss/saas-starter-kit/blob/master/fresh-auto-reload.conf)
is located in the project root. By default the following folders are watched by Fresh:
- handlers
- static
- templates
Any changes to [internal/*](https://gitlab.com/geeks-accelerator/oss/saas-starter-kit/tree/master/internal) or
additional project dependencies added to [go.mod](https://gitlab.com/geeks-accelerator/oss/saas-starter-kit/blob/master/go.mod)
will require the service to be rebuilt.
To restart a specific service, first use `docker ps` to see the list of services running.
```bash ```bash
$ docker ps docker-compose up --build -d web-app
CONTAINER ID IMAGE COMMAND NAMES
35043164fd0d example-project/web-api:latest "/gosrv" saas-starter-kit_web-api_1
d34c8fc27f3b example-project/web-app:latest "/gosrv" saas-starter-kit_web-app_1
fd844456243e postgres:11-alpine "docker-entrypoint.s…" saas-starter-kit_postgres_1
dda16bfbb8b5 redis:latest "redis-server --appe…" saas-starter-kit_redis_1
``` ```
Then use `docker-compose stop` for a specific service. In the command including the name of service in `docker-compose.yaml` file for the service
to shut down. In the example command, we will shut down the web-api service so we can start it again.
### Forking your own copy
1. Checkout the project
2. Update references.
```bash ```bash
$ docker-compose stop web-app flist=`grep -r "geeks-accelerator/oss/saas-starter-kit" * | awk -F ':' '{print $1}' | sort | uniq`
for f in $flist; do echo $f; sed -i "" -e "s#geeks-accelerator/oss/saas-starter-kit#geeks-accelerator/oss/aurora-cam#g" $f; done
flist=`grep -r "saas-starter-kit" * | awk -F ':' '{print $1}' | sort | uniq`
for f in $flist; do echo $f; sed -i "" -e "s#saas-starter-kit#aurora-cam#g" $f; done
flist=`grep -r "example-project" * | awk -F ':' '{print $1}' | sort | uniq`
for f in $flist; do echo $f; sed -i "" -e "s#example-project#aurora-cam#g" $f; done
``` ```
If you are not in the directory for the service you want to restart then navigate to it. We will go to the directory for the 3. Create a new AWS Policy with the following details:
web-api. ```
Name: SaasStarterKitDevServices
Description: Defines access for saas-starter-kit services.
Policy Document: {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DefaultServiceAccess",
"Effect": "Allow",
"Action": [
"s3:HeadBucket",
"s3:ListObjects",
"s3:PutObject",
"s3:PutObjectAcl",
"cloudfront:ListDistributions",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ecs:ListTasks",
"ecs:DescribeServices",
"ecs:DescribeTasks",
"ec2:DescribeNetworkInterfaces",
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"route53:ChangeResourceRecordSets",
"ecs:UpdateService",
"ses:SendEmail",
"ses:ListIdentities",
"ses:GetAccountSendingEnabled",
"secretsmanager:ListSecretVersionIds",
"secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret",
"secretsmanager:RestoreSecret",
"secretsmanager:DeleteSecret"
],
"Resource": "*"
},
{
"Sid": "ServiceInvokeLambda",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"lambda:InvokeFunction",
"lambda:ListVersionsByFunction",
"lambda:GetFunction",
"lambda:InvokeAsync",
"lambda:GetFunctionConfiguration",
"iam:PassRole",
"lambda:GetAlias",
"lambda:GetPolicy"
],
"Resource": [
"arn:aws:iam:::role/*",
"arn:aws:lambda:::function:*"
]
},
{
"Sid": "datadoglambda",
"Effect": "Allow",
"Action": [
"cloudwatch:Get*",
"cloudwatch:List*",
"ec2:Describe*",
"support:*",
"tag:GetResources",
"tag:GetTagKeys",
"tag:GetTagValues"
],
"Resource": "*"
}
]
}
```
Create a new user with programmatic access and directly attach it the policy `SaasStarterKitDevServices`
4. Create a new docker-compose config file
```bash ```bash
$ cd cmd/web-api/ cp sample.env_docker_compose .env_docker_compose
``` ```
Then you can start the service again by running main.go 5. Update .env_docker_compose with the Access key ID and Secret access key
```bash
$ go run main.go 6. Update `.gitlab-ci.yml` with relevant details.
```
### Optional. Set AWS and Datadog Configs ### Optional. Set AWS and Datadog Configs

View File

@ -29,8 +29,9 @@ ENV GO111MODULE="on"
COPY go.mod . COPY go.mod .
COPY go.sum . COPY go.sum .
RUN go mod download RUN go mod download
RUN go get github.com/pilu/fresh
FROM build_base_golang AS builder FROM build_base_golang AS dev
ARG service ARG service
ARG commit_ref=- ARG commit_ref=-
@ -40,15 +41,22 @@ ARG swagInit
COPY internal ./internal COPY internal ./internal
# Copy cmd specific packages. # Copy cmd specific packages.
COPY cmd/${service} ./cmd/web-api COPY cmd/${service} ./cmd/${service}
COPY cmd/${service}/templates /templates COPY cmd/${service}/templates /templates
#COPY cmd/${service}/static /static #COPY cmd/${service}/static /static
# Copy the global templates. # Copy the global templates.
ADD resources/templates/shared /templates/shared ADD resources/templates/shared /templates/shared
ADD fresh-auto-reload.conf /runner.conf
ENV TEMPLATE_DIR=/templates
WORKDIR ./cmd/${service} WORKDIR ./cmd/${service}
ENTRYPOINT ["fresh", "-c", "/runner.conf"]
FROM dev AS builder
# Update the API documentation. # Update the API documentation.
# Disabled for the moment as it takes forever to run, rely on manual execution. # Disabled for the moment as it takes forever to run, rely on manual execution.
RUN if [ "$swagInit" != "" ]; then swag init ; fi RUN if [ "$swagInit" != "" ]; then swag init ; fi

View File

@ -254,7 +254,7 @@ swag init
### Additional Swagger Annotations ### Additional Swagger Annotations
Below are some additional example annotions that can be added to `main.go` Below are some additional example annotations that can be added to `main.go`
```go ```go
// @title SaaS Example API // @title SaaS Example API
// @description This provides a public API... // @description This provides a public API...

View File

@ -66,8 +66,10 @@ func main() {
// ========================================================================= // =========================================================================
// Logging // Logging
log.SetFlags(log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
log.SetPrefix(service+" : ")
log := log.New(os.Stdout, log.Prefix() , log.Flags())
log := log.New(os.Stdout, service+" : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
// ========================================================================= // =========================================================================
// Configuration // Configuration
@ -87,11 +89,11 @@ func main() {
Service struct { Service struct {
Name string `default:"web-api" envconfig:"NAME"` Name string `default:"web-api" envconfig:"NAME"`
Project string `default:"" envconfig:"PROJECT"` Project string `default:"" envconfig:"PROJECT"`
BaseUrl string `default:"" envconfig:"BASE_URL" example:"http://api.eproc.tech"` BaseUrl string `default:"" envconfig:"BASE_URL" example:"http://api.example.saasstartupkit.com"`
HostNames []string `envconfig:"HOST_NAMES" example:"alternative-subdomain.eproc.tech"` HostNames []string `envconfig:"HOST_NAMES" example:"alternative-subdomain.example.saasstartupkit.com"`
EnableHTTPS bool `default:"false" envconfig:"ENABLE_HTTPS"` EnableHTTPS bool `default:"false" envconfig:"ENABLE_HTTPS"`
TemplateDir string `default:"./templates" envconfig:"TEMPLATE_DIR"` TemplateDir string `default:"./templates" envconfig:"TEMPLATE_DIR"`
WebAppBaseUrl string `default:"http://127.0.0.1:3000" envconfig:"WEB_APP_BASE_URL" example:"www.eproc.tech"` WebAppBaseUrl string `default:"http://127.0.0.1:3000" envconfig:"WEB_APP_BASE_URL" example:"www.example.saasstartupkit.com"`
DebugHost string `default:"0.0.0.0:4000" envconfig:"DEBUG_HOST"` DebugHost string `default:"0.0.0.0:4000" envconfig:"DEBUG_HOST"`
ShutdownTimeout time.Duration `default:"5s" envconfig:"SHUTDOWN_TIMEOUT"` ShutdownTimeout time.Duration `default:"5s" envconfig:"SHUTDOWN_TIMEOUT"`
} }

View File

@ -1378,7 +1378,7 @@ func TestUserToken(t *testing.T) {
// Test user token with empty credentials. // Test user token with empty credentials.
{ {
expectedStatus := http.StatusUnauthorized expectedStatus := http.StatusBadRequest
rt := requestTest{ rt := requestTest{
fmt.Sprintf("Token %d using empty request", expectedStatus), fmt.Sprintf("Token %d using empty request", expectedStatus),
@ -1406,8 +1406,24 @@ func TestUserToken(t *testing.T) {
expected := weberror.ErrorResponse{ expected := weberror.ErrorResponse{
StatusCode: expectedStatus, StatusCode: expectedStatus,
Error: http.StatusText(expectedStatus), Error: "Field validation error",
Details: "must provide email and password in Basic auth", Fields: []weberror.FieldError{
{
Field: "username",
Value: "",
Tag: "required",
Error: "username is a required field",
Display: "username is a required field",
},
{
Field: "password",
Value: "",
Tag: "required",
Error: "password is a required field",
Display: "password is a required field",
},
},
Details: actual.Details,
StackTrace: actual.StackTrace, StackTrace: actual.StackTrace,
} }

View File

@ -13,8 +13,9 @@ ENV GO111MODULE="on"
COPY go.mod . COPY go.mod .
COPY go.sum . COPY go.sum .
RUN go mod download RUN go mod download
RUN go get github.com/pilu/fresh
FROM build_base_golang AS builder FROM build_base_golang AS dev
ARG service ARG service
ARG commit_ref=- ARG commit_ref=-
@ -23,15 +24,22 @@ ARG commit_ref=-
COPY internal ./internal COPY internal ./internal
# Copy cmd specific packages. # Copy cmd specific packages.
COPY cmd/${service} ./cmd/web-app COPY cmd/${service} ./cmd/${service}
COPY cmd/${service}/templates /templates COPY cmd/${service}/templates /templates
COPY cmd/${service}/static /static COPY cmd/${service}/static /static
# Copy the global templates. # Copy the global templates.
ADD resources/templates/shared /templates/shared ADD resources/templates/shared /templates/shared
ADD fresh-auto-reload.conf /runner.conf
ENV TEMPLATE_DIR=/templates
WORKDIR ./cmd/${service} WORKDIR ./cmd/${service}
ENTRYPOINT ["fresh", "-c", "/runner.conf"]
FROM dev AS builder
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.build=${commit_ref}" -a -installsuffix nocgo -o /gosrv . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.build=${commit_ref}" -a -installsuffix nocgo -o /gosrv .
FROM alpine:3.9 FROM alpine:3.9

View File

@ -24,7 +24,7 @@ http://127.0.0.1:3000/
While the web-api service has While the web-api service has
significant functionality, this web-app service is still in development. Currently this web-app services only resizes significant functionality, this web-app service is still in development. Currently this web-app services only resizes
an image and displays resvised versions of it on the index page. See section below on Future Functionality. an image and displays resized versions of it on the index page. See section below on Future Functionality.
If you would like to help, please email twins@geeksinthewoods.com. If you would like to help, please email twins@geeksinthewoods.com.

View File

@ -249,7 +249,7 @@ func (h *Account) Update(ctx context.Context, w http.ResponseWriter, r *http.Req
return false, err return false, err
} }
data["geonameCountries"] = geonames.ValidGeonameCountries data["geonameCountries"] = geonames.ValidGeonameCountries(ctx)
data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "") data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "")
if err != nil { if err != nil {

View File

@ -45,6 +45,7 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
// Build a sitemap. // Build a sitemap.
sm := stm.NewSitemap(1) sm := stm.NewSitemap(1)
sm.SetVerbose(false)
sm.SetDefaultHost(projectRoutes.WebAppUrl("")) sm.SetDefaultHost(projectRoutes.WebAppUrl(""))
sm.Create() sm.Create()

View File

@ -105,7 +105,7 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque
return nil return nil
} }
data["geonameCountries"] = geonames.ValidGeonameCountries data["geonameCountries"] = geonames.ValidGeonameCountries(ctx)
data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "") data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "")
if err != nil { if err != nil {

View File

@ -66,8 +66,10 @@ func main() {
// ========================================================================= // =========================================================================
// Logging // Logging
log.SetFlags(log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
log.SetPrefix(service+" : ")
log := log.New(os.Stdout, log.Prefix() , log.Flags())
log := log.New(os.Stdout, service+" : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
// ========================================================================= // =========================================================================
// Configuration // Configuration
@ -87,8 +89,8 @@ func main() {
Service struct { Service struct {
Name string `default:"web-app" envconfig:"NAME"` Name string `default:"web-app" envconfig:"NAME"`
Project string `default:"" envconfig:"PROJECT"` Project string `default:"" envconfig:"PROJECT"`
BaseUrl string `default:"" envconfig:"BASE_URL" example:"http://eproc.tech"` BaseUrl string `default:"" envconfig:"BASE_URL" example:"http://example.saasstartupkit.com"`
HostNames []string `envconfig:"HOST_NAMES" example:"www.eproc.tech"` HostNames []string `envconfig:"HOST_NAMES" example:"www.example.saasstartupkit.com"`
EnableHTTPS bool `default:"false" envconfig:"ENABLE_HTTPS"` EnableHTTPS bool `default:"false" envconfig:"ENABLE_HTTPS"`
TemplateDir string `default:"./templates" envconfig:"TEMPLATE_DIR"` TemplateDir string `default:"./templates" envconfig:"TEMPLATE_DIR"`
SharedTemplateDir string `default:"../../resources/templates/shared" envconfig:"SHARED_TEMPLATE_DIR"` SharedTemplateDir string `default:"../../resources/templates/shared" envconfig:"SHARED_TEMPLATE_DIR"`
@ -99,10 +101,10 @@ func main() {
CloudFrontEnabled bool `envconfig:"CLOUDFRONT_ENABLED"` CloudFrontEnabled bool `envconfig:"CLOUDFRONT_ENABLED"`
ImgResizeEnabled bool `envconfig:"IMG_RESIZE_ENABLED"` ImgResizeEnabled bool `envconfig:"IMG_RESIZE_ENABLED"`
} }
WebApiBaseUrl string `default:"http://127.0.0.1:3001" envconfig:"WEB_API_BASE_URL" example:"http://api.eproc.tech"` WebApiBaseUrl string `default:"http://127.0.0.1:3001" envconfig:"WEB_API_BASE_URL" example:"http://api.example.saasstartupkit.com"`
SessionKey string `default:"" envconfig:"SESSION_KEY"` SessionKey string `default:"" envconfig:"SESSION_KEY"`
SessionName string `default:"" envconfig:"SESSION_NAME"` SessionName string `default:"" envconfig:"SESSION_NAME"`
EmailSender string `default:"test@eproc.tech" envconfig:"EMAIL_SENDER"` EmailSender string `default:"test@example.saasstartupkit.com" envconfig:"EMAIL_SENDER"`
DebugHost string `default:"0.0.0.0:4000" envconfig:"DEBUG_HOST"` DebugHost string `default:"0.0.0.0:4000" envconfig:"DEBUG_HOST"`
ShutdownTimeout time.Duration `default:"5s" envconfig:"SHUTDOWN_TIMEOUT"` ShutdownTimeout time.Duration `default:"5s" envconfig:"SHUTDOWN_TIMEOUT"`
} }
@ -774,6 +776,9 @@ func main() {
} }
imgUrlFormatter = func(p string) string { imgUrlFormatter = func(p string) string {
if strings.HasPrefix(p, "http") {
return p
}
baseUrl.Path = p baseUrl.Path = p
return baseUrl.String() return baseUrl.String()
} }
@ -818,7 +823,12 @@ func main() {
tmplFuncs["S3ImgUrl"] = func(ctx context.Context, p string, size int) string { tmplFuncs["S3ImgUrl"] = func(ctx context.Context, p string, size int) string {
imgUrl := imgUrlFormatter(p) imgUrl := imgUrlFormatter(p)
if cfg.Service.StaticFiles.ImgResizeEnabled { if cfg.Service.StaticFiles.ImgResizeEnabled {
imgUrl, _ = img_resize.S3ImgUrl(ctx, redisClient, staticS3UrlFormatter, awsSession, cfg.Aws.S3BucketPublic, imgResizeS3KeyPrefix, imgUrl, size) var rerr error
imgUrl, rerr = img_resize.S3ImgUrl(ctx, redisClient, staticS3UrlFormatter, awsSession, cfg.Aws.S3BucketPublic, imgResizeS3KeyPrefix, imgUrl, size)
if rerr != nil {
imgUrl = "error"
log.Printf("main : S3ImgUrl : %s - %s\n", p, rerr)
}
} }
return imgUrl return imgUrl
} }
@ -843,6 +853,10 @@ func main() {
} }
} }
if web.RequestIsImage(r) {
return err
}
switch statusCode { switch statusCode {
case http.StatusUnauthorized: case http.StatusUnauthorized:
http.Redirect(w, r, "/user/login?redirect="+url.QueryEscape(r.RequestURI), http.StatusFound) http.Redirect(w, r, "/user/login?redirect="+url.QueryEscape(r.RequestURI), http.StatusFound)

View File

@ -3,7 +3,6 @@
{{end}} {{end}}
{{define "content"}} {{define "content"}}
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/account">Account</a></li> <li class="breadcrumb-item"><a href="/account">Account</a></li>

View File

@ -2,7 +2,7 @@
# docker-compose up # docker-compose up
# docker-compose stop # docker-compose stop
# docker-compose down # docker-compose down
version: '3' version: '3.7'
networks: networks:
main: main:
@ -63,9 +63,12 @@ services:
image: example-project/web-app:latest image: example-project/web-app:latest
build: build:
context: . context: .
target: dev
dockerfile: cmd/web-app/Dockerfile dockerfile: cmd/web-app/Dockerfile
args: args:
service: 'web-app' service: 'web-app'
volumes:
- ./:/go/src/gitlab.com/geeks-accelerator/oss/saas-starter-kit
ports: ports:
- 3000:3000 # WEB APP - 3000:3000 # WEB APP
- 4000:4000 # DEBUG API - 4000:4000 # DEBUG API
@ -99,9 +102,12 @@ services:
image: example-project/web-api:latest image: example-project/web-api:latest
build: build:
context: . context: .
target: dev
dockerfile: cmd/web-api/Dockerfile dockerfile: cmd/web-api/Dockerfile
args: args:
service: 'web-api' service: 'web-api'
volumes:
- ./:/go/src/gitlab.com/geeks-accelerator/oss/saas-starter-kit
ports: ports:
- 3001:3001 # WEB API - 3001:3001 # WEB API
- 4001:4001 # DEBUG API - 4001:4001 # DEBUG API

14
fresh-auto-reload.conf Normal file
View File

@ -0,0 +1,14 @@
root: .
tmp_path: ./tmp
build_name: runner-build
build_log: runner-build-errors.log
valid_ext: .go, .tpl, .tmpl, .html, .gohtml
no_rebuild_ext: .tpl, .tmpl, .html
ignored: assets, tmp
build_delay: 600
colors: 1
log_color_main: cyan
log_color_build: yellow
log_color_runner: green
log_color_watcher: magenta
log_color_app:

12
go.mod
View File

@ -3,12 +3,9 @@ module geeks-accelerator/oss/saas-starter-kit
require ( require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/aws/aws-sdk-go v1.23.0 github.com/aws/aws-sdk-go v1.23.0
github.com/bobesa/go-domain-util v0.0.0-20180815122459-1d708c097a6a
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dimfeld/httptreemux v5.0.1+incompatible github.com/dimfeld/httptreemux v5.0.1+incompatible
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/fatih/camelcase v1.0.0
github.com/fatih/structtag v1.0.0
github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14 github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14
github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db
github.com/geeks-accelerator/swag v1.6.3 github.com/geeks-accelerator/swag v1.6.3
@ -28,33 +25,28 @@ require (
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0
github.com/json-iterator/go v1.1.7 // indirect
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/kr/pty v1.1.8 // indirect
github.com/lib/pq v1.2.0 github.com/lib/pq v1.2.0
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 github.com/onsi/gomega v1.5.0 // indirect
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/sergi/go-diff v1.0.0
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
github.com/sudo-suhas/symcrypto v1.0.0 github.com/sudo-suhas/symcrypto v1.0.0
github.com/ugorji/go v1.1.7 // indirect
github.com/urfave/cli v1.21.0 github.com/urfave/cli v1.21.0
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
gitlab.com/geeks-accelerator/oss/devops v0.0.0-20190815180027-17c30c1f4c9e gitlab.com/geeks-accelerator/oss/devops v0.0.0-20190815180027-17c30c1f4c9e
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de // indirect golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1 gopkg.in/DataDog/dd-trace-go.v1 v1.16.1
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-playground/validator.v9 v9.29.1 gopkg.in/go-playground/validator.v9 v9.29.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
) )
replace gitlab.com/geeks-accelerator/oss/devops => ../devops replace gitlab.com/geeks-accelerator/oss/devops => ../devops

36
go.sum
View File

@ -8,15 +8,13 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/aws/aws-sdk-go v1.21.8 h1:Lv6hW2twBhC6mGZAuWtqplEpIIqtVctJg02sE7Qn0Zw=
github.com/aws/aws-sdk-go v1.21.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.23.0 h1:ilfJN/vJtFo1XDFxB2YMBYGeOvGZl6Qow17oyD4+Z9A= github.com/aws/aws-sdk-go v1.23.0 h1:ilfJN/vJtFo1XDFxB2YMBYGeOvGZl6Qow17oyD4+Z9A=
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/bobesa/go-domain-util v0.0.0-20180815122459-1d708c097a6a/go.mod h1:/mf0HzRK9xVv+1puqGSMzCo7bhEcQhiisuUXlMkq2p4= github.com/bobesa/go-domain-util v0.0.0-20180815122459-1d708c097a6a/go.mod h1:/mf0HzRK9xVv+1puqGSMzCo7bhEcQhiisuUXlMkq2p4=
github.com/clbanning/mxj v1.8.3 h1:2r/KCJi52w2MRz+K+UMa/1d7DdCjnLqYJfnbr7dYNWI=
github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -27,12 +25,8 @@ github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4w
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0= github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14 h1:Rrxsq3gr2TWGdnSWHfRbhP/hcxatCyC9kMgLZ3da75A= github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14 h1:Rrxsq3gr2TWGdnSWHfRbhP/hcxatCyC9kMgLZ3da75A=
@ -80,10 +74,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
github.com/golang/protobuf v1.3.1/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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -108,14 +100,12 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
@ -129,15 +119,14 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.7/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.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/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/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
@ -156,8 +145,6 @@ 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.8.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 h1:X9XMOYjxEfAYSy3xK1DzO5dMkkWhs9E9UCcS1IERx2k= github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 h1:X9XMOYjxEfAYSy3xK1DzO5dMkkWhs9E9UCcS1IERx2k=
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
@ -173,9 +160,7 @@ github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE= github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
@ -199,8 +184,6 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -213,23 +196,16 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190730183949-1393eb018365 h1:SaXEMXhWzMJThc05vu6uh61Q245r4KaWMrsTedk0FDc= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190730205120-7deaedd405c4 h1:GhbPrljMrt6gCNHHAJcWLDV3nDPFkIm0EEuqY9GtuX0=
golang.org/x/tools v0.0.0-20190730205120-7deaedd405c4/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a h1:0AGI+cC4FJwXNdClvHzfHhJf/yPjKwdo/+m0lPKrdJA=
golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de h1:VNumCimp/Bwk6fRqgPHkjiUPZ/vzlpi23/kQTuQ4gBA= golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de h1:VNumCimp/Bwk6fRqgPHkjiUPZ/vzlpi23/kQTuQ4gBA=
golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
@ -239,6 +215,8 @@ google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1 h1:Dngw1zun6yTYFHNdzEWBlrJzFA2QJMjSA2sZ4nH2UWo= gopkg.in/DataDog/dd-trace-go.v1 v1.16.1 h1:Dngw1zun6yTYFHNdzEWBlrJzFA2QJMjSA2sZ4nH2UWo=
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/DataDog/dd-trace-go.v1 v1.16.1/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -251,8 +229,6 @@ gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvR
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=

View File

@ -96,6 +96,10 @@ func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session
return errors.Wrapf(err, "failed to list tasks for cluster '%s' service '%s'", ecsClusterName, ecsServiceName) return errors.Wrapf(err, "failed to list tasks for cluster '%s' service '%s'", ecsClusterName, ecsServiceName)
} }
if len(servceTaskRes.TaskArns) == 0 {
continue
}
taskRes, err := svc.DescribeTasks(&ecs.DescribeTasksInput{ taskRes, err := svc.DescribeTasks(&ecs.DescribeTasksInput{
Cluster: aws.String(ecsClusterName), Cluster: aws.String(ecsClusterName),
Tasks: servceTaskRes.TaskArns, Tasks: servceTaskRes.TaskArns,
@ -148,6 +152,10 @@ func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session
time.Sleep((time.Duration(a) * time.Second * 10) * time.Duration(a)) time.Sleep((time.Duration(a) * time.Second * 10) * time.Duration(a))
} }
if len(networkInterfaceIds) == 0 {
return errors.New("Unable to update public IPs. No network interfaces found.")
}
log.Println("Get public IPs for network interface IDs.") log.Println("Get public IPs for network interface IDs.")
var publicIps []string var publicIps []string
for a := 0; a <= 3; a++ { for a := 0; a <= 3; a++ {
@ -202,7 +210,11 @@ func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session
} }
// Add all the A record names with the same set of public IPs. // Add all the A record names with the same set of public IPs.
addedNames := make(map[string]bool)
for _, aName := range aNames { for _, aName := range aNames {
if addedNames[aName] {
continue
}
log.Printf("\t\tAdd A record for '%s'.\n", aName) log.Printf("\t\tAdd A record for '%s'.\n", aName)
input.ChangeBatch.Changes = append(input.ChangeBatch.Changes, &route53.Change{ input.ChangeBatch.Changes = append(input.ChangeBatch.Changes, &route53.Change{
@ -214,6 +226,7 @@ func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session
Type: aws.String("A"), Type: aws.String("A"),
}, },
}) })
addedNames[aName] = true
} }
_, err := svc.ChangeResourceRecordSets(input) _, err := svc.ChangeResourceRecordSets(input)

View File

@ -2,8 +2,8 @@ package tests
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"io" "io"
"log" "log"
"os" "os"
@ -13,9 +13,14 @@ import (
"time" "time"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/docker" "geeks-accelerator/oss/saas-starter-kit/internal/platform/docker"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"geeks-accelerator/oss/saas-starter-kit/internal/schema" "geeks-accelerator/oss/saas-starter-kit/internal/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/kelseyhightower/envconfig"
) )
// Success and failure markers. // Success and failure markers.
@ -43,10 +48,72 @@ func New() *Test {
log := log.New(os.Stdout, "TEST : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) log := log.New(os.Stdout, "TEST : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
// =========================================================================
// Configuration
var cfg struct {
Aws struct {
AccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID"` // WEB_API_AWS_AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_ID
SecretAccessKey string `envconfig:"AWS_SECRET_ACCESS_KEY" json:"-"` // don't print
Region string `default:"us-west-2" envconfig:"AWS_REGION"`
UseRole bool `envconfig:"AWS_USE_ROLE"`
}
}
// For additional details refer to https://github.com/kelseyhightower/envconfig
if err := envconfig.Process("TESTS", &cfg); err != nil {
log.Fatalf("startup : Parsing Config : %+v", err)
}
// AWS access keys are required, if roles are enabled, remove any placeholders.
if cfg.Aws.UseRole {
cfg.Aws.AccessKeyID = ""
cfg.Aws.SecretAccessKey = ""
// Get an AWS session from an implicit source if no explicit
// configuration is provided. This is useful for taking advantage of
// EC2/ECS instance roles.
if cfg.Aws.Region == "" {
sess := session.Must(session.NewSession())
md := ec2metadata.New(sess)
var err error
cfg.Aws.Region, err = md.Region()
if err != nil {
log.Fatalf("startup : Load region of ecs metadata : %+v", err)
}
}
}
// Print the config for our logs. It's important to any credentials in the config
// that could expose a security risk are excluded from being json encoded by
// applying the tag `json:"-"` to the struct var.
{
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
log.Fatalf("startup : Marshalling Config to JSON : %+v", err)
}
log.Printf("startup : Config : %v\n", string(cfgJSON))
}
// ============================================================ // ============================================================
// Init AWS Session // Init AWS Session
var awsSession *session.Session
if cfg.Aws.UseRole {
// Get an AWS session from an implicit source if no explicit
// configuration is provided. This is useful for taking advantage of
// EC2/ECS instance roles.
awsSession = session.Must(session.NewSession())
if cfg.Aws.Region != "" {
awsSession.Config.WithRegion(cfg.Aws.Region)
}
awsSession := session.Must(session.NewSession()) log.Printf("startup : AWS : Using role.\n")
} else if cfg.Aws.AccessKeyID != "" {
creds := credentials.NewStaticCredentials(cfg.Aws.AccessKeyID, cfg.Aws.SecretAccessKey, "")
awsSession = session.New(&aws.Config{Region: aws.String(cfg.Aws.Region), Credentials: creds})
log.Printf("startup : AWS : Using static credentials\n")
}
// ============================================================ // ============================================================
// Startup Postgres container // Startup Postgres container
@ -93,8 +160,16 @@ func New() *Test {
log.Fatalf("startup : Register DB : %v", err) log.Fatalf("startup : Register DB : %v", err)
} }
// Set the context with the required values to
// process the request.
v := webcontext.Values{
Now: time.Now(),
Env: webcontext.Env_Dev,
}
ctx := context.WithValue(context.Background(), webcontext.KeyValues, &v)
// Execute the migrations // Execute the migrations
if err = schema.Migrate(masterDB, log, true); err != nil { if err = schema.Migrate(ctx, masterDB, log, true); err != nil {
log.Fatalf("main : Migrate : %v", err) log.Fatalf("main : Migrate : %v", err)
} }
log.Printf("main : Migrate : Completed") log.Printf("main : Migrate : Completed")

View File

@ -16,6 +16,7 @@ import (
// Headers // Headers
const ( const (
HeaderAccept = "Accept"
HeaderUpgrade = "Upgrade" HeaderUpgrade = "Upgrade"
HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto" HeaderXForwardedProto = "X-Forwarded-Proto"
@ -127,6 +128,11 @@ func RequestIsWebSocket(r *http.Request) bool {
return strings.ToLower(upgrade) == "websocket" return strings.ToLower(upgrade) == "websocket"
} }
func RequestIsImage(r *http.Request) bool {
accept := r.Header.Get(HeaderAccept)
return strings.HasPrefix(accept, "image/")
}
func RequestScheme(r *http.Request) string { func RequestScheme(r *http.Request) string {
// Can't use `r.Request.URL.Scheme` // Can't use `r.Request.URL.Scheme`
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0

View File

@ -191,6 +191,10 @@ func RenderError(ctx context.Context, w http.ResponseWriter, r *http.Request, er
} }
v.StatusCode = webErr.Status v.StatusCode = webErr.Status
if RequestIsImage(r) {
return nil
}
resp := webErr.Response(ctx, true) resp := webErr.Response(ctx, true)
data := map[string]interface{}{ data := map[string]interface{}{

View File

@ -279,6 +279,11 @@ func NewTemplateRenderer(templateDir string, enableHotReload bool, globalViewDat
// statusCode: the error method calls this function so allow the HTTP Status Code to be set // statusCode: the error method calls this function so allow the HTTP Status Code to be set
// data: map[string]interface{} to allow including additional request and globally defined values. // data: map[string]interface{} to allow including additional request and globally defined values.
func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, req *http.Request, templateLayoutName, templateContentName, contentType string, statusCode int, data map[string]interface{}) error { func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, req *http.Request, templateLayoutName, templateContentName, contentType string, statusCode int, data map[string]interface{}) error {
// Not really anyway to render an image response using a template.
if web.RequestIsImage(req) {
return nil
}
// If the template has not been rendered yet or hot reload is enabled, // If the template has not been rendered yet or hot reload is enabled,
// then parse the template files. // then parse the template files.
t, ok := r.templates[templateContentName] t, ok := r.templates[templateContentName]

View File

@ -3,3 +3,8 @@
#AWS_REGION=us-west-2 #AWS_REGION=us-west-2
#AWS_USE_ROLE=false #AWS_USE_ROLE=false
#DD_API_KEY= #DD_API_KEY=
#WEB_APP_AWS_S3_BUCKET_PRIVATE=
#WEB_APP_AWS_S3_BUCKET_PUBLIC=
#WEB_API_AWS_S3_BUCKET_PRIVATE=
#WEB_API_AWS_S3_BUCKET_PUBLIC=
#EMAIL_SENDER=

View File

@ -26,7 +26,7 @@ in other configuration files. And since this project is open-source, we wanted t
If you don't have an AWS account, signup for one now and then proceed with the deployment setup. If you don't have an AWS account, signup for one now and then proceed with the deployment setup.
We assume that if you are deploying the SaaS Stater Kit, you are starting from scratch with no existing dependencies. We assume that if you are deploying the SaaS Starter Kit, you are starting from scratch with no existing dependencies.
This however, excludes any domain names that you would like to use for resolving your services publicly. To use any This however, excludes any domain names that you would like to use for resolving your services publicly. To use any
pre-purchased domain names, make sure they are added to Route 53 in the AWS account. Or you can let the deploy script pre-purchased domain names, make sure they are added to Route 53 in the AWS account. Or you can let the deploy script
create a new zone is Route 53 and update the DNS for the domain name when your ready to make the transition. It is create a new zone is Route 53 and update the DNS for the domain name when your ready to make the transition. It is
@ -235,3 +235,10 @@ instance will be a dedicated host since we need it always up and running, thus i
```bash ```bash
sudo gitlab-runner restart sudo gitlab-runner restart
``` ```
## Examples
```bash
go run main.go deploy -service=web-app -env=dev -enable_https=true -primary_host=example.saasstartupkit.com -host_names=example.saasstartupkit.com,dev.example.saasstartupkit.com -private_bucket=saas-starter-kit-private -public_bucket=saas-starter-kit-public -public_bucket_cloudfront=true -static_files_s3=true -static_files_img_resize=1 -recreate_service=0
```

View File

@ -1,6 +1,7 @@
package cicd package cicd
import ( import (
"context"
"encoding/json" "encoding/json"
"log" "log"
"path/filepath" "path/filepath"
@ -144,7 +145,7 @@ func NewMigrateRequest(log *log.Logger, flags MigrateFlags) (*migrateRequest, er
} }
// Run is the main entrypoint for migration of database schema for a given target environment. // Run is the main entrypoint for migration of database schema for a given target environment.
func Migrate(log *log.Logger, req *migrateRequest) error { func Migrate(log *log.Logger, ctx context.Context, req *migrateRequest) error {
// Load the database details. // Load the database details.
var db DB var db DB
@ -200,7 +201,7 @@ func Migrate(log *log.Logger, req *migrateRequest) error {
// Start Migrations // Start Migrations
log.Printf("\t\tStart migrations.") log.Printf("\t\tStart migrations.")
if err = schema.Migrate(masterDb, log, false); err != nil { if err = schema.Migrate(ctx, masterDb, log, false); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -1,10 +1,13 @@
package main package main
import ( import (
"context"
"expvar" "expvar"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"log" "log"
"os" "os"
"strings" "strings"
"time"
"geeks-accelerator/oss/saas-starter-kit/tools/devops/cmd/cicd" "geeks-accelerator/oss/saas-starter-kit/tools/devops/cmd/cicd"
_ "github.com/lib/pq" _ "github.com/lib/pq"
@ -111,7 +114,16 @@ func main() {
if err != nil { if err != nil {
return err return err
} }
return cicd.ServiceDeploy(log, req)
// Set the context with the required values to
// process the request.
v := webcontext.Values{
Now: time.Now(),
Env: req.Env,
}
ctx := context.WithValue(context.Background(), webcontext.KeyValues, &v)
return cicd.ServiceDeploy(log, ctx, req)
}, },
}, },
{ {
@ -165,7 +177,16 @@ func main() {
if err != nil { if err != nil {
return err return err
} }
return cicd.Migrate(log, req)
// Set the context with the required values to
// process the request.
v := webcontext.Values{
Now: time.Now(),
Env: req.Env,
}
ctx := context.WithValue(context.Background(), webcontext.KeyValues, &v)
return cicd.Migrate(log, ctx, req)
}, },
}, },
} }