fix: exclude dockerutils and testutils package
This commit is contained in:
parent
675af77965
commit
d0cfdd7102
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user