From d0cfdd7102daebdba55c9e8abb565a70e75330e4 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Sat, 7 Nov 2020 23:03:11 +0100 Subject: [PATCH] fix: exclude dockerutils and testutils package --- go.mod | 10 +- go.sum | 32 +- pkg/repository/repository_test.go | 2 +- pkg/testutils/database/database.go | 285 --------------- pkg/testutils/database/database_test.go | 331 ----------------- pkg/testutils/dockerutils/builder.go | 232 ------------ pkg/testutils/dockerutils/client.go | 402 -------------------- pkg/testutils/dockerutils/client_test.go | 443 ----------------------- pkg/testutils/dockerutils/watcher.go | 164 --------- 9 files changed, 15 insertions(+), 1886 deletions(-) delete mode 100644 pkg/testutils/database/database.go delete mode 100644 pkg/testutils/database/database_test.go delete mode 100644 pkg/testutils/dockerutils/builder.go delete mode 100644 pkg/testutils/dockerutils/client.go delete mode 100644 pkg/testutils/dockerutils/client_test.go delete mode 100644 pkg/testutils/dockerutils/watcher.go diff --git a/go.mod b/go.mod index 2f4eff5..7911303 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.cryptic.systems/volker.raschek/flucky go 1.14 require ( + git.cryptic.systems/volker.raschek/dockerutils v0.1.0 git.cryptic.systems/volker.raschek/go-dht v0.1.2 git.cryptic.systems/volker.raschek/go-logger v0.1.0 github.com/Masterminds/semver v1.5.0 @@ -10,18 +11,9 @@ require ( github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v1.13.1 - github.com/docker/go-connections v0.4.0 - github.com/docker/go-units v0.4.0 // indirect - github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/lib/pq v1.8.0 github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect - gorm.io/driver/sqlite v1.1.3 // indirect - gorm.io/gorm v1.20.1 // indirect ) diff --git a/go.sum b/go.sum index 602fd7c..5928264 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.cryptic.systems/volker.raschek/dockerutils v0.1.0 h1:NjE5BVS0k9XoRj8qZOHNvpjb1+C8Zr4sK/g4Yjw5Of4= +git.cryptic.systems/volker.raschek/dockerutils v0.1.0/go.mod h1:PikYsmSANKHEKJSD2pIY9gPqMpiQh1ufCrU/s/v2I5Q= git.cryptic.systems/volker.raschek/go-dht v0.1.2 h1:kGmfpaVUETQhSELCIrKXMjKwuUhQkRUz/7VbLYiTRJA= git.cryptic.systems/volker.raschek/go-dht v0.1.2/go.mod h1:FUMwxa4cD+ATHPztXJntlO22I0DBTUPtXxfRF0JxXy8= git.cryptic.systems/volker.raschek/go-logger v0.1.0 h1:JHBDesKBZaXjc2AlqYms1T3dGIX0oNIOBWl4cnVFWIo= @@ -44,9 +46,6 @@ 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-bindata/go-bindata v1.0.0 h1:DZ34txDXWn1DyWa+vQf7V9ANc2ILTtrEjtlsdJRF26M= -github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= -github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= 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-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -67,10 +66,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= -github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -82,13 +77,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= -github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -103,6 +94,8 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -148,16 +141,18 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -167,13 +162,16 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -191,10 +189,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc= -gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= -gorm.io/gorm v1.20.1 h1:+hOwlHDqvqmBIMflemMVPLJH7tZYK4RxFDBHEfJTup0= -gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= periph.io/x/periph v3.6.4+incompatible h1:8FyXTbu9lcMVofz8mf+cj1pzTLN4V6EuPY2EF+DoJF4= periph.io/x/periph v3.6.4+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y= diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go index af529b0..ee3b89e 100644 --- a/pkg/repository/repository_test.go +++ b/pkg/repository/repository_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" + "git.cryptic.systems/volker.raschek/dockerutils" "git.cryptic.systems/volker.raschek/flucky/pkg/repository" - "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/dockerutils" "git.cryptic.systems/volker.raschek/flucky/pkg/types" "git.cryptic.systems/volker.raschek/go-logger" uuid "github.com/satori/go.uuid" diff --git a/pkg/testutils/database/database.go b/pkg/testutils/database/database.go deleted file mode 100644 index 33c6676..0000000 --- a/pkg/testutils/database/database.go +++ /dev/null @@ -1,285 +0,0 @@ -package database - -import ( - "context" - "database/sql" - "errors" - "fmt" - "net/url" - "strconv" - "strings" - "testing" - "time" - - "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/dockerutils" - "github.com/docker/docker/api/types" - uuid "github.com/satori/go.uuid" - "github.com/stretchr/testify/require" -) - -var ( - ErrInvalidAttr = errors.New("Invalid DatabaseOption attribute") -) - -type PostgresOptions struct { - ContainerEnv map[string]string - ContainerImage string - ContainerLabels map[string]string - ContainerName string - ContainerNetworks map[string][]string - ContainerNetworkLabels map[string]map[string]string - ContainerPort string - Driver string - DSN string - HostPort string -} - -// Validate the DatabaseOption struct, if all required atrributes are valid -func (dbOptions *PostgresOptions) Validate() error { - - // Required strings - for _, values := range [][]string{ - 0: {"ContainerImage", dbOptions.ContainerImage}, - 1: {"ContainerPort", dbOptions.ContainerPort}, - 2: {"Driver", dbOptions.Driver}, - 3: {"HostPort", dbOptions.HostPort}, - } { - if len(values[1]) <= 0 { - return fmt.Errorf("%w: Attribute %v is empty", ErrInvalidAttr, values[0]) - } - } - - // Require initialized maps - for key, value := range map[string]interface{}{ - "ContainerEnv": dbOptions.ContainerEnv, - "ContainerLabels": dbOptions.ContainerLabels, - "ContainerNetworks": dbOptions.ContainerNetworks, - "ContainerNetworkLabels": dbOptions.ContainerNetworkLabels, - } { - if value == nil { - return fmt.Errorf("%w: Attribut %v is not initialized", ErrInvalidAttr, key) - } - } - - // Required postgres environment variables - for _, key := range []string{ - "POSTGRES_PASSWORD", - "POSTGRES_USER", - "POSTGRES_DB", - } { - if _, present := dbOptions.ContainerEnv[key]; !present { - return fmt.Errorf("%w: Required env %v not defined", ErrInvalidAttr, key) - } - } - - // Supported drivers - found := false - for _, supportedDriver := range []string{ - "postgres", - } { - if supportedDriver == dbOptions.Driver { - found = true - break - } - } - - if !found { - return fmt.Errorf("%w: Driver %v not supported", ErrInvalidAttr, dbOptions.Driver) - } - - // Protect well-known ports - hostPort, err := strconv.ParseInt(dbOptions.HostPort, 10, 64) - if err != nil { - return fmt.Errorf("Failed to parse hostport %v: %w", dbOptions.HostPort, err) - } - - if hostPort > 0 && hostPort < 1024 { - return fmt.Errorf("%w: ContainerPort %v: Protect well-known ports between 1-1023", ErrInvalidAttr, dbOptions.HostPort) - } - - return nil -} - -// NewPostgresDatabase starts a new postgres database based on default values -func NewPostgresDatabase(t *testing.T) (*PostgresOptions, func()) { - return StartPostgres(t, NewPostgresOptions()) -} - -// NewPostgresOptions returns a new PostgresOptions structs with default values -func NewPostgresOptions() *PostgresOptions { - - return &PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_USER": "postgres", - "POSTGRES_DB": "postgres", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerLabels: make(map[string]string, 0), - ContainerName: uuid.NewV4().String(), - ContainerNetworks: make(map[string][]string, 0), - ContainerNetworkLabels: make(map[string]map[string]string, 0), - ContainerPort: "5432", - Driver: "postgres", - DSN: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable", - HostPort: "0", - } -} - -// StartPostgres starts a postgres container image based on the PostgresOption -// struct -func StartPostgres(t *testing.T, pgOptions *PostgresOptions) (*PostgresOptions, func()) { - - var ( - ctx = context.Background() - require = require.New(t) - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := dockerutils.New() - require.NoError(err) - - // Validate the PostgresOptions struct - require.NoError(pgOptions.Validate()) - - // Pre-Define DSN based on the pgOption attributes - pgOptions.DSN = fmt.Sprintf("postgres://%v:%v@localhost:%v/%v?sslmode=disable", - pgOptions.ContainerEnv["POSTGRES_USER"], - pgOptions.ContainerEnv["POSTGRES_PASSWORD"], - pgOptions.HostPort, - pgOptions.ContainerEnv["POSTGRES_DB"], - ) - - // Create missing networks - // The dockerutils package check only if the network exist and append the - // container with his aliasses as network endpoint. Additionally every network - // get his own network labels. If no network labels are defined, new labels - // will be generated. This is required to remove the networks by their labels. - networks, err := dockerClient.NetworkList(ctx, types.NetworkListOptions{}) - require.NoError(err) - - for networkName := range pgOptions.ContainerNetworks { - found := false - for _, network := range networks { - if network.Name == networkName { - found = true - break - } - } - if !found { - // Create network labels if there are no one defined - if _, present := pgOptions.ContainerNetworkLabels[networkName]; !present { - pgOptions.ContainerNetworkLabels[networkName] = make(map[string]string, 0) - } - - // Append cleanup labels to the network - for key, value := range cleanupLabels { - pgOptions.ContainerNetworkLabels[networkName][key] = value - } - - _, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Labels: pgOptions.ContainerNetworkLabels[networkName], - }) - require.NoError(err) - } - } - - // Append cleanup labels to the container - for key, value := range cleanupLabels { - pgOptions.ContainerLabels[key] = value - } - - // Build database container - databaseContainerBuilder := dockerClient.NewBuilder(pgOptions.ContainerImage). - Env(pgOptions.ContainerEnv). - Labels(pgOptions.ContainerLabels). - Port(fmt.Sprintf("%s:%s", pgOptions.HostPort, pgOptions.ContainerPort)). - Pull(). - WithName(pgOptions.ContainerName) - - networkNames := make([]string, 0) - for networkName, aliasses := range pgOptions.ContainerNetworks { - if aliasses == nil { - aliasses = make([]string, 0) - } - if len(aliasses) <= 0 { - aliasses = append(aliasses, pgOptions.ContainerName) - } - databaseContainerBuilder.Network(networkName, aliasses...) - pgOptions.ContainerNetworks[networkName] = aliasses - networkNames = append(networkNames, networkName) - } - - dbContainerID, err := databaseContainerBuilder.Start(ctx) - require.NoError(err) - - // cleanupFunction to remove the database container with all defined networks. - // Skip network if the network has an additional endpoint - cleanupFunc := func() { - err := dockerClient.ContainerRemove(ctx, dbContainerID, types.ContainerRemoveOptions{Force: true}) - require.NoError(err) - - for networkName := range pgOptions.ContainerNetworks { - networks, err := dockerClient.NetworkListByLabels(ctx, pgOptions.ContainerNetworkLabels[networkName]) - require.NoError(err) - - for _, network := range networks { - if len(network.Containers) <= 0 { - err := dockerClient.NetworkRemove(ctx, network.ID) - require.NoError(err) - } - } - } - } - - // Search for allocated port if public port is defined as random by 0 - if pgOptions.HostPort == "0" { - - containers, err := dockerClient.ContainerListByLabels(ctx, true, pgOptions.ContainerLabels) - require.NoError(err) - - var pubPort string - for _, port := range containers[0].Ports { - if port.PrivatePort == 5432 { - pubPort = strconv.Itoa(int(port.PublicPort)) - break - } - } - require.Greater(len(pubPort), 0, "Failed to detect allocated random port of the postgres container") - - connectionURL, err := url.Parse(pgOptions.DSN) - require.NoError(err) - - parts := strings.Split(connectionURL.Host, ":") - connectionURL.Host = fmt.Sprintf("%v:%v", parts[0], pubPort) - pgOptions.DSN = connectionURL.String() - pgOptions.HostPort = pubPort - } - - var db *sql.DB - for i := 0; i < 10; i++ { - db, err = sql.Open(pgOptions.Driver, pgOptions.DSN) - if err != nil { - t.Log("Database not ready - wait 10 seconds") - time.Sleep(10 * time.Second) - continue - } - err = db.Ping() - if err != nil { - t.Log("Database not ready - wait 10 seconds") - db.Close() - time.Sleep(10 * time.Second) - continue - } - break - } - require.NoError(err) - - err = db.Close() - require.NoError(err) - - return pgOptions, cleanupFunc -} diff --git a/pkg/testutils/database/database_test.go b/pkg/testutils/database/database_test.go deleted file mode 100644 index 57ef5f7..0000000 --- a/pkg/testutils/database/database_test.go +++ /dev/null @@ -1,331 +0,0 @@ -package database_test - -import ( - "context" - "database/sql" - "errors" - "fmt" - "testing" - - "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/database" - "git.cryptic.systems/volker.raschek/flucky/pkg/testutils/dockerutils" - uuid "github.com/satori/go.uuid" - "github.com/stretchr/testify/require" - - _ "github.com/lib/pq" -) - -func requireCountNetworks(ctx context.Context, dockerClient *dockerutils.Client, dbOptions *database.PostgresOptions, equal int, t *testing.T) { - require := require.New(t) - containers, err := dockerClient.ContainerListByLabels(ctx, true, dbOptions.ContainerLabels) - require.NoError(err) - require.Equal(equal, len(containers[0].NetworkSettings.Networks)) -} - -func requireContainerHasNetworksAndAliasses(ctx context.Context, dockerClient *dockerutils.Client, dbOptions *database.PostgresOptions, t *testing.T) { - require := require.New(t) - - containers, err := dockerClient.ContainerListByNames(ctx, true, dbOptions.ContainerName) - require.NoError(err) - require.Contains(containers[0].Names, fmt.Sprintf("/%v", dbOptions.ContainerName)) - - for networkName, _ := range dbOptions.ContainerNetworks { - networks, err := dockerClient.NetworkListByNames(ctx, networkName) - require.NoError(err) - require.Equal(networkName, networks[0].Name) - require.Equal(dbOptions.ContainerName, networks[0].Containers[containers[0].ID].Name) - } -} - -func requireTestCleanup(ctx context.Context, dockerClient *dockerutils.Client, cleanupLabels map[string]string, t *testing.T) { - require := require.New(t) - containers, err := dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) - require.NoError(err) - require.NotNil(containers) - require.Len(containers, 0) - - networks, err := dockerClient.NetworkListByLabels(ctx, cleanupLabels) - require.NoError(err) - require.NotNil(networks) - require.Len(networks, 0) -} - -func TestPostgresDatabase(t *testing.T) { - require := require.New(t) - dbOptions, cleanup := database.NewPostgresDatabase(t) - t.Cleanup(func() { cleanup() }) - - require.NotEqual(0, dbOptions.HostPort) - - dockerClient, err := dockerutils.New() - require.NoError(err) - - dbContainers, err := dockerClient.ContainerListByLabels(context.Background(), true, dbOptions.ContainerLabels) - require.NoError(err) - require.NotNil(dbContainers) - require.Len(dbContainers, 1) - - dbo, err := sql.Open(dbOptions.Driver, dbOptions.DSN) - require.NoError(err) - require.NoError(dbo.Ping()) - - query := "SELECT 1" - row := dbo.QueryRow(query) - - var i int - err = row.Scan(&i) - require.NoError(err) - require.Equal(1, i) -} - -func TestPostgresNetwork(t *testing.T) { - var ( - ctx = context.Background() - require = require.New(t) - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := dockerutils.New() - require.NoError(err) - t.Cleanup(func() { - dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) - dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) - dockerClient.Close() - }) - - // TestDefaultNetwork - dbOptionsPointer, cleanup := database.NewPostgresDatabase(t) - t.Cleanup(cleanup) - - require.NotNil(dbOptionsPointer.ContainerNetworks) - require.Len(dbOptionsPointer.ContainerNetworks, 0) - requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptionsPointer, t) - - // TestRandomNetwork - dbOptions := database.NewPostgresOptions() - dbOptions.HostPort = "0" - dbOptions.ContainerName = "Volker" - dbOptions.ContainerLabels = cleanupLabels - dbOptions.ContainerNetworks[uuid.NewV4().String()] = []string{ - uuid.NewV4().String(), - uuid.NewV4().String(), - } - _, cleanup = database.StartPostgres(t, dbOptions) - t.Cleanup(cleanup) - - requireCountNetworks(ctx, dockerClient, dbOptions, 1, t) - requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptions, t) - - // TestRandomMultipleNetworks - containerNetworkNameA := uuid.NewV4().String() - containerNetworkNameB := uuid.NewV4().String() - containerNetworks := map[string][]string{ - containerNetworkNameA: { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - containerNetworkNameB: { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - } - - containerNetworkLabels := map[string]map[string]string{ - containerNetworkNameA: cleanupLabels, - containerNetworkNameB: cleanupLabels, - } - - dbOptions = database.NewPostgresOptions() - dbOptions.HostPort = "0" - dbOptions.ContainerName = "Raschek" - dbOptions.ContainerNetworks = containerNetworks - dbOptions.ContainerNetworkLabels = containerNetworkLabels - dbOptions.ContainerLabels = cleanupLabels - - _, cleanup = database.StartPostgres(t, dbOptions) - t.Cleanup(cleanup) - - requireCountNetworks(ctx, dockerClient, dbOptions, 2, t) - requireContainerHasNetworksAndAliasses(ctx, dockerClient, dbOptions, t) - - // TestCleanup - // Test if a network with an additional endpoint will not be removed - containerNetwork := uuid.NewV4().String() - containerNetworkA := map[string][]string{ - containerNetwork: { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - } - containerNetworkB := map[string][]string{ - containerNetwork: { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - } - containerNetworkLabels = map[string]map[string]string{ - containerNetwork: cleanupLabels, - } - - dbOptions = database.NewPostgresOptions() - dbOptions.HostPort = "0" - dbOptions.ContainerName = "is" - dbOptions.ContainerLabels = cleanupLabels - dbOptions.ContainerNetworks = containerNetworkA - dbOptions.ContainerNetworkLabels = containerNetworkLabels - _, cleanup = database.StartPostgres(t, dbOptions) - - dbOptions = database.NewPostgresOptions() - dbOptions.HostPort = "0" - dbOptions.ContainerName = "the_best" - dbOptions.ContainerLabels = cleanupLabels - dbOptions.ContainerNetworks = containerNetworkB - dbOptions.ContainerNetworkLabels = containerNetworkLabels - _, cleanupSecond := database.StartPostgres(t, dbOptions) - - cleanup() - - networkResources, err := dockerClient.NetworkListByNames(ctx, containerNetwork) - require.NoError(err) - require.Len(networkResources[0].Containers, 1) - - cleanupSecond() - requireTestCleanup(ctx, dockerClient, cleanupLabels, t) -} - -func TestPostgresOptionsValidate(t *testing.T) { - - require := require.New(t) - - // ContainerImage is empty - dbOptions := database.PostgresOptions{} - err := dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Attribute ContainerImage is empty") - - // ContainerPort is empty - dbOptions = database.PostgresOptions{ - ContainerImage: "docker.io/library/postgres:13-alpine", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Attribute ContainerPort is empty") - - // Driver is empty - dbOptions = database.PostgresOptions{ - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Attribute Driver is empty") - - // HostPort is empty - dbOptions = database.PostgresOptions{ - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "sdfsdfsdf", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Attribute HostPort is empty") - - // ContainerEnv: POSTGRES_PASSWORD - dbOptions = database.PostgresOptions{ - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "sdfsdfsdf", - HostPort: "asdasd", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_PASSWORD not defined") - - // ContainerEnv: POSTGRES_USER - dbOptions = database.PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "HelloWorld", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "sdfsdfsdf", - HostPort: "asdasd", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_USER not defined") - - // ContainerEnv: POSTGRES_USER - dbOptions = database.PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_USER": "postgres", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "sdfsdfsdf", - HostPort: "asdasd", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Required env POSTGRES_DB not defined") - - // Driver not supported - dbOptions = database.PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_USER": "postgres", - "POSTGRES_DB": "postgres", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "sdfsdfsdf", - HostPort: "asdasd", - } - err = dbOptions.Validate() - require.Error(err) - require.True(errors.Is(err, database.ErrInvalidAttr)) - require.EqualError(err, "Invalid DatabaseOption attribute: Driver sdfsdfsdf not supported") - - // HostPort: Failed to parse - dbOptions = database.PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_USER": "postgres", - "POSTGRES_DB": "postgres", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "postgres", - HostPort: "asdasd", - } - err = dbOptions.Validate() - require.Error(err) - require.EqualError(err, "Failed to parse hostport asdasd: strconv.ParseInt: parsing \"asdasd\": invalid syntax") - - // HostPort: Well-Known port - dbOptions = database.PostgresOptions{ - ContainerEnv: map[string]string{ - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_USER": "postgres", - "POSTGRES_DB": "postgres", - }, - ContainerImage: "docker.io/library/postgres:13-alpine", - ContainerPort: "5432", - Driver: "postgres", - HostPort: "443", - } - err = dbOptions.Validate() - require.Error(err) - require.EqualError(err, "Invalid DatabaseOption attribute: ContainerPort 443: Protect well-known ports between 1-1023") -} diff --git a/pkg/testutils/dockerutils/builder.go b/pkg/testutils/dockerutils/builder.go deleted file mode 100644 index 5e2cc8a..0000000 --- a/pkg/testutils/dockerutils/builder.go +++ /dev/null @@ -1,232 +0,0 @@ -package dockerutils - -import ( - "context" - "fmt" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/go-connections/nat" -) - -type Builder struct { - client *Client - containerConfig *container.Config - containerName string - hostConfig *container.HostConfig - networkConfig *network.NetworkingConfig - networks map[string][]string - ports []string - pull bool - waitForHealthy bool -} - -// AddEnv to the container -func (builder *Builder) AddEnv(key string, value string) *Builder { - builder.containerConfig.Env = append(builder.containerConfig.Env, fmt.Sprintf("%v=%v", key, value)) - return builder -} - -// AddLabel to the container -func (builder *Builder) AddLabel(key string, value string) *Builder { - builder.containerConfig.Labels[key] = value - return builder -} - -// Env set environment variables to the container -func (builder *Builder) Env(env map[string]string) *Builder { - builder.containerConfig.Labels = make(map[string]string) - for key, value := range env { - builder.containerConfig.Env = append(builder.containerConfig.Env, fmt.Sprintf("%v=%v", key, value)) - } - return builder -} - -// Labels set labels to the container -func (builder *Builder) Labels(labels map[string]string) *Builder { - builder.containerConfig.Labels = labels - return builder -} - -// Memory defines a memory limit for the container -func (builder *Builder) Memory(limit int64) *Builder { - builder.hostConfig.Memory = limit - return builder -} - -// Mount a source volume or hostpath into the filesystem of the container -func (builder *Builder) Mount(source string, dest string) *Builder { - builder.hostConfig.Binds = append(builder.hostConfig.Binds, fmt.Sprintf("%v:%v", source, dest)) - return builder -} - -// Mounts a set of source volumes or hostpath into the filesystem of the -// container -func (builder *Builder) Mounts(mounts map[string]string) *Builder { - for source, dest := range mounts { - builder.Mount(source, dest) - } - return builder -} - -// Network add the container with aliasses to a specific network -func (builder *Builder) Network(networkName string, aliasses ...string) *Builder { - builder.networks[networkName] = aliasses - return builder -} - -// Port defines a port forwarding from the host machine to the container -// Examples: -// - 8080:8080 -// - 10.6.231.10:8080:8080 -// - 10.6.231.10:8080:8080/tcp -func (builder *Builder) Port(port string) *Builder { - builder.ports = append(builder.ports, port) - return builder -} - -// Ports defines a set port forwarding rules from the host machine to the -// container -// Examples: -// - 8080:8080 -// - 10.6.231.10:8080:8080 -// - 10.6.231.10:8080:8080/tcp -func (builder *Builder) Ports(ports []string) *Builder { - builder.ports = ports - return builder -} - -// Pull the image if absent -func (builder *Builder) Pull() *Builder { - builder.pull = true - return builder -} - -// Start the container -func (builder *Builder) Start(ctx context.Context) (string, error) { - - // Pull container image if absent - if builder.pull { - err := builder.client.PullQuiet(ctx, builder.containerConfig.Image) - if err != nil { - return "", err - } - } - - // Network: portbinding Host->Container - exposedPorts, portBindings, err := nat.ParsePortSpecs(builder.ports) - if err != nil { - return "", fmt.Errorf("unabel to parse ports: %v", err) - } - if len(portBindings) > 0 { - time.Sleep(1 * time.Second) - builder.containerConfig.ExposedPorts = exposedPorts - builder.hostConfig.PortBindings = portBindings - } - - // Network: Add container to container networks - // Add the container to the first defined container network, if any one is - // defined. If no one is defined, the docker API will add the container to - // their default bridge docker0. The other networks will be added to the - // container after the container start. - var ( - networkNames = make([]string, 0) - networks = make([]types.NetworkResource, 0) - ) - if len(builder.networks) > 0 { - for networkName := range builder.networks { - networkNames = append(networkNames, networkName) - } - var err error - networks, err = builder.client.NetworkListByNames(ctx, networkNames...) - if err != nil { - return "", err - } - - endpointSetting := &network.EndpointSettings{ - NetworkID: networks[0].ID, - Aliases: builder.networks[networkNames[0]], - } - - builder.networkConfig.EndpointsConfig[networkNames[0]] = endpointSetting - - networkNames = networkNames[1:] - networks = networks[1:] - } - - // Container: Create - resp, err := builder.client.ContainerCreate( - ctx, - builder.containerConfig, - builder.hostConfig, - builder.networkConfig, - builder.containerName, - ) - if err != nil { - return "", fmt.Errorf("Unable to create container: %v", err) - } - - err = builder.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) - if err != nil { - - shutdownErr := builder.client.ContainerStopByIDs(ctx, 1*time.Second, resp.ID) - if shutdownErr != nil { - return "", fmt.Errorf("Unable to start container: %v\nUnable to remove container %s: %v\nManual cleanup necessary", err, resp.ID, shutdownErr) - } - return "", fmt.Errorf("Unable to start container: %v", err) - } - - // Network: Add more container networks - for i, networkName := range networkNames { - endpointSetting := &network.EndpointSettings{ - NetworkID: networks[i].ID, - Aliases: builder.networks[networkName], - } - err := builder.client.NetworkConnect(ctx, networks[i].ID, resp.ID, endpointSetting) - if err != nil { - return "", fmt.Errorf("Unable to append container endpoint to network %v", networkName) - } - } - - if builder.waitForHealthy { - watcher := builder.client.GetWatcher() - errors := make(chan error, 1) - done := make(chan struct{}) - err = watcher.AddListener(resp.ID, errors, done) - if err != nil { - containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) - if containerRemoveError != nil { - return "", fmt.Errorf("error while watching for container status: %v - unable to remove container: %v", err, containerRemoveError) - } - return "", fmt.Errorf("error while watching for container status: %v", err) - } - - select { - case err := <-errors: - if err != nil { - containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true}) - if containerRemoveError != nil { - return "", fmt.Errorf("%v - unable to remove container: %v", err, containerRemoveError) - } - return "", err - } - case <-done: - - } - } - - return resp.ID, err -} - -func (builder *Builder) WaitForHealthy() *Builder { - builder.waitForHealthy = true - return builder -} - -// WithName set the name of the container -func (builder *Builder) WithName(containerName string) *Builder { - builder.containerName = containerName - return builder -} diff --git a/pkg/testutils/dockerutils/client.go b/pkg/testutils/dockerutils/client.go deleted file mode 100644 index e0c07be..0000000 --- a/pkg/testutils/dockerutils/client.go +++ /dev/null @@ -1,402 +0,0 @@ -package dockerutils - -import ( - "context" - "fmt" - "io" - "io/ioutil" - "strings" - "sync" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/client" -) - -// Client from the docker API with additional functions -type Client struct { - *client.Client - watcher *Watcher - mutex *sync.Mutex -} - -// Close docker connection -func (client *Client) Close() error { - client.mutex.Lock() - defer client.mutex.Unlock() - if client.watcher != nil { - client.watcher.stop() - } - - return client.Client.Close() -} - -// ContainerListByLabels returns only containers which match by given labels -func (client *Client) ContainerListByLabels(ctx context.Context, all bool, containerLabels map[string]string) ([]types.Container, error) { - filterArgs := filters.NewArgs() - for key, value := range containerLabels { - filterArgs.Add("label", fmt.Sprintf("%v=%v", key, value)) - } - containers, err := client.ContainerList(ctx, types.ContainerListOptions{ - All: all, - Filters: filterArgs, - }) - if err != nil { - return nil, err - } - if containers == nil { - return nil, fmt.Errorf("No containers found by given labels") - } - return containers, nil -} - -// ContainerListByNames returns only containers which match by given labels -func (client *Client) ContainerListByNames(ctx context.Context, all bool, containerNames ...string) ([]types.Container, error) { - filterArgs := filters.NewArgs() - for _, name := range containerNames { - filterArgs.Add("name", name) - } - containers, err := client.ContainerList(ctx, types.ContainerListOptions{ - All: all, - Filters: filterArgs, - }) - if err != nil { - return nil, err - } - if containers == nil { - return nil, fmt.Errorf("No containers found by given names") - } - return containers, nil -} - -// ContainerRemoveByIDs deletes all containers which match by their container ids -func (client *Client) ContainerRemoveByIDs(ctx context.Context, containerIDs ...string) error { - for _, containerID := range containerIDs { - err := client.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true}) - if err != nil { - return err - } - } - return nil -} - -// ContainerRemoveByLabels deletes all containers which match by given labels -func (client *Client) ContainerRemoveByLabels(ctx context.Context, containerLabels map[string]string) error { - containers, err := client.ContainerListByLabels(ctx, true, containerLabels) - if err != nil { - return err - } - for _, container := range containers { - err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true}) - if err != nil { - return err - } - } - return nil -} - -// ContainerRemoveByNames deletes all containers which match by their names -func (client *Client) ContainerRemoveByNames(ctx context.Context, containerNames ...string) error { - containers, err := client.ContainerListByNames(ctx, true, containerNames...) - if err != nil { - return err - } - for _, container := range containers { - err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true}) - if err != nil { - return err - } - } - return nil -} - -// ContainerStopByIDs deletes all containers which match by their container ids -func (client *Client) ContainerStopByIDs(ctx context.Context, timeout time.Duration, containerIDs ...string) error { - for _, containerID := range containerIDs { - err := client.ContainerStop(ctx, containerID, &timeout) - if err != nil { - return err - } - } - return nil -} - -// ContainerStopByLabels shutdown containters which match by given labels -func (client *Client) ContainerStopByLabels(ctx context.Context, timeout time.Duration, containerLabels map[string]string) error { - containers, err := client.ContainerListByLabels(ctx, true, containerLabels) - if err != nil { - return err - } - for _, container := range containers { - err := client.ContainerStop(ctx, container.ID, &timeout) - if err != nil { - return err - } - } - return nil -} - -// ContainerStopByNames shutdown containters matching by their names -func (client *Client) ContainerStopByNames(ctx context.Context, timeout time.Duration, containerNames ...string) error { - containers, err := client.ContainerListByNames(ctx, true, containerNames...) - if err != nil { - return err - } - for _, container := range containers { - err := client.ContainerStop(ctx, container.ID, &timeout) - if err != nil { - return err - } - } - return nil -} - -// GetWatcher returns a watcher for container health states -func (client *Client) GetWatcher() *Watcher { - if client.watcher != nil { - return client.watcher - } - - client.mutex.Lock() - defer client.mutex.Unlock() - - client.watcher = &Watcher{ - client: client, - errorChannels: make(map[string]chan<- error), - doneChannels: make(map[string]chan<- struct{}), - errorMapper: make(map[string]ErrorMapper), - mutex: new(sync.RWMutex), - } - - client.watcher.start() - return client.watcher -} - -// NetworkListByLabels returns networks which match by given labels -func (client *Client) NetworkListByLabels(ctx context.Context, networkLabels map[string]string) ([]types.NetworkResource, error) { - args := filters.NewArgs() - for key, value := range networkLabels { - args.Add("label", fmt.Sprintf("%v=%v", key, value)) - } - - return client.NetworkList(ctx, types.NetworkListOptions{ - Filters: args, - }) -} - -// NetworkListByNames returns networks which match by their names. If a -// network can not be found, the function returns an error -func (client *Client) NetworkListByNames(ctx context.Context, networkNames ...string) ([]types.NetworkResource, error) { - networks, err := client.NetworkList(ctx, types.NetworkListOptions{}) - if err != nil { - return nil, err - } - - foundNetwork := make(map[string]bool, 0) - for _, networkName := range networkNames { - foundNetwork[networkName] = false - } - - filteredNetworks := make([]types.NetworkResource, 0) - for _, networkName := range networkNames { - for _, network := range networks { - if network.Name == networkName { - filteredNetworks = append(filteredNetworks, network) - foundNetwork[networkName] = true - } - } - } - - for _, networkName := range networkNames { - if !foundNetwork[networkName] { - return nil, fmt.Errorf("Network %v not found", networkName) - } - } - - return filteredNetworks, nil -} - -// NetworkRemoveByLabels remove all networks which match by given labels -func (client *Client) NetworkRemoveByLabels(ctx context.Context, containerLabels map[string]string) error { - networks, err := client.NetworkListByLabels(ctx, containerLabels) - if err != nil { - return err - } - - for _, network := range networks { - err := client.NetworkRemove(ctx, network.ID) - if err != nil { - return err - } - } - - return nil -} - -// NetworkRemoveByNames remove all networks match by their names. If a -// network can not be found, the function returns an error -func (client *Client) NetworkRemoveByNames(ctx context.Context, networkNames ...string) error { - networks, err := client.NetworkListByNames(ctx, networkNames...) - if err != nil { - return err - } - - for _, network := range networks { - err := client.NetworkRemove(ctx, network.ID) - if err != nil { - return err - } - } - - return nil -} - -// NetworkRemoveByIDs remove all networks match by their id -func (client *Client) NetworkRemoveByIDs(ctx context.Context, containerIDs ...string) error { - for _, containerID := range containerIDs { - err := client.NetworkRemove(ctx, containerID) - if err != nil { - return err - } - } - return nil -} - -// NewBuilder returns a new builder for containers -func (client *Client) NewBuilder(image string) *Builder { - return &Builder{ - client: client, - containerConfig: &container.Config{ - Image: image, - }, - hostConfig: new(container.HostConfig), - networkConfig: &network.NetworkingConfig{ - EndpointsConfig: make(map[string]*network.EndpointSettings, 0), - }, - networks: make(map[string][]string, 0), - ports: make([]string, 0), - pull: false, - waitForHealthy: false, - } -} - -// Pull image -func (client *Client) Pull(ctx context.Context, image string, w io.Writer) error { - - parts := strings.Split(image, "/") - switch len(parts) { - case 1: - image = fmt.Sprintf("docker.io/library/%v", parts[0]) - case 2: - if strings.Compare(parts[0], "library") == 0 || - strings.Compare(parts[0], "docker.io") == 0 { - image = fmt.Sprintf("docker.io/library/%v", parts[1]) - } - } - - readCloser, err := client.ImagePull(ctx, image, types.ImagePullOptions{}) - if err != nil { - return err - } - - _, err = io.Copy(w, readCloser) - if err != nil { - return err - } - - return nil -} - -// PullQuiet image -func (client *Client) PullQuiet(ctx context.Context, image string) error { - return client.Pull(ctx, image, ioutil.Discard) -} - -// VolumeListByLabels returns volumes which match by given labels -func (client *Client) VolumeListByLabels(ctx context.Context, volumeLabels map[string]string) (volume.VolumesListOKBody, error) { - args := filters.NewArgs() - for key, value := range volumeLabels { - args.Add("label", fmt.Sprintf("%v=%v", key, value)) - } - - return client.VolumeList(ctx, args) -} - -// VolumeListByNames returns volumes which match by their names. If a -// volume can not be found, the function returns an error -func (client *Client) VolumeListByNames(ctx context.Context, volumeNames ...string) (volume.VolumesListOKBody, error) { - args := filters.NewArgs() - foundVolumes := make(map[string]bool, 0) - for _, volumeName := range volumeNames { - foundVolumes[volumeName] = false - args.Add("name", volumeName) - } - - volumes, err := client.VolumeList(ctx, args) - if err != nil { - return volume.VolumesListOKBody{}, err - } - - for _, volume := range volumes.Volumes { - foundVolumes[volume.Name] = true - } - - for _, volumeName := range volumeNames { - if foundVolumes[volumeName] != true { - return volume.VolumesListOKBody{}, fmt.Errorf("Volume %v not found", volumeName) - } - } - return volumes, err -} - -// VolumeRemoveByLabels remove all volumes match by their labels -func (client *Client) VolumeRemoveByLabels(ctx context.Context, volumeLabels map[string]string) error { - volumes, err := client.VolumeListByLabels(ctx, volumeLabels) - if err != nil { - return err - } - - for _, volume := range volumes.Volumes { - err := client.VolumeRemove(ctx, volume.Name, true) - if err != nil { - return err - } - } - - return nil -} - -// VolumeRemoveByNames remove all volumes match by their names. If a -// volume can not be found, the function returns an error -func (client *Client) VolumeRemoveByNames(ctx context.Context, volumeNames ...string) error { - volumes, err := client.VolumeListByNames(ctx, volumeNames...) - if err != nil { - return err - } - - for _, volume := range volumes.Volumes { - err := client.VolumeRemove(ctx, volume.Name, true) - if err != nil { - return err - } - } - - return nil -} - -// New returns a new dockerutil client -func New() (*Client, error) { - dockerClient, err := client.NewEnvClient() - if err != nil { - return nil, err - } - return &Client{ - dockerClient, - nil, - new(sync.Mutex), - }, nil -} diff --git a/pkg/testutils/dockerutils/client_test.go b/pkg/testutils/dockerutils/client_test.go deleted file mode 100644 index e69d921..0000000 --- a/pkg/testutils/dockerutils/client_test.go +++ /dev/null @@ -1,443 +0,0 @@ -package dockerutils - -import ( - "context" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/volume" - uuid "github.com/satori/go.uuid" - "github.com/stretchr/testify/require" -) - -// TestContainerCRUD -// Test the following API functions: -// - ContainerListByLabels -// - ContainerListByNames -// - ContainerRemoveByNames -// - ContainerRemoveByLabels -// - ContainerRemoveByIDs -func TestContainerCRUD(t *testing.T) { - var ( - ctx = context.Background() - require = require.New(t) - - iterations = 5 - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := New() - require.NoError(err) - t.Cleanup(func() { - dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) - }) - - // Create Containers - containerIDs := make([]string, 0) - containerNames := make([]string, 0) - for i := 0; i < iterations; i++ { - containerName := uuid.NewV4().String() - containerID, err := dockerClient.NewBuilder("nginx:alpine"). - Labels(cleanupLabels). - Port("80"). - Pull(). - WithName(containerName). - Start(ctx) - require.NoError(err) - - containerNames = append(containerNames, containerName) - containerIDs = append(containerIDs, containerID) - } - - // ListByLabels - containers, err := dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) - require.NoError(err) - require.Len(containers, iterations) - for _, container := range containers { - require.Contains(containerIDs, container.ID) - require.Contains(containerNames, strings.Split(container.Names[0], "/")[1]) - } - - // ListByNames - containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...) - require.NoError(err) - require.Len(containers, iterations) - for _, container := range containers { - require.Contains(containerIDs, container.ID) - require.Contains(containerNames, strings.Split(container.Names[0], "/")[1]) - } - - // RemoveByLabels - err = dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) - require.NoError(err) - - containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) - require.NoError(err) - require.Len(containers, 0) - - // Create - containerIDs = make([]string, 0) - containerNames = make([]string, 0) - for i := 0; i < iterations; i++ { - containerName := uuid.NewV4().String() - containerID, err := dockerClient.NewBuilder("nginx:alpine"). - Labels(cleanupLabels). - Port("80"). - Pull(). - WithName(containerName). - Start(ctx) - require.NoError(err) - - containerNames = append(containerNames, containerName) - containerIDs = append(containerIDs, containerID) - } - - // RemoveByNames - err = dockerClient.ContainerRemoveByNames(ctx, containerNames...) - require.NoError(err) - - containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...) - require.NoError(err) - require.Len(containers, 0) - - // Create - containerIDs = make([]string, 0) - containerNames = make([]string, 0) - for i := 0; i < iterations; i++ { - containerName := uuid.NewV4().String() - containerID, err := dockerClient.NewBuilder("nginx:alpine"). - Labels(cleanupLabels). - Port("80"). - Pull(). - WithName(containerName). - Start(ctx) - require.NoError(err) - - containerNames = append(containerNames, containerName) - containerIDs = append(containerIDs, containerID) - } - - // RemoveByID - err = dockerClient.ContainerRemoveByIDs(ctx, containerIDs...) - require.NoError(err) - - containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels) - require.NoError(err) - require.Len(containers, 0) -} - -// TestNetworkCRUD -// Test the following API functions: -// - NetworkListByLabels -// - NetworkListByNames -// - NetworkRemoveByLabels -// - NetworkRemoveByNames -// - NetworkRemoveByIDs -func TestNetworkCRUD(t *testing.T) { - var ( - ctx = context.Background() - require = require.New(t) - - iterations = 5 - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := New() - require.NoError(err) - t.Cleanup(func() { - dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) - }) - - // Create Networks - networkIDs := make([]string, 0) - networkNames := make([]string, 0) - for i := 0; i < iterations; i++ { - networkName := uuid.NewV4().String() - resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Labels: cleanupLabels, - }) - require.NoError(err) - - networkNames = append(networkNames, networkName) - networkIDs = append(networkIDs, resp.ID) - } - - // ListByLabels - networks, err := dockerClient.NetworkListByLabels(ctx, cleanupLabels) - require.NoError(err) - require.Len(networks, iterations) - for _, network := range networks { - require.Contains(networkIDs, network.ID) - require.Contains(networkNames, network.Name) - } - - // ListByLabels, network with label does not exist - networks, err = dockerClient.NetworkListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) - require.NoError(err) - require.Len(networks, 0) - - // ListByNames - networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) - require.NoError(err) - require.Len(networks, iterations) - for _, network := range networks { - require.Contains(networkIDs, network.ID) - require.Contains(networkNames, network.Name) - } - - // ListByNames, network with names does not exist - networks, err = dockerClient.NetworkListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String()) - require.Error(err) - require.Nil(networks) - - // RemoveByLabels - err = dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) - require.NoError(err) - - networks, err = dockerClient.NetworkListByLabels(ctx, cleanupLabels) - require.NoError(err) - require.Len(networks, 0) - - // RemoveByLabels, label does not exists - err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) - require.NoError(err) - - // Create Networks - networkIDs = make([]string, 0) - networkNames = make([]string, 0) - for i := 0; i < iterations; i++ { - networkName := uuid.NewV4().String() - resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Labels: cleanupLabels, - }) - require.NoError(err) - - networkNames = append(networkNames, networkName) - networkIDs = append(networkIDs, resp.ID) - } - - // RemoveByNames - err = dockerClient.NetworkRemoveByNames(ctx, networkNames...) - require.NoError(err) - - networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) - require.Error(err) - require.Nil(networks) - - // RemoveByNames, name does not exists - err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String()) - require.Error(err) - - // Create Networks - networkIDs = make([]string, 0) - networkNames = make([]string, 0) - for i := 0; i < iterations; i++ { - networkName := uuid.NewV4().String() - resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Labels: cleanupLabels, - }) - require.NoError(err) - - networkNames = append(networkNames, networkName) - networkIDs = append(networkIDs, resp.ID) - } - - // RemoveByIDs - err = dockerClient.NetworkRemoveByIDs(ctx, networkIDs...) - require.NoError(err) - - networks, err = dockerClient.NetworkListByNames(ctx, networkNames...) - require.Error(err) - require.Nil(networks) - - // RemoveByID, id does not exists - err = dockerClient.NetworkRemoveByIDs(ctx, uuid.NewV4().String()) - require.Error(err) -} - -func TestVolumeCRUD(t *testing.T) { - var ( - ctx = context.Background() - require = require.New(t) - - iterations = 5 - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := New() - require.NoError(err) - t.Cleanup(func() { - dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels) - }) - - // Create Volumes - volumeNames := make([]string, 0) - for i := 0; i < iterations; i++ { - volumeName := uuid.NewV4().String() - volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{ - Name: volumeName, - Labels: cleanupLabels, - }) - require.NoError(err) - - volumeNames = append(volumeNames, volume.Name) - } - - // ListByLabels - volumes, err := dockerClient.VolumeListByLabels(ctx, cleanupLabels) - require.NoError(err) - require.Len(volumes.Volumes, iterations) - for _, volume := range volumes.Volumes { - require.Contains(volumeNames, volume.Name) - } - - // ListByLabels, network with label does not exist - volumes, err = dockerClient.VolumeListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) - require.NoError(err) - require.Len(volumes.Volumes, 0) - - // ListByNames - volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...) - require.NoError(err) - require.Len(volumes.Volumes, iterations) - for _, volume := range volumes.Volumes { - require.Contains(volumeNames, volume.Name) - } - - // ListByNames, network with names does not exist - volumes, err = dockerClient.VolumeListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String()) - require.Error(err) - require.Nil(volumes.Volumes) - - // RemoveByLabels - err = dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels) - require.NoError(err) - - volumes, err = dockerClient.VolumeListByLabels(ctx, cleanupLabels) - require.NoError(err) - require.Len(volumes.Volumes, 0) - - // RemoveByLabels, labels does not exists - err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()}) - require.NoError(err) - - // Create Volumes - volumeNames = make([]string, 0) - for i := 0; i < iterations; i++ { - volumeName := uuid.NewV4().String() - volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{ - Name: volumeName, - Labels: cleanupLabels, - }) - require.NoError(err) - - volumeNames = append(volumeNames, volume.Name) - } - - // RemoveByNames - err = dockerClient.VolumeRemoveByNames(ctx, volumeNames...) - require.NoError(err) - - volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...) - require.Error(err) - require.Nil(volumes.Volumes) - - // RemoveByNames, name does not exists - err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String()) - require.Error(err) -} - -// TestContainerMultipleNetworks -// Test if a container can be accessed ofer multiple networks/ips. -func TestContainerMultipleNetworks(t *testing.T) { - var ( - ctx = context.Background() - require = require.New(t) - - iterations = 5 - - cleanupLabels = map[string]string{ - uuid.NewV4().String(): uuid.NewV4().String(), - } - ) - - dockerClient, err := New() - require.NoError(err) - t.Cleanup(func() { - dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels) - dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels) - }) - - // Create Containers - containerIDs := make([]string, 0) - containerNames := make([]string, 0) - containersNetworks := make(map[string]map[string][]string, 0) - for i := 0; i < iterations; i++ { - containerName := uuid.NewV4().String() - - containerNetworks := map[string][]string{ - uuid.NewV4().String(): { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - uuid.NewV4().String(): { - uuid.NewV4().String(), - uuid.NewV4().String(), - }, - } - - builder := dockerClient.NewBuilder("nginx:alpine"). - Labels(cleanupLabels). - Port("80"). - Pull(). - WithName(containerName) - - for networkName, aliasses := range containerNetworks { - _, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Labels: cleanupLabels, - }) - require.NoError(err) - - builder.Network(networkName, aliasses...) - } - - containerID, err := builder.Start(ctx) - require.NoError(err) - - containerNames = append(containerNames, containerName) - containerIDs = append(containerIDs, containerID) - containersNetworks[containerID] = containerNetworks - } - - for containerID, containerNetworks := range containersNetworks { - for networkName := range containerNetworks { - networks, err := dockerClient.NetworkListByNames(ctx, networkName) - require.NoError(err) - for _, network := range networks { - if _, present := network.Containers[containerID]; !present { - require.Fail("Container %v not found in network %v", containerID, network.ID) - } - - networkIPParts := strings.Split(network.Containers[containerID].IPv4Address, "/") - - url := fmt.Sprintf("http://%v", networkIPParts[0]) - resp, err := http.Get(url) - require.NoError(err) - defer resp.Body.Close() - require.Equal(http.StatusOK, resp.StatusCode) - } - } - } -} diff --git a/pkg/testutils/dockerutils/watcher.go b/pkg/testutils/dockerutils/watcher.go deleted file mode 100644 index 29d7a10..0000000 --- a/pkg/testutils/dockerutils/watcher.go +++ /dev/null @@ -1,164 +0,0 @@ -package dockerutils - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/events" -) - -var ( - ErrDied = errors.New("died") - ErrUnhealthy = errors.New("went unhealthy") -) - -type Watcher struct { - client *Client - errorChannels map[string]chan<- error - doneChannels map[string]chan<- struct{} - errorMapper map[string]ErrorMapper - mutex *sync.RWMutex - lastError error - cancelFunc context.CancelFunc -} - -func (watcher *Watcher) AddListenerWithErrorMapper(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}, errorMapper ErrorMapper) error { - watcher.mutex.Lock() - defer watcher.mutex.Unlock() - - if watcher.lastError != nil { - return watcher.lastError - } - - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(20*time.Second)) - defer cancel() - - containerJSON, err := watcher.client.ContainerInspect(ctx, containerID) - if err != nil { - return fmt.Errorf("Unable to check if container is already unhealthy: %v", err) - } - - if containerJSON.State.Dead || !containerJSON.State.Running { - go func() { - errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrDied)) - doneChannel <- struct{}{} - }() - return nil - } - - if containerJSON.State.Health == nil { - go func() { - doneChannel <- struct{}{} - }() - return nil - } - - switch containerJSON.State.Health.Status { - case "starting": - watcher.errorChannels[containerID] = errorChannel - watcher.doneChannels[containerID] = doneChannel - watcher.errorMapper[containerID] = errorMapper - case "healthy": - go func() { - doneChannel <- struct{}{} - }() - case "unhealthy": - go func() { - errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrUnhealthy)) - doneChannel <- struct{}{} - }() - default: - go func() { - errorChannel <- errorMapper(fmt.Errorf("Container %v went in an unknown state during startup: %s", containerID, containerJSON.State.Health.Status)) - doneChannel <- struct{}{} - }() - } - return nil -} - -func (watcher *Watcher) AddListener(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}) error { - return watcher.AddListenerWithErrorMapper(containerID, errorChannel, doneChannel, defaultErrorMapper) -} - -func (watcher *Watcher) removeListener(containerID string) { - watcher.mutex.Lock() - defer watcher.mutex.Unlock() - delete(watcher.doneChannels, containerID) - delete(watcher.errorChannels, containerID) - delete(watcher.errorMapper, containerID) -} - -func (watcher *Watcher) start() { - ctx, cancel := context.WithCancel(context.Background()) - watcher.cancelFunc = cancel - dockerEvents, errors := watcher.client.Events(ctx, types.EventsOptions{}) - - sendErrorFunc := func() { - watcher.mutex.Lock() - for containerID, errorChannel := range watcher.errorChannels { - go func(errorChannel chan<- error, doneChannel chan<- struct{}, err error, errorMapper ErrorMapper) { - errorChannel <- errorMapper(err) - doneChannel <- struct{}{} - }(errorChannel, watcher.doneChannels[containerID], watcher.lastError, watcher.errorMapper[containerID]) - } - watcher.mutex.Unlock() - } - - go func() { - for { - select { - case event := <-dockerEvents: - watcher.mutex.RLock() - errorChannel, present := watcher.errorChannels[event.Actor.ID] - if !present || event.Type != events.ContainerEventType { - continue - } - doneChannel := watcher.doneChannels[event.Actor.ID] - errorMapper := watcher.errorMapper[event.Actor.ID] - watcher.mutex.RUnlock() - - switch event.Action { - case "health_status: healthy": - go func() { - doneChannel <- struct{}{} - }() - watcher.removeListener(event.Actor.ID) - case "health_status: unhealthy": - go func() { - errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrUnhealthy)) - doneChannel <- struct{}{} - }() - watcher.removeListener(event.Actor.ID) - case "die": - go func() { - errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrDied)) - doneChannel <- struct{}{} - }() - watcher.removeListener(event.Actor.ID) - } - case err := <-errors: - watcher.lastError = err - sendErrorFunc() - return - case <-ctx.Done(): - watcher.lastError = fmt.Errorf("Watcher was canceled") - sendErrorFunc() - return - } - } - }() -} - -func (watcher *Watcher) stop() { - watcher.cancelFunc() -} - -type ErrorMapper func(error) error - -func defaultErrorMapper(err error) error { - return err -}