1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-15 00:15:15 +02:00

checkpoint

This commit is contained in:
Lee Brown
2019-07-10 16:24:10 -08:00
parent acd1bca501
commit 4125a19156
9 changed files with 759 additions and 287 deletions

View File

@ -28,10 +28,10 @@ func (c *Check) Health(ctx context.Context, w http.ResponseWriter, r *http.Reque
} }
// check redis // check redis
//err = c.Redis.Ping().Err() err = c.Redis.Ping().Err()
//if err != nil { if err != nil {
// return errors.Wrap(err, "Redis failed") return errors.Wrap(err, "Redis failed")
//} }
status := struct { status := struct {
Status string `json:"status"` Status string `json:"status"`
@ -41,3 +41,11 @@ func (c *Check) Health(ctx context.Context, w http.ResponseWriter, r *http.Reque
return web.RespondJson(ctx, w, status, http.StatusOK) return web.RespondJson(ctx, w, status, http.StatusOK)
} }
// Ping validates the service is ready to accept requests.
func (c *Check) Ping(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
status := "pong"
return web.RespondJson(ctx, w, status, http.StatusOK)
}

View File

@ -23,8 +23,10 @@ func API(shutdown chan os.Signal, log *log.Logger, masterDB *sqlx.DB, redis *red
// Register health check endpoint. This route is not authenticated. // Register health check endpoint. This route is not authenticated.
check := Check{ check := Check{
MasterDB: masterDB, MasterDB: masterDB,
Redis: redis,
} }
app.Handle("GET", "/v1/health", check.Health) app.Handle("GET", "/v1/health", check.Health)
app.Handle("GET", "/ping", check.Ping)
// Register user management and authentication endpoints. // Register user management and authentication endpoints.
u := User{ u := User{

View File

@ -1,6 +1,7 @@
module geeks-accelerator/oss/saas-starter-kit/example-project module geeks-accelerator/oss/saas-starter-kit/example-project
require ( require (
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/aws/aws-sdk-go v1.20.16 github.com/aws/aws-sdk-go v1.20.16
github.com/bobesa/go-domain-util v0.0.0-20180815122459-1d708c097a6a github.com/bobesa/go-domain-util v0.0.0-20180815122459-1d708c097a6a
@ -10,6 +11,7 @@ require (
github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.7.3-0.20180815000130-e05b657120a6 github.com/docker/docker v0.7.3-0.20180815000130-e05b657120a6
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
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/camelcase v1.0.0
@ -43,6 +45,7 @@ require (
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/philhofer/fwd v1.0.0 // indirect github.com/philhofer/fwd v1.0.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.0.0 // indirect
github.com/sergi/go-diff v1.0.0 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/sirupsen/logrus v1.4.2 // indirect github.com/sirupsen/logrus v1.4.2 // indirect
@ -55,6 +58,7 @@ require (
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-20190628185345-da137c7871d7 // indirect golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
golang.org/x/tools v0.0.0-20190708203411-c8855242db9c // indirect golang.org/x/tools v0.0.0-20190708203411-c8855242db9c // indirect
google.golang.org/appengine v1.6.1 // indirect google.golang.org/appengine v1.6.1 // indirect
google.golang.org/grpc v1.22.0 // indirect google.golang.org/grpc v1.22.0 // indirect

View File

@ -1,6 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -8,12 +10,16 @@ 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-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aws/aws-sdk-go v1.19.33 h1:qz9ZQtxCUuwBKdc5QiY6hKuISYGeRQyLVA2RryDEDaQ= github.com/aws/aws-sdk-go v1.19.33 h1:qz9ZQtxCUuwBKdc5QiY6hKuISYGeRQyLVA2RryDEDaQ=
github.com/aws/aws-sdk-go v1.19.33/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.19.33/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.20.15 h1:y9ts8MJhB7ReUidS6Rq+0KxdFeL01J+pmOlGq6YqpiQ= github.com/aws/aws-sdk-go v1.20.15 h1:y9ts8MJhB7ReUidS6Rq+0KxdFeL01J+pmOlGq6YqpiQ=
github.com/aws/aws-sdk-go v1.20.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.20.16 h1:Dq68fBH39XnSjjb2hX/iW6mui8JtXcVAuhRYGSRiisY= github.com/aws/aws-sdk-go v1.20.16 h1:Dq68fBH39XnSjjb2hX/iW6mui8JtXcVAuhRYGSRiisY=
github.com/aws/aws-sdk-go v1.20.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
@ -37,6 +43,7 @@ github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
@ -51,6 +58,8 @@ github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db h1:m
github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db/go.mod h1:dzpCjo4q7chhMVuHDzs/odROkieZ5Wjp70rNDuX83jU= github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db/go.mod h1:dzpCjo4q7chhMVuHDzs/odROkieZ5Wjp70rNDuX83jU=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
@ -74,6 +83,8 @@ github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -100,6 +111,8 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
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=
@ -107,6 +120,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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.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.2/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 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=
@ -128,6 +142,11 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
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/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=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -149,14 +168,27 @@ github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3/go.mod h1:VyrYX9gd7ir
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
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 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 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/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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -177,6 +209,7 @@ 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/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -188,6 +221,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmy
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@ -197,9 +231,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
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=
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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-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-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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -213,6 +251,7 @@ 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/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@ -233,6 +272,7 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0 h1:2LhklnAJsRSelbnBrrE5QuRleRDkmOh2JWxOtIX6yec= gopkg.in/DataDog/dd-trace-go.v1 v1.15.0 h1:2LhklnAJsRSelbnBrrE5QuRleRDkmOh2JWxOtIX6yec=
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/DataDog/dd-trace-go.v1 v1.15.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
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 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=

View File

@ -8,13 +8,13 @@
"Sid": "ServiceDeployPermissions", "Sid": "ServiceDeployPermissions",
"Effect": "Allow", "Effect": "Allow",
"Action": [ "Action": [
"cloudwatchlogs:DescribeLogGroups",
"cloudwatchlogs:CreateLogGroup",
"ec2:DescribeSubnets", "ec2:DescribeSubnets",
"ec2:DescribeSubnets", "ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups", "ec2:DescribeSecurityGroups",
"ec2:CreateSecurityGroup", "ec2:CreateSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress", "ec2:AuthorizeSecurityGroupIngress",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeVpcs",
"elasticache:DescribeCacheClusters", "elasticache:DescribeCacheClusters",
"elasticache:CreateCacheCluster", "elasticache:CreateCacheCluster",
"elasticache:DescribeCacheParameterGroups", "elasticache:DescribeCacheParameterGroups",
@ -29,6 +29,7 @@
"elasticloadbalancing:ModifyTargetGroupAttributes", "elasticloadbalancing:ModifyTargetGroupAttributes",
"ecs:CreateCluster", "ecs:CreateCluster",
"ecs:CreateService", "ecs:CreateService",
"ecs:DeleteService",
"ecs:DescribeClusters", "ecs:DescribeClusters",
"ecs:DescribeServices", "ecs:DescribeServices",
"ecs:UpdateService", "ecs:UpdateService",
@ -64,6 +65,8 @@
"iam:ListPolicies", "iam:ListPolicies",
"iam:GetPolicyVersion", "iam:GetPolicyVersion",
"iam:CreatePolicyVersion", "iam:CreatePolicyVersion",
"logs:DescribeLogGroups",
"logs:CreateLogGroup",
"logs:DescribeLogStreams", "logs:DescribeLogStreams",
"logs:CreateExportTask", "logs:CreateExportTask",
"logs:DescribeExportTasks", "logs:DescribeExportTasks",
@ -85,11 +88,18 @@
"s3:PutBucketPolicy", "s3:PutBucketPolicy",
"s3:PutBucketPublicAccessBlock", "s3:PutBucketPublicAccessBlock",
"route53:CreateHostedZone", "route53:CreateHostedZone",
"route53:ChangeResourceRecordSets",
"route53:ListHostedZones", "route53:ListHostedZones",
"secretsmanager:CreateSecret", "secretsmanager:CreateSecret",
"secretsmanager:ListSecrets", "secretsmanager:ListSecrets",
"secretsmanager:GetSecretValue", "secretsmanager:GetSecretValue",
"secretsmanager:UpdateSecret" "secretsmanager:UpdateSecret",
"servicediscovery:ListNamespaces",
"servicediscovery:CreatePrivateDnsNamespace",
"servicediscovery:GetOperation",
"servicediscovery:ListServices",
"servicediscovery:CreateService",
"servicediscovery:GetService"
], ],
"Resource": "*" "Resource": "*"
}, },

View File

@ -2,7 +2,9 @@ package devops
import ( import (
"github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elasticache"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/servicediscovery"
"strings" "strings"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -54,6 +56,11 @@ type serviceDeployRequest struct {
EcsExecutionRoleName string `validate:"required"` EcsExecutionRoleName string `validate:"required"`
EcsTaskRoleName string `validate:"required"` EcsTaskRoleName string `validate:"required"`
EcsTaskPolicyName string `validate:"required"` EcsTaskPolicyName string `validate:"required"`
EcsTaskPolicy *iam.CreatePolicyInput
EcsTaskPolicyDocument IamPolicyDocument
EcsServiceDesiredCount int64 `validate:"required"` EcsServiceDesiredCount int64 `validate:"required"`
Ec2SecurityGroupName string `validate:"required"` Ec2SecurityGroupName string `validate:"required"`
CloudWatchLogGroupName string `validate:"required"` CloudWatchLogGroupName string `validate:"required"`
@ -80,6 +87,12 @@ type serviceDeployRequest struct {
NoPush bool `validate:"omitempty"` NoPush bool `validate:"omitempty"`
RecreateService bool `validate:"omitempty"` RecreateService bool `validate:"omitempty"`
SDNamepsace *servicediscovery.CreatePrivateDnsNamespaceInput
SDService *servicediscovery.CreateServiceInput
CacheCluster *elasticache.CreateCacheClusterInput CacheCluster *elasticache.CreateCacheClusterInput
CacheClusterParameter []*elasticache.ParameterNameValue CacheClusterParameter []*elasticache.ParameterNameValue

View File

@ -18,6 +18,8 @@ import (
"strings" "strings"
"time" "time"
"geeks-accelerator/oss/saas-starter-kit/example-project/tools/truss/internal/retry"
"github.com/aws/aws-sdk-go/service/servicediscovery"
"github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/rds"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elasticache"
@ -43,71 +45,6 @@ import (
"gopkg.in/go-playground/validator.v9" "gopkg.in/go-playground/validator.v9"
) )
// baseServicePolicyDocument defines the default permissions required to access AWS services for all deployed services.
var baseServicePolicyDocument = IamPolicyDocument{
Version: "2012-10-17",
Statement: []IamStatementEntry{
IamStatementEntry{
Sid: "DefaultServiceAccess",
Effect: "Allow",
Action: []string{
"s3:HeadBucket",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ecs:ListTasks",
"ecs:DescribeTasks",
"ec2:DescribeNetworkInterfaces",
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"route53:ChangeResourceRecordSets",
"ecs:UpdateService",
"ses:SendEmail",
},
Resource: "*",
},
IamStatementEntry{
Sid: "ServiceInvokeLambda",
Effect: "Allow",
Action: []string{
"iam:GetRole",
"lambda:InvokeFunction",
"lambda:ListVersionsByFunction",
"lambda:GetFunction",
"lambda:InvokeAsync",
"lambda:GetFunctionConfiguration",
"iam:PassRole",
"lambda:GetAlias",
"lambda:GetPolicy",
},
Resource: []string{
"arn:aws:iam:::role/*",
"arn:aws:lambda:::function:*",
},
},
IamStatementEntry{
Sid: "datadoglambda",
Effect: "Allow",
Action: []string{
"cloudwatch:Get*",
"cloudwatch:List*",
"ec2:Describe*",
"support:*",
"tag:GetResources",
"tag:GetTagKeys",
"tag:GetTagValues",
},
Resource: "*",
},
},
}
/*
// requiredCmdsBuild proves a list of required executables for completing build.
var requiredCmdsDeploy = [][]string{
[]string{"docker", "version", "-f", "{{.Client.Version}}"},
}
*/
// NewServiceDeployRequest generated a new request for executing deploy for a given set of flags. // NewServiceDeployRequest generated a new request for executing deploy for a given set of flags.
func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*serviceDeployRequest, error) { func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*serviceDeployRequest, error) {
@ -254,8 +191,73 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic
// Set default AWS ECS Task Policy Name. // Set default AWS ECS Task Policy Name.
req.EcsTaskPolicyName = fmt.Sprintf("%s%sServices", req.ProjectNameCamel(), strcase.ToCamel(req.Env)) req.EcsTaskPolicyName = fmt.Sprintf("%s%sServices", req.ProjectNameCamel(), strcase.ToCamel(req.Env))
req.EcsTaskPolicy = &iam.CreatePolicyInput{
PolicyName: aws.String(req.EcsTaskPolicyName),
Description: aws.String(fmt.Sprintf("Defines access for %s services. ", req.ProjectName)),
}
log.Printf("\t\t\tSet ECS Task Policy Name to '%s'.", req.EcsTaskPolicyName) log.Printf("\t\t\tSet ECS Task Policy Name to '%s'.", req.EcsTaskPolicyName)
// EcsTaskPolicyDocument defines the default document policy used to create the AWS ECS Task Policy. If the
// policy already exists, the permissions will be used to add new required actions, but not for removal.
// The policy document grants the permissions required for deployed services to access AWS services.
req.EcsTaskPolicyDocument = IamPolicyDocument{
Version: "2012-10-17",
Statement: []IamStatementEntry{
IamStatementEntry{
Sid: "DefaultServiceAccess",
Effect: "Allow",
Action: []string{
"s3:HeadBucket",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ecs:ListTasks",
"ecs:DescribeTasks",
"ec2:DescribeNetworkInterfaces",
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"route53:ChangeResourceRecordSets",
"ecs:UpdateService",
"ses:SendEmail",
},
Resource: "*",
},
IamStatementEntry{
Sid: "ServiceInvokeLambda",
Effect: "Allow",
Action: []string{
"iam:GetRole",
"lambda:InvokeFunction",
"lambda:ListVersionsByFunction",
"lambda:GetFunction",
"lambda:InvokeAsync",
"lambda:GetFunctionConfiguration",
"iam:PassRole",
"lambda:GetAlias",
"lambda:GetPolicy",
},
Resource: []string{
"arn:aws:iam:::role/*",
"arn:aws:lambda:::function:*",
},
},
IamStatementEntry{
Sid: "datadoglambda",
Effect: "Allow",
Action: []string{
"cloudwatch:Get*",
"cloudwatch:List*",
"ec2:Describe*",
"support:*",
"tag:GetResources",
"tag:GetTagKeys",
"tag:GetTagValues",
},
Resource: "*",
},
},
}
// Set default Cloudwatch Log Group Name. // Set default Cloudwatch Log Group Name.
req.CloudWatchLogGroupName = fmt.Sprintf("logs/env_%s/aws/ecs/cluster_%s/service_%s", req.Env, req.EcsClusterName, req.ServiceName) req.CloudWatchLogGroupName = fmt.Sprintf("logs/env_%s/aws/ecs/cluster_%s/service_%s", req.Env, req.EcsClusterName, req.ServiceName)
log.Printf("\t\t\tSet CloudWatch Log Group Name to '%s'.", req.CloudWatchLogGroupName) log.Printf("\t\t\tSet CloudWatch Log Group Name to '%s'.", req.CloudWatchLogGroupName)
@ -264,6 +266,7 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic
req.Ec2SecurityGroupName = req.EcsClusterName req.Ec2SecurityGroupName = req.EcsClusterName
log.Printf("\t\t\tSet ECS Security Group Name to '%s'.", req.Ec2SecurityGroupName) log.Printf("\t\t\tSet ECS Security Group Name to '%s'.", req.Ec2SecurityGroupName)
// Set default ELB Load Balancer Name when ELB is enabled. // Set default ELB Load Balancer Name when ELB is enabled.
if req.EnableEcsElb { if req.EnableEcsElb {
if !strings.Contains(req.EcsClusterName, req.Env) && !strings.Contains(req.ServiceName, req.Env) { if !strings.Contains(req.EcsClusterName, req.Env) && !strings.Contains(req.ServiceName, req.Env) {
@ -295,6 +298,64 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic
// S3 temp prefix, a life cycle policy will be applied to this. // S3 temp prefix, a life cycle policy will be applied to this.
req.S3BucketTempPrefix = "tmp/" req.S3BucketTempPrefix = "tmp/"
// Service Discovery Namespace settings.
req.SDNamepsace = &servicediscovery.CreatePrivateDnsNamespaceInput{
Name: aws.String(req.EcsClusterName),
Description: aws.String(fmt.Sprintf("Private DNS namespace used for services running on the ECS Cluster %s", req.EcsClusterName)),
// A unique string that identifies the request and that allows failed CreatePrivateDnsNamespace
// requests to be retried without the risk of executing the operation twice.
// CreatorRequestId can be any unique string, for example, a date/time stamp.
CreatorRequestId: aws.String("truss-deploy"),
}
// Service Discovery Service settings.
req.SDService = &servicediscovery.CreateServiceInput{
Name: aws.String(req.EcsServiceName),
Description: aws.String(fmt.Sprintf("Service %s running on the ECS Cluster %s", req.EcsServiceName, req.EcsClusterName)),
// A complex type that contains information about the Amazon Route 53 records
// that you want AWS Cloud Map to create when you register an instance.
DnsConfig: &servicediscovery.DnsConfig{
DnsRecords: []*servicediscovery.DnsRecord{
{
// The amount of time, in seconds, that you want DNS resolvers to cache the
// settings for this record.
TTL: aws.Int64(300),
// The type of the resource, which indicates the type of value that Route 53
// returns in response to DNS queries.
Type: aws.String("A"),
},
},
},
// A complex type that contains information about an optional custom health
// check.
//
// If you specify a health check configuration, you can specify either HealthCheckCustomConfig
// or HealthCheckConfig but not both.
HealthCheckCustomConfig: &servicediscovery.HealthCheckCustomConfig{
// The number of 30-second intervals that you want Cloud Map to wait after receiving
// an UpdateInstanceCustomHealthStatus request before it changes the health
// status of a service instance. For example, suppose you specify a value of
// 2 for FailureTheshold, and then your application sends an UpdateInstanceCustomHealthStatus
// request. Cloud Map waits for approximately 60 seconds (2 x 30) before changing
// the status of the service instance based on that request.
//
// Sending a second or subsequent UpdateInstanceCustomHealthStatus request with
// the same value before FailureThreshold x 30 seconds has passed doesn't accelerate
// the change. Cloud Map still waits FailureThreshold x 30 seconds after the
// first request to make the change.
FailureThreshold: aws.Int64(3),
},
// A unique string that identifies the request and that allows failed CreatePrivateDnsNamespace
// requests to be retried without the risk of executing the operation twice.
// CreatorRequestId can be any unique string, for example, a date/time stamp.
CreatorRequestId: aws.String("truss-deploy"),
}
// Elastic Cache settings for a Redis cache cluster. Could defined different settings by env. // Elastic Cache settings for a Redis cache cluster. Could defined different settings by env.
req.CacheCluster = &elasticache.CreateCacheClusterInput{ req.CacheCluster = &elasticache.CreateCacheClusterInput{
AutoMinorVersionUpgrade: aws.Bool(true), AutoMinorVersionUpgrade: aws.Bool(true),
@ -342,6 +403,8 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic
}, },
} }
log.Printf("\t%s\tDefaults set.", tests.Success) log.Printf("\t%s\tDefaults set.", tests.Success)
} }
@ -359,26 +422,7 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic
// Run is the main entrypoint for deploying a service for a given target env. // Run is the main entrypoint for deploying a service for a given target env.
func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error { func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
/*
log.Println("Verify required commands are installed.")
for _, cmdVals := range requiredCmdsDeploy {
cmd := exec.Command(cmdVals[0], cmdVals[1:]...)
cmd.Env = os.Environ()
out, err := cmd.CombinedOutput()
if err != nil {
return errors.WithMessagef(err, "failed to execute %s - %s\n%s", strings.Join(cmdVals, " "), string(out))
}
log.Printf("\t%s\t%s - %s", tests.Success, cmdVals[0], string(out))
}
// Pull the current env variables to be passed in for command execution.
envVars := EnvVars(os.Environ())
*/
startTime := time.Now() startTime := time.Now()
// Load the AWS ECR repository. Try to find by name else create new one. // Load the AWS ECR repository. Try to find by name else create new one.
@ -526,10 +570,12 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
return errors.Wrap(err, "failed to create docker build context") return errors.Wrap(err, "failed to create docker build context")
} }
_, err = docker.ImageBuild(context.Background(), buildCtx, buildOpts) res, err := docker.ImageBuild(context.Background(), buildCtx, buildOpts)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to build docker image") return errors.Wrap(err, "failed to build docker image")
} }
io.Copy(os.Stdout, res.Body)
res.Body.Close()
// Push the newly built docker container to the registry. // Push the newly built docker container to the registry.
if req.NoPush == false { if req.NoPush == false {
@ -855,39 +901,22 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
svc := ec2.New(req.awsSession()) svc := ec2.New(req.awsSession())
log.Println("\t\tFind all subnets are that default for each available AZ.")
var subnets []*ec2.Subnet var subnets []*ec2.Subnet
if true { // len(req.ec2SubnetIds) == 0 { err := svc.DescribeSubnetsPages(&ec2.DescribeSubnetsInput{}, func(res *ec2.DescribeSubnetsOutput, lastPage bool) bool {
log.Println("\t\tFind all subnets are that default for each available AZ.") for _, s := range res.Subnets {
if *s.DefaultForAz {
err := svc.DescribeSubnetsPages(&ec2.DescribeSubnetsInput{}, func(res *ec2.DescribeSubnetsOutput, lastPage bool) bool {
for _, s := range res.Subnets {
if *s.DefaultForAz {
subnets = append(subnets, s)
}
}
return !lastPage
})
if err != nil {
return errors.Wrap(err, "failed to find default subnets")
}
/*} else {
log.Println("\t\tFind all subnets for the IDs provided.")
err := svc.DescribeSubnetsPages(&ec2.DescribeSubnetsInput{
SubnetIds: aws.StringSlice(flags.Ec2SubnetIds),
}, func(res *ec2.DescribeSubnetsOutput, lastPage bool) bool {
for _, s := range res.Subnets {
subnets = append(subnets, s) subnets = append(subnets, s)
} }
return !lastPage }
}) return !lastPage
if err != nil { })
return errors.Wrapf(err, "failed to find subnets: %s", strings.Join(flags.Ec2SubnetIds, ", ")) if err != nil {
} else if len(flags.Ec2SubnetIds) != len(subnets) { return errors.Wrap(err, "failed to find default subnets")
return errors.Errorf("failed to find all subnets, expected %d, got %d", len(flags.Ec2SubnetIds) != len(subnets))
}*/
} }
if len(subnets) == 0 { if len(subnets) == 0 {
return errors.New("failed to find any subnets, expected at least 1") return errors.New("failed to find any subnets, expected at least 1")
} }
@ -1330,6 +1359,310 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
log.Printf("\t%s\tUsing Cache Cluster '%s'.\n", tests.Success, *cacheCluster.CacheClusterId) log.Printf("\t%s\tUsing Cache Cluster '%s'.\n", tests.Success, *cacheCluster.CacheClusterId)
} }
// Route 53 zone lookup when hostname is set. Supports both top level domains or sub domains.
var zoneArecNames = map[string][]string{}
if req.ServiceDomainName != "" {
log.Println("Route 53 - Get or create hosted zones.")
svc := route53.New(req.awsSession())
log.Println("\tList all hosted zones.")
var zones []*route53.HostedZone
err := svc.ListHostedZonesPages(&route53.ListHostedZonesInput{},
func(res *route53.ListHostedZonesOutput, lastPage bool) bool {
for _, z := range res.HostedZones {
zones = append(zones, z)
}
return !lastPage
})
if err != nil {
return errors.Wrap(err, "failed list route 53 hosted zones")
}
// Generate a slice with the primary domain name and include all the alternative domain names.
lookupDomains := []string{req.ServiceDomainName}
for _, dn := range req.ServiceDomainNameAliases {
lookupDomains = append(lookupDomains, dn)
}
// Loop through all the defined domain names and find the associated zone even when they are a sub domain.
for _, dn := range lookupDomains {
log.Printf("\t\tFind zone for domain '%s'", dn)
// Get the top level domain from url.
zoneName := domainutil.Domain(dn)
var subdomain string
if zoneName == "" {
// Handle domain names that have weird TDL: ie .tech
zoneName = dn
log.Printf("\t\t\tNon-standard Level Domain: '%s'", zoneName)
} else {
log.Printf("\t\t\tTop Level Domain: '%s'", zoneName)
// Check if url has subdomain.
if domainutil.HasSubdomain(dn) {
subdomain = domainutil.Subdomain(dn)
log.Printf("\t\t\tsubdomain: '%s'", subdomain)
}
}
// Start at the top level domain and try to find a hosted zone. Search until a match is found or there are
// no more domain levels to search for.
var zoneId string
for {
log.Printf("\t\t\tChecking zone '%s' for associated hosted zone.", zoneName)
// Loop over each one of hosted zones and try to find match.
for _, z := range zones {
zn := strings.TrimRight(*z.Name, ".")
log.Printf("\t\t\t\tChecking if '%s' matches '%s'", zn, zoneName)
if zn == zoneName {
zoneId = *z.Id
break
}
}
if zoneId != "" || zoneName == dn {
// Found a matching zone or have search all possibilities!
break
}
// If we have not found a hosted zone, append the next level from the domain to the zone.
pts := strings.Split(subdomain, ".")
subs := []string{}
for idx, sn := range pts {
if idx == len(pts)-1 {
zoneName = sn + "." + zoneName
} else {
subs = append(subs, sn)
}
}
subdomain = strings.Join(subs, ".")
}
var aName string
if zoneId == "" {
// Get the top level domain from url again.
zoneName := domainutil.Domain(dn)
if zoneName == "" {
// Handle domain names that have weird TDL: ie .tech
zoneName = dn
}
log.Printf("\t\t\tNo hosted zone found for '%s', create '%s'.", dn, zoneName)
createRes, err := svc.CreateHostedZone(&route53.CreateHostedZoneInput{
Name: aws.String(zoneName),
HostedZoneConfig: &route53.HostedZoneConfig{
Comment: aws.String(fmt.Sprintf("Public hosted zone created by saas-starter-kit.")),
},
// A unique string that identifies the request and that allows failed CreateHostedZone
// requests to be retried without the risk of executing the operation twice.
// You must use a unique CallerReference string every time you submit a CreateHostedZone
// request. CallerReference can be any unique string, for example, a date/time
// stamp.
//
// CallerReference is a required field
CallerReference: aws.String("truss-deploy"),
})
if err != nil {
return errors.Wrapf(err, "failed to create route 53 hosted zone '%s' for domain '%s'", zoneName, dn)
}
zoneId = *createRes.HostedZone.Id
log.Printf("\t\t\tCreated hosted zone '%s'", zoneId)
// The fully qualified A record name.
aName = dn
} else {
log.Printf("\t\t\tFound hosted zone '%s'", zoneId)
// The fully qualified A record name.
if subdomain != "" {
aName = subdomain + "." + zoneName
} else {
aName = zoneName
}
}
// Add the A record to be maintained for the zone.
if _, ok := zoneArecNames[zoneId]; !ok {
zoneArecNames[zoneId] = []string{}
}
zoneArecNames[zoneId] = append(zoneArecNames[zoneId], aName)
log.Printf("\t%s\tZone '%s' found with A record name '%s'.\n", tests.Success, zoneId, aName)
}
}
// Setup service discovery.
var sdService *servicediscovery.Service
{
log.Println("SD - Get or Create Namespace")
svc := servicediscovery.New(req.awsSession())
log.Println("\t\tList all the private namespaces and try to find an existing entry.")
listNamespaces := func() (*servicediscovery.NamespaceSummary, error) {
var found *servicediscovery.NamespaceSummary
err := svc.ListNamespacesPages(&servicediscovery.ListNamespacesInput{
Filters: []*servicediscovery.NamespaceFilter{
&servicediscovery.NamespaceFilter{
Name: aws.String("TYPE"),
Condition: aws.String("EQ"),
Values: aws.StringSlice([]string{"DNS_PRIVATE"}),
},
},
}, func(res *servicediscovery.ListNamespacesOutput, lastPage bool) bool {
for _, n := range res.Namespaces {
if *n.Name == *req.SDNamepsace.Name {
found = n
return false
}
}
return !lastPage
})
if err != nil {
return nil, errors.Wrap(err, "failed to list namespaces")
}
return found, nil
}
sdNamespace, err := listNamespaces()
if err != nil {
return err
}
if sdNamespace == nil {
// Link the namespace to the VPC.
req.SDNamepsace.Vpc = aws.String(vpcId)
log.Println("\t\tCreate private namespace.")
// If no namespace was found, create one.
createRes, err := svc.CreatePrivateDnsNamespace(req.SDNamepsace)
if err != nil {
return errors.Wrapf(err, "failed to create namespace '%s'", *req.SDNamepsace.Name)
}
operationId := createRes.OperationId
log.Println("\t\tWait for create operation to finish.")
retryFunc := func() (bool, error) {
opRes, err := svc.GetOperation(&servicediscovery.GetOperationInput{
OperationId: operationId,
})
if err != nil {
return true, err
}
log.Printf("\t\t\tStatus: %s.", *opRes.Operation.Status)
// The status of the operation. Values include the following:
// * SUBMITTED: This is the initial state immediately after you submit a
// request.
// * PENDING: AWS Cloud Map is performing the operation.
// * SUCCESS: The operation succeeded.
// * FAIL: The operation failed. For the failure reason, see ErrorMessage.
if *opRes.Operation.Status == "SUCCESS" {
return true, nil
} else if *opRes.Operation.Status == "FAIL" {
err = errors.Errorf("operation failed")
err = awserr.New(*opRes.Operation.ErrorCode, *opRes.Operation.ErrorMessage, err)
return true, err
}
return false, nil
}
err = retry.Retry(context.Background(), nil, retryFunc)
if err != nil {
return errors.Wrapf(err, "failed to get operation for namespace '%s'", *req.SDNamepsace.Name)
}
// Now that the create operation is complete, try to find the namespace again.
sdNamespace, err = listNamespaces()
if err != nil {
return err
}
log.Printf("\t\tCreated: %s.", *sdNamespace.Arn)
} else {
log.Printf("\t\tFound: %s.", *sdNamespace.Arn)
// The number of services that are associated with the namespace.
if sdNamespace.ServiceCount != nil {
log.Printf("\t\t\tServiceCount: %d.", *sdNamespace.ServiceCount)
}
}
log.Printf("\t%s\tUsing Service Discovery Namespace '%s'.\n", tests.Success, *sdNamespace.Id)
// Try to find an existing entry for the current service.
var existingService *servicediscovery.ServiceSummary
err = svc.ListServicesPages(&servicediscovery.ListServicesInput{
Filters: []*servicediscovery.ServiceFilter{
&servicediscovery.ServiceFilter{
Name: aws.String("NAMESPACE_ID"),
Condition: aws.String("EQ"),
Values: aws.StringSlice([]string{*sdNamespace.Id}),
},
},
}, func(res *servicediscovery.ListServicesOutput, lastPage bool) bool {
for _, n := range res.Services {
if *n.Name == req.EcsServiceName {
existingService = n
return false
}
}
return !lastPage
})
if err != nil {
return errors.Wrapf(err, "failed to list services for namespace '%s'", *sdNamespace.Id)
}
if existingService == nil {
// Link the service to the namespace.
req.SDService.NamespaceId = sdNamespace.Id
// If no namespace was found, create one.
createRes, err := svc.CreateService(req.SDService)
if err != nil {
return errors.Wrapf(err, "failed to create service '%s'", *req.SDService.Name)
}
sdService = createRes.Service
log.Printf("\t\tCreated: %s.", *sdService.Arn)
} else {
// If no namespace was found, create one.
getRes, err := svc.GetService(&servicediscovery.GetServiceInput{
Id: existingService.Id,
})
if err != nil {
return errors.Wrapf(err, "failed to get service '%s'", *req.SDService.Name)
}
sdService = getRes.Service
log.Printf("\t\tFound: %s.", *sdService.Arn)
// The number of instances that are currently associated with the service. Instances
// that were previously associated with the service but that have been deleted
// are not included in the count.
if sdService.InstanceCount != nil {
log.Printf("\t\t\tInstanceCount: %d.", *sdService.InstanceCount)
}
}
log.Printf("\t%s\tUsing Service Discovery Service '%s'.\n", tests.Success, *sdService.Id)
}
// Try to find AWS ECS Cluster by name or create new one. // Try to find AWS ECS Cluster by name or create new one.
var ecsCluster *ecs.Cluster var ecsCluster *ecs.Cluster
{ {
@ -1805,7 +2138,7 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
} }
var updateDoc bool var updateDoc bool
for _, baseStmt := range baseServicePolicyDocument.Statement { for _, baseStmt := range req.EcsTaskPolicyDocument.Statement {
var found bool var found bool
for curIdx, curStmt := range curDoc.Statement { for curIdx, curStmt := range curDoc.Statement {
if baseStmt.Sid != curStmt.Sid { if baseStmt.Sid != curStmt.Sid {
@ -1857,17 +2190,14 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
} }
} }
} else { } else {
dat, err := json.Marshal(baseServicePolicyDocument) dat, err := json.Marshal(req.EcsTaskPolicyDocument)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to json encode policy document") return errors.Wrap(err, "failed to json encode policy document")
} }
req.EcsTaskPolicy.PolicyDocument = aws.String(string(dat))
// If no repository was found, create one. // If no repository was found, create one.
res, err := svc.CreatePolicy(&iam.CreatePolicyInput{ res, err := svc.CreatePolicy(req.EcsTaskPolicy)
PolicyName: aws.String(req.EcsTaskPolicyName),
Description: aws.String(fmt.Sprintf("Defines access for %s services. ", req.ProjectName)),
PolicyDocument: aws.String(string(dat)),
})
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to create task policy '%s'", req.EcsTaskPolicyName) return errors.Wrapf(err, "failed to create task policy '%s'", req.EcsTaskPolicyName)
} }
@ -2004,11 +2334,17 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
recreateService = true recreateService = true
forceDelete = true forceDelete = true
} else if req.EnableEcsElb && (ecsService.LoadBalancers == nil || len(ecsService.LoadBalancers) == 0) { } else if req.EnableEcsElb && (ecsService.LoadBalancers == nil || len(ecsService.LoadBalancers) == 0) {
// Service was created with no ELB and now ELB is enabled. // Service was created without ELB and now ELB is enabled.
recreateService = true recreateService = true
} else if !req.EnableEcsElb && (ecsService.LoadBalancers != nil && len(ecsService.LoadBalancers) > 0) { } else if !req.EnableEcsElb && (ecsService.LoadBalancers != nil && len(ecsService.LoadBalancers) > 0) {
// Service was created with ELB and now ELB is disabled. // Service was created with ELB and now ELB is disabled.
recreateService = true recreateService = true
} else if sdService != nil && sdService.Arn != nil && (ecsService.ServiceRegistries == nil || len(ecsService.ServiceRegistries) == 0) {
// Service was created without Service Discovery and now Service Discovery is enabled.
recreateService = true
} else if (sdService == nil || sdService.Arn == nil) && (ecsService.ServiceRegistries != nil && len(ecsService.ServiceRegistries) > 0) {
// Service was created with Service Discovery and now Service Discovery is disabled.
recreateService = true
} }
if recreateService { if recreateService {
@ -2016,7 +2352,30 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
svc := ecs.New(req.awsSession()) svc := ecs.New(req.awsSession())
_, err := svc.DeleteService(&ecs.DeleteServiceInput{ // The service cannot be stopped while it is scaled above 0
if ecsService.DesiredCount != nil && *ecsService.DesiredCount > 0 {
log.Println("\t\tScaling service down to zero.")
_, err := svc.UpdateService(&ecs.UpdateServiceInput{
Cluster: ecsService.ClusterArn,
Service: ecsService.ServiceArn,
DesiredCount: aws.Int64(int64(0)),
})
if err != nil {
return errors.Wrapf(err, "failed to update service '%s'", ecsService.ServiceName)
}
log.Println("\t\tWait for the service to scale down.")
err = svc.WaitUntilServicesStable(&ecs.DescribeServicesInput{
Cluster: ecsCluster.ClusterArn,
Services: aws.StringSlice([]string{*ecsService.ServiceArn}),
})
if err != nil {
return errors.Wrapf(err, "failed to wait for service '%s' to enter stable state", *ecsService.ServiceName)
}
}
log.Println("\t\tDelete Service.")
res, err := svc.DeleteService(&ecs.DeleteServiceInput{
Cluster: ecsService.ClusterArn, Cluster: ecsService.ClusterArn,
Service: ecsService.ServiceArn, Service: ecsService.ServiceArn,
@ -2026,8 +2385,9 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
Force: aws.Bool(forceDelete), Force: aws.Bool(forceDelete),
}) })
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to create security group '%s'", req.Ec2SecurityGroupName) return errors.Wrapf(err, "failed to delete service '%s'", ecsService.ServiceName)
} }
ecsService = res.Service
log.Printf("\t%s\tDelete Service.\n", tests.Success) log.Printf("\t%s\tDelete Service.\n", tests.Success)
} }
@ -2611,6 +2971,18 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
}, },
} }
// Add the Service Discovery registry to the ECS service.
if sdService != nil {
if serviceInput.ServiceRegistries == nil {
serviceInput.ServiceRegistries = []*ecs.ServiceRegistry{}
}
serviceInput.ServiceRegistries = append(serviceInput.ServiceRegistries, &ecs.ServiceRegistry{
RegistryArn: sdService.Arn,
})
}
createRes, err := svc.CreateService(serviceInput) createRes, err := svc.CreateService(serviceInput)
// If tags aren't enabled for the account, try the request again without them. // If tags aren't enabled for the account, try the request again without them.
@ -2867,133 +3239,6 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
log.Printf("\t%s\tService running.\n", tests.Success) log.Printf("\t%s\tService running.\n", tests.Success)
} }
// Route 53 zone lookup when hostname is set. Supports both top level domains or sub domains.
var zoneArecNames = map[string][]string{}
if req.ServiceDomainName != "" {
log.Println("Route 53 - Get or create hosted zones.")
svc := route53.New(req.awsSession())
log.Println("\tList all hosted zones.")
var zones []*route53.HostedZone
err := svc.ListHostedZonesPages(&route53.ListHostedZonesInput{},
func(res *route53.ListHostedZonesOutput, lastPage bool) bool {
for _, z := range res.HostedZones {
zones = append(zones, z)
}
return !lastPage
})
if err != nil {
return errors.Wrap(err, "failed list route 53 hosted zones")
}
// Generate a slice with the primary domain name and include all the alternative domain names.
lookupDomains := []string{req.ServiceDomainName}
for _, dn := range req.ServiceDomainNameAliases {
lookupDomains = append(lookupDomains, dn)
}
// Loop through all the defined domain names and find the associated zone even when they are a sub domain.
for _, dn := range lookupDomains {
log.Printf("\t\tFind zone for domain '%s'", dn)
// Get the top level domain from url.
zoneName := domainutil.Domain(dn)
log.Printf("\t\t\tTop Level Domain: '%s'", zoneName)
// Check if url has subdomain.
var subdomain string
if domainutil.HasSubdomain(dn) {
subdomain = domainutil.Subdomain(dn)
log.Printf("\t\t\tsubdomain: '%s'", subdomain)
}
// Start at the top level domain and try to find a hosted zone. Search until a match is found or there are
// no more domain levels to search for.
var zoneId string
for {
log.Printf("\t\t\tChecking zone '%s' for associated hosted zone.", zoneName)
// Loop over each one of hosted zones and try to find match.
for _, z := range zones {
log.Printf("\t\t\t\tChecking if %s matches %s", *z.Name, zoneName)
if strings.TrimRight(*z.Name, ".") == zoneName {
zoneId = *z.Id
break
}
}
if zoneId != "" || zoneName == dn {
// Found a matching zone or have search all possibilities!
break
}
// If we have not found a hosted zone, append the next level from the domain to the zone.
pts := strings.Split(subdomain, ".")
subs := []string{}
for idx, sn := range pts {
if idx == len(pts)-1 {
zoneName = sn + "." + zoneName
} else {
subs = append(subs, sn)
}
}
subdomain = strings.Join(subs, ".")
}
var aName string
if zoneId == "" {
// Get the top level domain from url again.
zoneName := domainutil.Domain(dn)
log.Printf("\t\t\tNo hosted zone found for '%s', create '%s'.", dn, zoneName)
createRes, err := svc.CreateHostedZone(&route53.CreateHostedZoneInput{
Name: aws.String(zoneName),
HostedZoneConfig: &route53.HostedZoneConfig{
Comment: aws.String(fmt.Sprintf("Public hosted zone created by saas-starter-kit.")),
},
// A unique string that identifies the request and that allows failed CreateHostedZone
// requests to be retried without the risk of executing the operation twice.
// You must use a unique CallerReference string every time you submit a CreateHostedZone
// request. CallerReference can be any unique string, for example, a date/time
// stamp.
//
// CallerReference is a required field
CallerReference: aws.String("truss-deploy"),
})
if err != nil {
return errors.Wrapf(err, "failed to create route 53 hosted zone '%s' for domain '%s'", zoneName, dn)
}
zoneId = *createRes.HostedZone.Id
log.Printf("\t\t\tCreated hosted zone '%s'", zoneId)
// The fully qualified A record name.
aName = dn
} else {
log.Printf("\t\t\tFound hosted zone '%s'", zoneId)
// The fully qualified A record name.
if subdomain != "" {
aName = subdomain + "." + zoneName
} else {
aName = zoneName
}
}
// Add the A record to be maintained for the zone.
if _, ok := zoneArecNames[zoneId]; !ok {
zoneArecNames[zoneId] = []string{}
}
zoneArecNames[zoneId] = append(zoneArecNames[zoneId], aName)
log.Printf("\t%s\tZone '%s' found with A record name '%s'.\n", tests.Success, zoneId, aName)
}
}
if req.EnableEcsElb { if req.EnableEcsElb {
// TODO: Need to connect ELB to route53 // TODO: Need to connect ELB to route53
} else { } else {
@ -3006,6 +3251,7 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
servceTaskRes, err := svc.ListTasks(&ecs.ListTasksInput{ servceTaskRes, err := svc.ListTasks(&ecs.ListTasksInput{
Cluster: aws.String(req.EcsClusterName), Cluster: aws.String(req.EcsClusterName),
ServiceName: aws.String(req.EcsServiceName), ServiceName: aws.String(req.EcsServiceName),
DesiredStatus: aws.String("RUNNING"),
}) })
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to list tasks for cluster '%s' service '%s'", req.EcsClusterName, req.EcsServiceName) return errors.Wrapf(err, "failed to list tasks for cluster '%s' service '%s'", req.EcsClusterName, req.EcsServiceName)
@ -3020,43 +3266,18 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
return errors.Wrapf(err, "failed to describe %d tasks for cluster '%s'", len(servceTaskRes.TaskArns), req.EcsClusterName) return errors.Wrapf(err, "failed to describe %d tasks for cluster '%s'", len(servceTaskRes.TaskArns), req.EcsClusterName)
} }
var failures []*ecs.Failure
var taskArns []string var taskArns []string
for _, t := range taskRes.Tasks { for _, t := range taskRes.Tasks {
if t.TaskDefinitionArn != taskDef.TaskDefinitionArn { if *t.TaskDefinitionArn != *taskDef.TaskDefinitionArn {
continue continue
} }
log.Printf("\t\t\t%s: %s\n", *t.TaskArn, *t.LastStatus)
taskArns = append(taskArns, *t.TaskArn) taskArns = append(taskArns, *t.TaskArn)
// Limit failures to only the current task definition.
for _, f := range taskRes.Failures {
if *f.Arn == *t.TaskArn {
failures = append(failures, f)
}
}
}
if len(failures) > 0 {
for _, t := range failures {
log.Printf("\t%s\tTask %s failed with %s.\n", tests.Failed, *t.Arn, *t.Reason)
}
} else {
log.Printf("\t%s\tTasks founds.\n", tests.Success)
}
log.Println("\t\tWaiting for tasks to enter running state.")
{
var err error
err = svc.WaitUntilTasksRunning(&ecs.DescribeTasksInput{
Cluster: ecsCluster.ClusterArn,
Tasks: aws.StringSlice(taskArns),
})
if err != nil {
return errors.Wrapf(err, "failed to wait for tasks to enter running state for cluster '%s'", req.EcsClusterName)
}
log.Printf("\t%s\tTasks running.\n", tests.Success)
} }
log.Printf("\t%s\tTasks founds.\n", tests.Success)
log.Println("\t\tDescribe tasks for running tasks.") log.Println("\t\tDescribe tasks for running tasks.")
{ {
@ -3073,6 +3294,8 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error {
continue continue
} }
// t.Containers[0].NetworkInterfaces[0].Ipv6Address
for _, a := range t.Attachments { for _, a := range t.Attachments {
if a.Details == nil { if a.Details == nil {
continue continue

View File

@ -0,0 +1,86 @@
// Package retry contains a simple retry mechanism defined by a slice of delay
// times. There are no maximum retries accounted for here. If retries should be
// limited, use a Timeout context to keep from retrying forever. This should
// probably be made into something more robust.
package retry
import (
"context"
"time"
)
// queryPollIntervals is a slice of the delays before re-checking the status on
// an executing query, backing off from a short delay at first. This sequence
// has been selected with Athena queries in mind, which may operate very
// quickly for things like schema manipulation, or which may run for an
// extended period of time, when running an actual data analysis query.
// Long-running queries will exhaust their rapid retries quickly, and fall back
// to checking every few seconds or longer.
var DefaultPollIntervals = []time.Duration{
time.Millisecond,
2 * time.Millisecond,
2 * time.Millisecond,
5 * time.Millisecond,
10 * time.Millisecond,
20 * time.Millisecond,
50 * time.Millisecond,
50 * time.Millisecond,
100 * time.Millisecond,
100 * time.Millisecond,
200 * time.Millisecond,
500 * time.Millisecond,
time.Second,
2 * time.Second,
5 * time.Second,
10 * time.Second,
20 * time.Second,
30 * time.Second,
time.Minute,
}
// delayer keeps track of the current delay between retries.
type delayer struct {
Delays []time.Duration
currentIndex int
}
// Delay returns the current delay duration, and advances the index to the next
// delay defined. If the index has reached the end of the delay slice, then it
// will continue to return the maximum delay defined.
func (d *delayer) Delay() time.Duration {
t := d.Delays[d.currentIndex]
if d.currentIndex < len(d.Delays)-1 {
d.currentIndex++
}
return t
}
// Retry uses a slice of time.Duration interval delays to retry a function
// until it either errors or indicates that it is ready to proceed. If f
// returns true, or an error, the retry loop is broken. Pass a closure as f if
// you need to record a value from the operation that you are performing inside
// f.
func Retry(ctx context.Context, retryIntervals []time.Duration, f func() (bool, error)) (err error) {
if retryIntervals == nil || len(retryIntervals) == 0 {
retryIntervals = DefaultPollIntervals
}
d := delayer{Delays: retryIntervals}
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
ok, err := f()
if err != nil {
return err
}
if ok {
return nil
}
time.Sleep(d.Delay())
}
}
return err
}

View File

@ -0,0 +1,86 @@
package retry
import (
"context"
"errors"
"testing"
"time"
)
var errExpectedFailure = errors.New("expected failure for test purposes")
func TestDelayer(t *testing.T) {
delays := []time.Duration{
time.Millisecond,
2 * time.Millisecond,
4 * time.Millisecond,
10 * time.Millisecond,
}
tt := []struct {
desc string
numRetries int
expDelay time.Duration
}{
{"first try", 0, time.Millisecond},
{"second try", 1, 2 * time.Millisecond},
{"len(delays) try", len(delays) - 1, delays[len(delays)-1]},
{"len(delays) + 1 try", len(delays), delays[len(delays)-1]},
{"len(delays) * 2 try", len(delays) * 2, delays[len(delays)-1]},
}
for _, tc := range tt {
t.Run(tc.desc, func(t *testing.T) {
var (
d = delayer{Delays: delays}
delay time.Duration
)
for i := tc.numRetries + 1; i > 0; i-- {
delay = d.Delay()
}
if delay != tc.expDelay {
t.Fatalf(
"expected delay of %s after %d retries, but got %s",
tc.expDelay, tc.numRetries, delay)
}
})
}
}
func TestRetry(t *testing.T) {
delays := []time.Duration{
time.Millisecond,
2 * time.Millisecond,
3 * time.Millisecond,
}
tt := []struct {
desc string
tries int
success bool
err error
}{
{"first try", 1, true, nil},
{"second try error", 2, false, errExpectedFailure},
{"third try success", 3, true, nil},
}
for _, tc := range tt {
t.Run(tc.desc, func(t *testing.T) {
tries := 0
retryFunc := func() (bool, error) {
tries++
if tries == tc.tries {
return tc.success, tc.err
}
t.Logf("try #%d unsuccessful: trying again up to %d times", tries, tc.tries)
return false, nil
}
err := Retry(context.Background(), delays, retryFunc)
if err != tc.err {
t.Fatalf("expected error %s, but got error %s", err, tc.err)
}
if tries != tc.tries {
t.Fatalf("expected %d tries, but tried %d times", tc.tries, tries)
}
})
}
}