diff --git a/.github/workflows/golang-test-linux.yml b/.github/workflows/golang-test-linux.yml index ef66720024d..0532e8a9396 100644 --- a/.github/workflows/golang-test-linux.yml +++ b/.github/workflows/golang-test-linux.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: arch: [ '386','amd64' ] - store: [ 'sqlite', 'postgres'] + store: [ 'sqlite', 'postgres', 'mysql' ] runs-on: ubuntu-22.04 steps: - name: Install Go diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml index da3ec746ac5..5a3c6c22e19 100644 --- a/.github/workflows/test-infrastructure-files.yml +++ b/.github/workflows/test-infrastructure-files.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - store: [ 'sqlite', 'postgres' ] + store: [ 'sqlite', 'postgres', 'mysql' ] services: postgres: image: ${{ (matrix.store == 'postgres') && 'postgres' || '' }} @@ -34,6 +34,19 @@ jobs: --health-timeout 5s ports: - 5432:5432 + mysql: + image: ${{ (matrix.store == 'mysql') && 'mysql' || '' }} + env: + MYSQL_USER: netbird + MYSQL_PASSWORD: mysql + MYSQL_ROOT_PASSWORD: mysqlroot + MYSQL_DATABASE: netbird + options: >- + --health-cmd "mysqladmin ping --silent" + --health-interval 10s + --health-timeout 5s + ports: + - 3306:3306 steps: - name: Set Database Connection String run: | @@ -42,6 +55,11 @@ jobs: else echo "NETBIRD_STORE_ENGINE_POSTGRES_DSN==" >> $GITHUB_ENV fi + if [ "${{ matrix.store }}" == "mysql" ]; then + echo "NETBIRD_STORE_ENGINE_MYSQL_DSN=netbird:mysql@tcp($(hostname -I | awk '{print $1}'):3306)/netbird" >> $GITHUB_ENV + else + echo "NETBIRD_STORE_ENGINE_MYSQL_DSN==" >> $GITHUB_ENV + fi - name: Install jq run: sudo apt-get install -y jq @@ -84,6 +102,7 @@ jobs: CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified" CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }} NETBIRD_STORE_ENGINE_POSTGRES_DSN: ${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }} + NETBIRD_STORE_ENGINE_MYSQL_DSN: ${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }} CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false - name: check values @@ -112,6 +131,7 @@ jobs: CI_NETBIRD_SIGNAL_PORT: 12345 CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }} NETBIRD_STORE_ENGINE_POSTGRES_DSN: '${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}$' + NETBIRD_STORE_ENGINE_MYSQL_DSN: '${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$' CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4" @@ -149,6 +169,7 @@ jobs: grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES" grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep -A 3 RedirectURLs | grep "http://localhost:53000" grep "external-ip" turnserver.conf | grep $CI_NETBIRD_TURN_EXTERNAL_IP + grep "NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN" docker-compose.yml grep NETBIRD_STORE_ENGINE_POSTGRES_DSN docker-compose.yml | egrep "$NETBIRD_STORE_ENGINE_POSTGRES_DSN" # check relay values grep "NB_EXPOSED_ADDRESS=$CI_NETBIRD_DOMAIN:33445" docker-compose.yml diff --git a/go.mod b/go.mod index 0a16753ea43..f3b633dfc88 100644 --- a/go.mod +++ b/go.mod @@ -76,8 +76,9 @@ require ( github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.31.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 + github.com/testcontainers/testcontainers-go v0.34.0 + github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 github.com/things-go/go-socks5 v0.0.4 github.com/yusufpapurcu/wmi v1.2.4 github.com/zcalusic/sysinfo v1.0.2 @@ -96,9 +97,10 @@ require ( golang.org/x/term v0.25.0 google.golang.org/api v0.177.0 gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.7 gorm.io/driver/sqlite v1.5.3 - gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde + gorm.io/gorm v1.25.7 nhooyr.io/websocket v1.8.11 ) @@ -107,10 +109,10 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect dario.cat/mergo v1.0.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.3 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect @@ -131,13 +133,14 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/containerd v1.7.16 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v26.1.5+incompatible // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -151,6 +154,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-text/render v0.1.0 // indirect github.com/go-text/typesetting v0.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect diff --git a/go.sum b/go.sum index a4d7ea7f9c1..59cbdd959e9 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJpl dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= fyne.io/fyne/v2 v2.5.0 h1:lEjEIso0Vi4sJXYngIMoXOM6aUjqnPjK7pBpxRxG9aI= fyne.io/fyne/v2 v2.5.0/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc= fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= @@ -62,8 +64,6 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= -github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= @@ -135,18 +135,20 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg= -github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -164,8 +166,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= -github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -238,6 +240,9 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -662,6 +667,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -678,10 +684,12 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= -github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= -github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E= -github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw= +github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= +github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= +github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0 h1:Tqz17mGXjPORHFS/oBUGdeJyIsZXLsVVHRhaBqhewGI= +github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0/go.mod h1:hDpm3DLfjo7rd6232wWflEBDGr6Ow9ys43mJTiJwWx8= +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 h1:c51aBXT3v2HEBVarmaBnsKzvgZjC5amn0qsj8Naqi50= +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0/go.mod h1:EWP75ogLQU4M4L8U+20mFipjV4WIR9WtlMXSB6/wiuc= github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0= github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= @@ -1224,14 +1232,16 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g= gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg= -gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/infrastructure_files/configure.sh b/infrastructure_files/configure.sh index ff33004b212..d02e4f40c14 100755 --- a/infrastructure_files/configure.sh +++ b/infrastructure_files/configure.sh @@ -53,6 +53,18 @@ if [[ "$NETBIRD_STORE_CONFIG_ENGINE" == "postgres" ]]; then export NETBIRD_STORE_ENGINE_POSTGRES_DSN fi +# Check if MySQL is set as the store engine +if [[ "$NETBIRD_STORE_CONFIG_ENGINE" == "mysql" ]]; then + # Exit if 'NETBIRD_STORE_ENGINE_MYSQL_DSN' is not set + if [[ -z "$NETBIRD_STORE_ENGINE_MYSQL_DSN" ]]; then + echo "Warning: NETBIRD_STORE_CONFIG_ENGINE=mysql but NETBIRD_STORE_ENGINE_MYSQL_DSN is not set." + echo "Please add the following line to your setup.env file:" + echo 'NETBIRD_STORE_ENGINE_MYSQL_DSN=":@tcp(127.0.0.1:3306)/"' + exit 1 + fi + export NETBIRD_STORE_ENGINE_MYSQL_DSN +fi + # local development or tests if [[ $NETBIRD_DOMAIN == "localhost" || $NETBIRD_DOMAIN == "127.0.0.1" ]]; then export NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN="netbird.selfhosted" diff --git a/infrastructure_files/docker-compose.yml.tmpl b/infrastructure_files/docker-compose.yml.tmpl index ba68b3f8da5..b7904fb5ba4 100644 --- a/infrastructure_files/docker-compose.yml.tmpl +++ b/infrastructure_files/docker-compose.yml.tmpl @@ -96,6 +96,7 @@ services: max-file: "2" environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN + - NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN # Coturn coturn: diff --git a/infrastructure_files/docker-compose.yml.tmpl.traefik b/infrastructure_files/docker-compose.yml.tmpl.traefik index c4415d84835..7d51c4ffbb3 100644 --- a/infrastructure_files/docker-compose.yml.tmpl.traefik +++ b/infrastructure_files/docker-compose.yml.tmpl.traefik @@ -83,6 +83,7 @@ services: - traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN + - NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN # Coturn coturn: diff --git a/management/server/account.go b/management/server/account.go index 95c93a22bdd..2bf62d646ab 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1186,7 +1186,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco } func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error { - if newSettings.PeerInactivityExpirationEnabled { if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration { oldSettings.PeerInactivityExpiration = newSettings.PeerInactivityExpiration diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index dc8765e197f..3eaf509809f 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -472,8 +472,14 @@ func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.Clie func Test_SyncStatusRace(t *testing.T) { t.Skip() - if os.Getenv("CI") == "true" && os.Getenv("NETBIRD_STORE_ENGINE") == "postgres" { - t.Skip("Skipping on CI and Postgres store") + if os.Getenv("CI") == "true" { + if os.Getenv("NETBIRD_STORE_ENGINE") == "postgres" { + t.Skip("Skipping on CI and Postgres store") + } + + if os.Getenv("NETBIRD_STORE_ENGINE") == "mysql" { + t.Skip("Skipping on CI and MySQL store") + } } for i := 0; i < 500; i++ { t.Run(fmt.Sprintf("TestRun-%d", i), func(t *testing.T) { diff --git a/management/server/migration/migration.go b/management/server/migration/migration.go index 6f12d94b401..9dc101d249a 100644 --- a/management/server/migration/migration.go +++ b/management/server/migration/migration.go @@ -17,12 +17,21 @@ import ( "gorm.io/gorm" ) +func GetColumnName(db *gorm.DB, column string) string { + + if db.Name() == "mysql" { + return fmt.Sprintf("`%s`", column) + } + + return column +} + // MigrateFieldFromGobToJSON migrates a column from Gob encoding to JSON encoding. // T is the type of the model that contains the field to be migrated. // S is the type of the field to be migrated. func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, fieldName string) error { - - oldColumnName := fieldName + orgColumnName := fieldName + oldColumnName := GetColumnName(db, orgColumnName) newColumnName := fieldName + "_tmp" var model T @@ -72,7 +81,7 @@ func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, f for _, row := range rows { var field S - str, ok := row[oldColumnName].(string) + str, ok := row[orgColumnName].(string) if !ok { return fmt.Errorf("type assertion failed") } @@ -111,7 +120,8 @@ func MigrateFieldFromGobToJSON[T any, S any](ctx context.Context, db *gorm.DB, f // MigrateNetIPFieldFromBlobToJSON migrates a Net IP column from Blob encoding to JSON encoding. // T is the type of the model that contains the field to be migrated. func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fieldName string, indexName string) error { - oldColumnName := fieldName + orgColumnName := fieldName + oldColumnName := GetColumnName(db, orgColumnName) newColumnName := fieldName + "_tmp" var model T @@ -163,7 +173,7 @@ func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fi for _, row := range rows { var blobValue string - if columnValue := row[oldColumnName]; columnValue != nil { + if columnValue := row[orgColumnName]; columnValue != nil { value, ok := columnValue.(string) if !ok { return fmt.Errorf("type assertion failed") @@ -210,7 +220,9 @@ func MigrateNetIPFieldFromBlobToJSON[T any](ctx context.Context, db *gorm.DB, fi } func MigrateSetupKeyToHashedSetupKey[T any](ctx context.Context, db *gorm.DB) error { - oldColumnName := "key" + + orgColumnName := "key" + oldColumnName := GetColumnName(db, orgColumnName) newColumnName := "key_secret" var model T @@ -250,8 +262,9 @@ func MigrateSetupKeyToHashedSetupKey[T any](ctx context.Context, db *gorm.DB) er } for _, row := range rows { + var plainKey string - if columnValue := row[oldColumnName]; columnValue != nil { + if columnValue := row[orgColumnName]; columnValue != nil { value, ok := columnValue.(string) if !ok { return fmt.Errorf("type assertion failed") diff --git a/management/server/sql_store.go b/management/server/sql_store.go index 0ebda6440c1..b3168cbaa23 100644 --- a/management/server/sql_store.go +++ b/management/server/sql_store.go @@ -16,6 +16,7 @@ import ( "time" log "github.com/sirupsen/logrus" + "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -36,6 +37,7 @@ const ( storeSqliteFileName = "store.db" idQueryCondition = "id = ?" keyQueryCondition = "key = ?" + mysqlKeyQueryCondition = "`key` = ?" accountAndIDQueryCondition = "account_id = ? and id = ?" accountAndIDsQueryCondition = "account_id = ? AND id IN ?" accountIDCondition = "account_id = ?" @@ -80,6 +82,12 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine StoreEngine, metr sql.SetMaxOpenConns(conns) + if storeEngine == MysqlStoreEngine { + sql.SetConnMaxLifetime(time.Minute * 2) + sql.SetConnMaxIdleTime(time.Minute * 2) + sql.SetMaxIdleConns(conns) + } + log.WithContext(ctx).Infof("Set max open db connections to %d", conns) if err := migrate(ctx, db); err != nil { @@ -97,6 +105,15 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine StoreEngine, metr return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1}, nil } +func GetKeyQueryCondition(s *SqlStore) string { + + if s.storeEngine == MysqlStoreEngine { + return mysqlKeyQueryCondition + } + + return keyQueryCondition +} + // AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock func (s *SqlStore) AcquireGlobalLock(ctx context.Context) (unlock func()) { log.WithContext(ctx).Tracef("acquiring global lock") @@ -483,7 +500,8 @@ func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength func (s *SqlStore) GetAccountBySetupKey(ctx context.Context, setupKey string) (*Account, error) { var key SetupKey - result := s.db.Select("account_id").First(&key, keyQueryCondition, setupKey) + result := s.db.Select("account_id").First(&key, GetKeyQueryCondition(s), setupKey) + if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.NewSetupKeyNotFoundError(setupKey) @@ -711,7 +729,8 @@ func (s *SqlStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Acco func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*Account, error) { var peer nbpeer.Peer - result := s.db.Select("account_id").First(&peer, keyQueryCondition, peerKey) + result := s.db.Select("account_id").First(&peer, GetKeyQueryCondition(s), peerKey) + if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") @@ -729,7 +748,7 @@ func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) ( func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) (string, error) { var peer nbpeer.Peer var accountID string - result := s.db.Model(&peer).Select("account_id").Where(keyQueryCondition, peerKey).First(&accountID) + result := s.db.Model(&peer).Select("account_id").Where(GetKeyQueryCondition(s), peerKey).First(&accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: index lookup failed") @@ -755,7 +774,7 @@ func (s *SqlStore) GetAccountIDByUserID(userID string) (string, error) { func (s *SqlStore) GetAccountIDBySetupKey(ctx context.Context, setupKey string) (string, error) { var accountID string - result := s.db.Model(&SetupKey{}).Select("account_id").Where(keyQueryCondition, setupKey).First(&accountID) + result := s.db.Model(&SetupKey{}).Select("account_id").Where(GetKeyQueryCondition(s), setupKey).First(&accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.NewSetupKeyNotFoundError(setupKey) @@ -828,7 +847,8 @@ func (s *SqlStore) GetAccountNetwork(ctx context.Context, lockStrength LockingSt func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) { var peer nbpeer.Peer - result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).First(&peer, keyQueryCondition, peerKey) + result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).First(&peer, GetKeyQueryCondition(s), peerKey) + if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "peer not found") @@ -921,11 +941,22 @@ func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMe return NewSqlStore(ctx, db, PostgresStoreEngine, metrics) } +// NewMysqlStore creates a new MySQL store. +func NewMysqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) { + db, err := gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), getGormConfig()) + if err != nil { + return nil, err + } + + return NewSqlStore(ctx, db, MysqlStoreEngine, metrics) +} + func getGormConfig() *gorm.Config { return &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - CreateBatchSize: 400, - PrepareStmt: true, + Logger: logger.Default.LogMode(logger.Silent), + CreateBatchSize: 400, + PrepareStmt: true, + SkipDefaultTransaction: true, } } @@ -938,6 +969,15 @@ func newPostgresStore(ctx context.Context, metrics telemetry.AppMetrics) (Store, return NewPostgresqlStore(ctx, dsn, metrics) } +// newMysqlStore initializes a new MySQL store. +func newMysqlStore(ctx context.Context, metrics telemetry.AppMetrics) (Store, error) { + dsn, ok := os.LookupEnv(mysqlDsnEnv) + if !ok { + return nil, fmt.Errorf("%s is not set", mysqlDsnEnv) + } + return NewMysqlStore(ctx, dsn, metrics) +} + // NewSqliteStoreFromFileStore restores a store from FileStore and stores SQLite DB in the file located in datadir. func NewSqliteStoreFromFileStore(ctx context.Context, fileStore *FileStore, dataDir string, metrics telemetry.AppMetrics) (*SqlStore, error) { store, err := NewSqliteStore(ctx, dataDir, metrics) @@ -982,10 +1022,33 @@ func NewPostgresqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, return store, nil } +// NewMysqlStoreFromSqlStore restores a store from SqlStore and stores MySQL DB. +func NewMysqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) { + store, err := NewMysqlStore(ctx, dsn, metrics) + if err != nil { + return nil, err + } + + err = store.SaveInstallationID(ctx, sqliteStore.GetInstallationID()) + if err != nil { + return nil, err + } + + for _, account := range sqliteStore.GetAllAccounts(ctx) { + err := store.SaveAccount(ctx, account) + if err != nil { + return nil, err + } + } + + return store, nil +} + func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*SetupKey, error) { var setupKey SetupKey result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). - First(&setupKey, keyQueryCondition, key) + First(&setupKey, GetKeyQueryCondition(s), key) + if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.NewSetupKeyNotFoundError(key) @@ -1212,9 +1275,13 @@ func (s *SqlStore) GetGroupByName(ctx context.Context, lockStrength LockingStren // TODO: This fix is accepted for now, but if we need to handle this more frequently // we may need to reconsider changing the types. query := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Preload(clause.Associations) - if s.storeEngine == PostgresStoreEngine { + + switch s.storeEngine { + case PostgresStoreEngine: query = query.Order("json_array_length(peers::json) DESC") - } else { + case MysqlStoreEngine: + query = query.Order("JSON_LENGTH(JSON_EXTRACT(peers, \"$\")) DESC") + default: query = query.Order("json_array_length(peers) DESC") } diff --git a/management/server/store.go b/management/server/store.go index 71b0d457b4c..93727052775 100644 --- a/management/server/store.go +++ b/management/server/store.go @@ -20,6 +20,7 @@ import ( "github.com/netbirdio/netbird/dns" nbgroup "github.com/netbirdio/netbird/management/server/group" + "github.com/netbirdio/netbird/management/server/testutil" "github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/util" @@ -27,7 +28,6 @@ import ( "github.com/netbirdio/netbird/management/server/migration" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" - "github.com/netbirdio/netbird/management/server/testutil" "github.com/netbirdio/netbird/route" ) @@ -139,8 +139,10 @@ const ( FileStoreEngine StoreEngine = "jsonfile" SqliteStoreEngine StoreEngine = "sqlite" PostgresStoreEngine StoreEngine = "postgres" + MysqlStoreEngine StoreEngine = "mysql" postgresDsnEnv = "NETBIRD_STORE_ENGINE_POSTGRES_DSN" + mysqlDsnEnv = "NETBIRD_STORE_ENGINE_MYSQL_DSN" ) func getStoreEngineFromEnv() StoreEngine { @@ -151,7 +153,7 @@ func getStoreEngineFromEnv() StoreEngine { } value := StoreEngine(strings.ToLower(kind)) - if value == SqliteStoreEngine || value == PostgresStoreEngine { + if value == SqliteStoreEngine || value == MysqlStoreEngine || value == PostgresStoreEngine { return value } @@ -202,6 +204,9 @@ func NewStore(ctx context.Context, kind StoreEngine, dataDir string, metrics tel case PostgresStoreEngine: log.WithContext(ctx).Info("using Postgres store engine") return newPostgresStore(ctx, metrics) + case MysqlStoreEngine: + log.WithContext(ctx).Info("using MySQL store engine") + return newMysqlStore(ctx, metrics) default: return nil, fmt.Errorf("unsupported kind of store: %s", kind) } @@ -285,12 +290,14 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) ( if err != nil { return nil, nil, fmt.Errorf("failed to create test store: %v", err) } - cleanUp := func() { - store.Close(ctx) - } + + return getSqlStoreEngine(ctx, store, kind) +} + +func getSqlStoreEngine(ctx context.Context, store *SqlStore, kind StoreEngine) (Store, func(), error) { if kind == PostgresStoreEngine { - cleanUp, err = testutil.CreatePGDB() + cleanUp, err := testutil.CreatePostgresTestContainer() if err != nil { return nil, nil, err } @@ -304,9 +311,34 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) ( if err != nil { return nil, nil, err } + + return store, cleanUp, nil + } + + if kind == MysqlStoreEngine { + cleanUp, err := testutil.CreateMysqlTestContainer() + if err != nil { + return nil, nil, err + } + + dsn, ok := os.LookupEnv(mysqlDsnEnv) + if !ok { + return nil, nil, fmt.Errorf("%s is not set", mysqlDsnEnv) + } + + store, err = NewMysqlStoreFromSqlStore(ctx, store, dsn, nil) + if err != nil { + return nil, nil, err + } + + return store, cleanUp, nil + } + + closeConnection := func() { + store.Close(ctx) } - return store, cleanUp, nil + return store, closeConnection, nil } func loadSQL(db *gorm.DB, filepath string) error { diff --git a/management/server/testdata/mysql.cnf b/management/server/testdata/mysql.cnf new file mode 100644 index 00000000000..7f4ff027340 --- /dev/null +++ b/management/server/testdata/mysql.cnf @@ -0,0 +1,41 @@ +# For advice on how to change settings please see +# http://dev.mysql.com/doc/refman/8.1/en/server-configuration-defaults.html + +[mysqld] +# +# Remove leading # and set to the amount of RAM for the most important data +# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%. +# innodb_buffer_pool_size = 128M +# +# Remove leading # to turn on a very important data integrity option: logging +# changes to the binary log between backups. +# log_bin +# +# Remove leading # to set options mainly useful for reporting servers. +# The server defaults are faster for transactions and fast SELECTs. +# Adjust sizes as needed, experiment to find the optimal values. +# join_buffer_size = 128M +# sort_buffer_size = 2M +# read_rnd_buffer_size = 2M + +# Remove leading # to revert to previous value for default_authentication_plugin, +# this will increase compatibility with older clients. For background, see: +# https://dev.mysql.com/doc/refman/8.1/en/server-system-variables.html#sysvar_default_authentication_plugin +# default-authentication-plugin=mysql_native_password +host_cache_size=0 +skip-name-resolve +datadir=/var/lib/mysql +socket=/var/run/mysqld/mysqld.sock +secure-file-priv=/var/lib/mysql-files +user=mysql +sql_mode="" +wait_timeout=300 +interactive_timeout=300 +innodb_flush_log_at_trx_commit=2 +default_storage_engine = InnoDB + +pid-file=/var/run/mysqld/mysqld.pid +[client] +socket=/var/run/mysqld/mysqld.sock + +!includedir /etc/mysql/conf.d/ diff --git a/management/server/testutil/store.go b/management/server/testutil/store.go index 156a762fb39..9bae1b58d97 100644 --- a/management/server/testutil/store.go +++ b/management/server/testutil/store.go @@ -8,38 +8,91 @@ import ( "os" "time" - log "github.com/sirupsen/logrus" "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/mysql" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" ) -func CreatePGDB() (func(), error) { +var ( + mysqlContainer = (*mysql.MySQLContainer)(nil) + mysqlContainerString = "" + mysqlContainerConfigPath = "../../management/server/testdata/mysql.cnf" + postgresContainer = (*postgres.PostgresContainer)(nil) + postgresContainerString = "" +) + +func emptyCleanup() { + // Empty function, don't do anything. +} + +func CreateMysqlTestContainer() (func(), error) { + ctx := context.Background() - c, err := postgres.RunContainer(ctx, - testcontainers.WithImage("postgres:alpine"), - postgres.WithDatabase("test"), - postgres.WithUsername("postgres"), - postgres.WithPassword("postgres"), - testcontainers.WithWaitStrategy( - wait.ForLog("database system is ready to accept connections"). - WithOccurrence(2).WithStartupTimeout(15*time.Second)), + + if mysqlContainerString != "" && mysqlContainer != nil && mysqlContainer.IsRunning() { + RefreshMysqlDatabase(ctx) + return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_MYSQL_DSN", mysqlContainerString) + } + + container, err := mysql.Run(ctx, + "mysql:8.0.40", + mysql.WithConfigFile(mysqlContainerConfigPath), + mysql.WithDatabase("netbird"), + mysql.WithUsername("root"), + mysql.WithPassword(""), ) + if err != nil { return nil, err } - cleanup := func() { - timeout := 10 * time.Second - err = c.Stop(ctx, &timeout) - if err != nil { - log.WithContext(ctx).Warnf("failed to stop container: %s", err) - } + talksConn, _ := container.ConnectionString(ctx) + + mysqlContainer = container + mysqlContainerString = talksConn + + RefreshMysqlDatabase(ctx) + return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_MYSQL_DSN", talksConn) +} + +func CreatePostgresTestContainer() (func(), error) { + + ctx := context.Background() + + if postgresContainerString != "" && postgresContainer != nil && postgresContainer.IsRunning() { + RefreshPostgresDatabase(ctx) + return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", postgresContainerString) } - talksConn, err := c.ConnectionString(ctx) + container, err := postgres.Run(ctx, + "postgres:16-alpine", + postgres.WithDatabase("netbird"), + postgres.WithUsername("root"), + postgres.WithPassword("netbird"), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2).WithStartupTimeout(15*time.Second)), + ) if err != nil { - return cleanup, err + return nil, err } - return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", talksConn) + + talksConn, _ := container.ConnectionString(ctx) + + postgresContainerString = talksConn + postgresContainer = container + + RefreshPostgresDatabase(ctx) + return emptyCleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", postgresContainerString) +} + +func RefreshMysqlDatabase(ctx context.Context) { + _, _, _ = mysqlContainer.Exec(ctx, []string{"mysqladmin", "--user=root", "drop", "netbird", "-f"}) + _, _, _ = mysqlContainer.Exec(ctx, []string{"mysqladmin", "--user=root", "create", "netbird"}) +} + +func RefreshPostgresDatabase(ctx context.Context) { + _, _, _ = postgresContainer.Exec(ctx, []string{"dropdb", "-f", "netbird"}) + _, _, _ = postgresContainer.Exec(ctx, []string{"createdb", "netbird"}) } diff --git a/management/server/testutil/store_ios.go b/management/server/testutil/store_ios.go index af2cf7a3ffb..edde62f1e1c 100644 --- a/management/server/testutil/store_ios.go +++ b/management/server/testutil/store_ios.go @@ -3,4 +3,14 @@ package testutil -func CreatePGDB() (func(), error) { return func() {}, nil } +func CreatePostgresTestContainer() (func(), error) { + return func() { + // Empty function for Postgres + }, nil +} + +func CreateMysqlTestContainer() (func(), error) { + return func() { + // Empty function for MySQL + }, nil +}