fix: exclude dockerutils and testutils package
This commit is contained in:
		
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								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 | ||||
| ) | ||||
|   | ||||
							
								
								
									
										32
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								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= | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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") | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Markus Pesch
					Markus Pesch