diff --git a/.github/workflows/build-test-containers.yaml b/.github/workflows/build-test-containers.yaml new file mode 100644 index 000000000..710b6a450 --- /dev/null +++ b/.github/workflows/build-test-containers.yaml @@ -0,0 +1,38 @@ +# -------------------------------------------------------------------------------------------- +# This file is generated. Do not edit manually +# -------------------------------------------------------------------------------------------- +name: Build test containers +'on': + push: + branches: + - master + - develop + - feature/** + paths: + - e2e-tests/containers/** +jobs: + build-docker-ssh: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + - name: Build mysql-ssh-login image + run: | + docker build -t dbgate/mysql-ssh-login:latest e2e-tests/containers/mysql-ssh-login + - name: Build mysql-ssh-keyfile image + run: | + docker build -t dbgate/mysql-ssh-keyfile:latest e2e-tests/containers/mysql-ssh-keyfile + - name: Log in to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: '${{ github.actor }}' + password: '${{ secrets.GITHUB_TOKEN }}' + - name: Push mysql-ssh-login to GHCR + run: | + docker tag dbgate/mysql-ssh-login:latest ghcr.io/dbgate/mysql-ssh-login:latest + docker push ghcr.io/dbgate/mysql-ssh-login:latest + - name: Push mysql-ssh-keyfile to GHCR + run: | + docker tag dbgate/mysql-ssh-keyfile:latest ghcr.io/dbgate/mysql-ssh-keyfile:latest + docker push ghcr.io/dbgate/mysql-ssh-keyfile:latest diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 4c7d96b03..f6ac413f0 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -9,7 +9,7 @@ name: Run tests - develop - feature/** jobs: - test-runner: + test: runs-on: ubuntu-latest container: 'node:18' steps: @@ -17,7 +17,7 @@ jobs: run: | apt-get update apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 1 - name: yarn install @@ -90,3 +90,7 @@ jobs: image: 'gvenzl/oracle-xe:21-slim' env: ORACLE_PASSWORD: Pwd2020Db + mysql-ssh-login: + image: 'ghcr.io/dbgate/mysql-ssh-login:latest' + mysql-ssh-keyfile: + image: 'ghcr.io/dbgate/mysql-ssh-keyfile:latest' diff --git a/e2e-tests/containers/mysql-ssh-keyfile/Dockerfile b/e2e-tests/containers/mysql-ssh-keyfile/Dockerfile new file mode 100644 index 000000000..f81aedf9d --- /dev/null +++ b/e2e-tests/containers/mysql-ssh-keyfile/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:22.04 + +RUN apt-get update && \ + apt-get install -y openssh-server mysql-server && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN mkdir /var/run/sshd + +RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \ + sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin without-password/' /etc/ssh/sshd_config + +RUN mkdir -p /root/.ssh && chmod 700 /root/.ssh + +COPY mykey.pub /root/.ssh/authorized_keys + +RUN chmod 600 /root/.ssh/authorized_keys + +RUN service mysql start && \ + mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;" && \ + service mysql stop + +EXPOSE 22 + +COPY start.sh /start.sh +RUN chmod +x /start.sh + +CMD ["/start.sh"] \ No newline at end of file diff --git a/e2e-tests/containers/mysql-ssh-keyfile/mykey.pub b/e2e-tests/containers/mysql-ssh-keyfile/mykey.pub new file mode 100644 index 000000000..f5a025dba --- /dev/null +++ b/e2e-tests/containers/mysql-ssh-keyfile/mykey.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQClN/tu+X7PVYpMBEQDwxtyG0oNuKqcJdBud/vBUalyYSpliXO30BsBusjCyAh8ENVllmGTRaTcqAUPg/vwekaAMFZu9EXmQsdhPEWSxrkPkXXKsWrZVPtRV8l3wuSy/VLxbXRLujTpIEE5Au88e2CFN3zzuhg9rvsXE0cGjyYe9Cw3Ub+Yo6rbnESI7F+Jun29/wD8RF+kRL5cr7BrDoOBQf4yGexrTbUPMZ4b8QlGfXUwZhn6LlWVoWq7BMRelnfIAkuC+LwavvnkMqXMITmGa7sG0zOXByW7iGdP6TWVq9Ks+yCZoAA9ncAkgkpwoNrOSXR6dM5UjrsfJ2Na332QfxK8MmC+nBiyXIKUaQY2G8Wfm93GQqDc+349JFwH4ZjgYWJlTd/UfRCn4j30jxe1RKnjt5xGqb3L+j8JfuSPcojWRekg/K8GxBmaMCcEdqjIFD4MxeIUW3bGHd30wnuZkTPWlSmAGrO0fZXj0s1fmOBME05TjE8jsyKPmb2Kd3JdtCuTrjOgrYiHaMfcv/VqVhIv2kCKjnvhAxCqfGfKxxgcerItbrsfFAvYak1kwZelBquHOK6SkMH5WhDyhvGY+ZF0Wbg3CpynlpJVu4E3LSglYNbkIWqS3pAf+hqJtXl9SHCLgKFUcCIz0ZNQkT+PtUlR3klp6qoPXUWrcL3pdQ== test \ No newline at end of file diff --git a/e2e-tests/mysql-ssh/start.sh b/e2e-tests/containers/mysql-ssh-keyfile/start.sh similarity index 100% rename from e2e-tests/mysql-ssh/start.sh rename to e2e-tests/containers/mysql-ssh-keyfile/start.sh diff --git a/e2e-tests/mysql-ssh/Dockerfile b/e2e-tests/containers/mysql-ssh-login/Dockerfile similarity index 94% rename from e2e-tests/mysql-ssh/Dockerfile rename to e2e-tests/containers/mysql-ssh-login/Dockerfile index 6744b701d..b62bae980 100644 --- a/e2e-tests/mysql-ssh/Dockerfile +++ b/e2e-tests/containers/mysql-ssh-login/Dockerfile @@ -14,9 +14,10 @@ RUN service mysql start && \ mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;" && \ service mysql stop -EXPOSE 22 3306 +EXPOSE 22 COPY start.sh /start.sh + RUN chmod +x /start.sh -CMD ["/start.sh"] \ No newline at end of file +CMD ["/start.sh"] diff --git a/e2e-tests/containers/mysql-ssh-login/start.sh b/e2e-tests/containers/mysql-ssh-login/start.sh new file mode 100644 index 000000000..ef09d973e --- /dev/null +++ b/e2e-tests/containers/mysql-ssh-login/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +service ssh start + +service mysql start + +tail -f /dev/null diff --git a/e2e-tests/cypress/e2e/connect.cy.js b/e2e-tests/cypress/e2e/connect.cy.js index 3869063f7..556241367 100644 --- a/e2e-tests/cypress/e2e/connect.cy.js +++ b/e2e-tests/cypress/e2e/connect.cy.js @@ -1,3 +1,5 @@ +const path = require('path'); + describe('Initialization', () => { it('successfully loads', () => { cy.visit('http://localhost:3000'); @@ -23,45 +25,71 @@ describe('Initialization', () => { cy.contains('performance_schema'); }); - // it('SSH connection', () => { - // const runOnCI = Cypress.env('runOnCI'); + it('SSH connection - user + password', () => { + const runOnCI = Cypress.env('runOnCI'); - // cy.get('body') - // .trigger('keydown', { - // key: 'F1', - // code: 'F1', - // which: 112, - // keyCode: 112, - // bubbles: true, - // }) - // .trigger('keyup', { - // key: 'F1', - // code: 'F1', - // which: 112, - // keyCode: 112, - // bubbles: true, - // }); - // cy.get('body').type('Close all'); - // cy.get('body').type('{enter}'); + cy.visit('http://localhost:3000'); + cy.contains('Connections'); - // cy.visit('http://localhost:3000'); - // cy.get('[data-testid=ConnectionList_buttonNewConnection]').click(); - // cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL'); - // cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root'); - // cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('root'); + cy.realPress('F1'); + cy.realType('Close all'); + cy.realPress('Enter'); - // cy.get('[data-testid=ConnectionSshTunnelFields_sshLogin]').clear().type('root'); - // cy.get('[data-testid=ConnectionSshTunnelFields_sshPassword]').clear().type('root'); - // if (runOnCI) { - // cy.get('[data-testid=ConnectionSshTunnelFields_sshHost]').clear().type('mysql-ssh'); - // } else { - // cy.get('[data-testid=ConnectionSshTunnelFields_sshPort]').clear().type('16006'); - // } - // cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-ssh-1'); - // cy.get('[data-testid=ConnectionTab_buttonSave]').click(); - // cy.get('[data-testid=ConnectionTab_buttonConnect]').click(); - // cy.contains('performance_schema'); - // }); + cy.get('[data-testid=ConnectionList_buttonNewConnection]').click(); + cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL'); + cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root'); + cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('root'); + + cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-ssh-1'); + + cy.get('[data-testid=ConnectionTab_tabSshTunnel]').click(); + cy.get('[data-testid=ConnectionSshTunnelFields_useSshTunnel]').check(); + cy.get('[data-testid=ConnectionSshTunnelFields_sshLogin]').clear().type('root'); + cy.get('[data-testid=ConnectionSshTunnelFields_sshPassword]').clear().type('root'); + if (runOnCI) { + cy.get('[data-testid=ConnectionSshTunnelFields_sshHost]').clear().type('mysql-ssh-login'); + } else { + cy.get('[data-testid=ConnectionSshTunnelFields_sshPort]').clear().type('16006'); + } + cy.get('[data-testid=ConnectionTab_buttonSave]').click(); + cy.get('[data-testid=ConnectionTab_buttonConnect]').click(); + cy.contains('performance_schema'); + }); + + it('SSH connection - keyfile', () => { + const runOnCI = Cypress.env('runOnCI'); + + cy.visit('http://localhost:3000'); + cy.contains('Connections'); + + cy.realPress('F1'); + cy.realType('Close all'); + cy.realPress('Enter'); + + cy.get('[data-testid=ConnectionList_buttonNewConnection]').click(); + cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL'); + cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root'); + cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('root'); + + cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-ssh-2'); + + cy.get('[data-testid=ConnectionTab_tabSshTunnel]').click(); + cy.get('[data-testid=ConnectionSshTunnelFields_useSshTunnel]').check(); + cy.get('[data-testid=ConnectionSshTunnelFields_sshMode]').select('Key file'); + cy.get('[data-testid=ConnectionSshTunnelFields_sshLogin]').clear() + cy.get('[data-testid=ConnectionSshTunnelFields_sshLogin]').type('root'); + cy.get('[data-testid=ConnectionSshTunnelFields_sshKeyfile]') + .clear() + .type(path.join(Cypress.config('fileServerFolder'), 'cypress', 'e2e', 'mykey')); + if (runOnCI) { + cy.get('[data-testid=ConnectionSshTunnelFields_sshHost]').clear().type('mysql-ssh-keyfile'); + } else { + cy.get('[data-testid=ConnectionSshTunnelFields_sshPort]').clear().type('16008'); + } + cy.get('[data-testid=ConnectionTab_buttonSave]').click(); + cy.get('[data-testid=ConnectionTab_buttonConnect]').click(); + cy.contains('performance_schema'); + }); // it('import chinook DB', () => { // cy.visit('http://localhost:3000'); diff --git a/e2e-tests/cypress/e2e/mykey b/e2e-tests/cypress/e2e/mykey new file mode 100644 index 000000000..85823480c --- /dev/null +++ b/e2e-tests/cypress/e2e/mykey @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +jIy5WYf/zrVHAAAABHRlc3QBAgMEBQY= +-----END OPENSSH PRIVATE KEY----- \ No newline at end of file diff --git a/e2e-tests/cypress/support/e2e.js b/e2e-tests/cypress/support/e2e.js index 0e7290a13..8d19da9bd 100644 --- a/e2e-tests/cypress/support/e2e.js +++ b/e2e-tests/cypress/support/e2e.js @@ -17,4 +17,6 @@ import './commands' // Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file +// require('./commands') + +import "cypress-real-events"; \ No newline at end of file diff --git a/e2e-tests/docker-compose.yaml b/e2e-tests/docker-compose.yaml index e983cdd4a..93b18e4c2 100644 --- a/e2e-tests/docker-compose.yaml +++ b/e2e-tests/docker-compose.yaml @@ -17,9 +17,16 @@ services: environment: - MYSQL_ROOT_PASSWORD=Pwd2020Db - mysql-ssh: - build: mysql-ssh + mysql-ssh-login: + build: containers/mysql-ssh-login restart: always ports: - 16005:3306 - 16006:22 + + mysql-ssh-keyfile: + build: containers/mysql-ssh-keyfile + restart: always + ports: + - 16007:3306 + - 16008:22 diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 0f5eff1b0..4606e5c5d 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -7,6 +7,7 @@ "axios": "^1.7.9", "cross-env": "^7.0.3", "cypress": "^13.16.1", + "cypress-real-events": "^1.13.0", "kill-port": "^2.0.1", "start-server-and-test": "^2.0.8" }, diff --git a/e2e-tests/yarn.lock b/e2e-tests/yarn.lock index 1a31e6637..1c8ab226e 100644 --- a/e2e-tests/yarn.lock +++ b/e2e-tests/yarn.lock @@ -350,6 +350,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +cypress-real-events@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.13.0.tgz#6b7cd32dcac172db1493608f97a2576c7d0bd5af" + integrity sha512-LoejtK+dyZ1jaT8wGT5oASTPfsNV8/ClRp99ruN60oPj8cBJYod80iJDyNwfPAu4GCxTXOhhAv9FO65Hpwt6Hg== + cypress@^13.16.1: version "13.16.1" resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.16.1.tgz#82e776f6ad2037ccce6b6feabed768615c476258" diff --git a/integration-tests/package.json b/integration-tests/package.json index 3eaa12e85..5e8bb394d 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -11,14 +11,11 @@ "scripts": { "wait:local": "cross-env DEVMODE=1 LOCALTEST=1 node wait.js", "wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js", - "test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest", + "test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest --testTimeout=5000", "test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js", - "test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit", + "test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit --testTimeout=10000", "run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local" }, - "jest": { - "testTimeout": 5000 - }, "devDependencies": { "cross-env": "^7.0.3", "jest": "^27.0.1", diff --git a/packages/api/package.json b/packages/api/package.json index f048a9b8e..9e899d971 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -60,7 +60,7 @@ "rimraf": "^3.0.0", "semver": "^7.6.3", "simple-encryptor": "^4.0.0", - "ssh2": "^1.11.0", + "ssh2": "^1.16.0", "stream-json": "^1.8.0", "tar": "^6.0.5" }, diff --git a/packages/api/src/proc/serverConnectionProcess.js b/packages/api/src/proc/serverConnectionProcess.js index 3f32c0c5e..327c4f44f 100644 --- a/packages/api/src/proc/serverConnectionProcess.js +++ b/packages/api/src/proc/serverConnectionProcess.js @@ -166,7 +166,9 @@ function start() { if (time - lastPing > 40 * 1000) { logger.info('Server connection not alive, exiting'); const driver = requireEngineDriver(storedConnection); - await driver.close(dbhan); + if (dbhan) { + await driver.close(dbhan); + } process.exit(0); } }, 10 * 1000); diff --git a/packages/api/src/proc/sshForwardProcess.js b/packages/api/src/proc/sshForwardProcess.js index 63e14bed1..870c00905 100644 --- a/packages/api/src/proc/sshForwardProcess.js +++ b/packages/api/src/proc/sshForwardProcess.js @@ -7,7 +7,7 @@ const { getLogger, extractErrorLogData, extractErrorMessage } = require('dbgate- const logger = getLogger('sshProcess'); -async function getSshConnection(connection) { +async function getSshConnection(connection, tunnelConfig) { const sshConfig = { endHost: connection.sshHost || '', endPort: connection.sshPort || 22, @@ -23,6 +23,7 @@ async function getSshConnection(connection) { : undefined, skipAutoPrivateKey: true, noReadline: true, + bindHost: tunnelConfig.fromHost, }; const sshConn = new SSHConnection(sshConfig); @@ -31,7 +32,7 @@ async function getSshConnection(connection) { async function handleStart({ connection, tunnelConfig }) { try { - const sshConn = await getSshConnection(connection); + const sshConn = await getSshConnection(connection, tunnelConfig); await sshConn.forward(tunnelConfig); process.send({ diff --git a/packages/api/src/utility/SSHConnection.js b/packages/api/src/utility/SSHConnection.js index 8e46eacf4..6cd4c4db0 100644 --- a/packages/api/src/utility/SSHConnection.js +++ b/packages/api/src/utility/SSHConnection.js @@ -130,7 +130,7 @@ class SSHConnection { const connectionToBastion = await this.connect(bastionHost); return new Promise((resolve, reject) => { connectionToBastion.forwardOut( - '127.0.0.1', + this.options.bindHost, 22, this.options.endHost, this.options.endPort || 22, @@ -228,9 +228,9 @@ class SSHConnection { options.toPort ); connection.forwardOut( - 'localhost', + this.options.bindHost, options.fromPort, - options.toHost || 'localhost', + options.toHost || this.options.bindHost, options.toPort, (error, stream) => { if (error) { @@ -241,7 +241,7 @@ class SSHConnection { } ); }) - .listen(options.fromPort, 'localhost', () => { + .listen(options.fromPort, this.options.bindHost, () => { return resolve(); }); }); diff --git a/packages/api/src/utility/connectUtility.js b/packages/api/src/utility/connectUtility.js index a8485261a..45d3f5a42 100644 --- a/packages/api/src/utility/connectUtility.js +++ b/packages/api/src/utility/connectUtility.js @@ -63,7 +63,7 @@ async function connectUtility(driver, storedConnection, connectionMode, addition throw new Error(tunnel.message); } - connection.server = 'localhost'; + connection.server = tunnel.localHost; connection.port = tunnel.localPort; } diff --git a/packages/api/src/utility/sshTunnel.js b/packages/api/src/utility/sshTunnel.js index 85d7fb8e3..da19aa1eb 100644 --- a/packages/api/src/utility/sshTunnel.js +++ b/packages/api/src/utility/sshTunnel.js @@ -43,33 +43,44 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) { logger.error(extractErrorLogData(err), 'Error connecting SSH'); } return new Promise((resolve, reject) => { + let promiseHandled = false; subprocess.on('message', resp => { // @ts-ignore const { msgtype, errorMessage } = resp; if (msgtype == 'connected') { resolve(subprocess); + promiseHandled = true; } if (msgtype == 'error') { reject(new Error(errorMessage)); + promiseHandled = true; } }); subprocess.on('exit', code => { logger.info('SSH forward process exited'); delete sshTunnelCache[tunnelCacheKey]; + if (!promiseHandled) { + reject(new Error('SSH forward process exited, try to change "Local host address for SSH connections" in Settings/Connections')); + } }); }); } async function getSshTunnel(connection) { + const config = require('../controllers/config'); + const tunnelCacheKey = stableStringify(_.pick(connection, TUNNEL_FIELDS)); + const globalSettings = await config.getSettings(); return await lock.acquire(tunnelCacheKey, async () => { if (sshTunnelCache[tunnelCacheKey]) return sshTunnelCache[tunnelCacheKey]; const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 }); + const localHost = globalSettings?.['connection.sshBindHost'] || '127.0.0.1'; // workaround for `getPortPromise` not releasing the port quickly enough await new Promise(resolve => setTimeout(resolve, 500)); const tunnelConfig = { fromPort: localPort, + fromHost: localHost, toPort: connection.port, toHost: connection.server, }; @@ -87,6 +98,7 @@ async function getSshTunnel(connection) { sshTunnelCache[tunnelCacheKey] = { state: 'ok', localPort, + localHost, subprocess, }; return sshTunnelCache[tunnelCacheKey]; diff --git a/packages/web/src/elements/TabControl.svelte b/packages/web/src/elements/TabControl.svelte index a8aa06984..b95afae33 100644 --- a/packages/web/src/elements/TabControl.svelte +++ b/packages/web/src/elements/TabControl.svelte @@ -7,6 +7,7 @@ slot?: number; component?: any; props?: any; + testid?: string; } export let tabs: TabDef[]; @@ -27,7 +28,7 @@
{#each _.compact(tabs) as tab, index} -
(value = index)}> +
(value = index)} data-testid={tab.testid}> {tab.label} diff --git a/packages/web/src/settings/SettingsModal.svelte b/packages/web/src/settings/SettingsModal.svelte index b19e8efef..41b24458a 100644 --- a/packages/web/src/settings/SettingsModal.svelte +++ b/packages/web/src/settings/SettingsModal.svelte @@ -202,6 +202,17 @@ ORDER BY defaultValue="30" disabled={values['connection.autoRefresh'] === false} /> +
Query sessions
diff --git a/workflow-templates/build-test-containers.yaml b/workflow-templates/build-test-containers.yaml new file mode 100644 index 000000000..741880cc2 --- /dev/null +++ b/workflow-templates/build-test-containers.yaml @@ -0,0 +1,42 @@ +name: Build test containers + +on: + push: + branches: + - master + - develop + - 'feature/**' + paths: + - e2e-tests/containers/** + +jobs: + build-docker-ssh: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Build mysql-ssh-login image + run: | + docker build -t dbgate/mysql-ssh-login:latest e2e-tests/containers/mysql-ssh-login + + - name: Build mysql-ssh-keyfile image + run: | + docker build -t dbgate/mysql-ssh-keyfile:latest e2e-tests/containers/mysql-ssh-keyfile + + - name: Log in to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push mysql-ssh-login to GHCR + run: | + docker tag dbgate/mysql-ssh-login:latest ghcr.io/dbgate/mysql-ssh-login:latest + docker push ghcr.io/dbgate/mysql-ssh-login:latest + + - name: Push mysql-ssh-keyfile to GHCR + run: | + docker tag dbgate/mysql-ssh-keyfile:latest ghcr.io/dbgate/mysql-ssh-keyfile:latest + docker push ghcr.io/dbgate/mysql-ssh-keyfile:latest diff --git a/workflow-templates/run-tests.yaml b/workflow-templates/run-tests.yaml index c813d251e..b8eeaeb74 100644 --- a/workflow-templates/run-tests.yaml +++ b/workflow-templates/run-tests.yaml @@ -1,4 +1,5 @@ name: Run tests + on: push: branches: @@ -7,7 +8,7 @@ on: - 'feature/**' jobs: - test-runner: + test: runs-on: ubuntu-latest container: node:18 @@ -16,50 +17,60 @@ jobs: run: | apt-get update apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 - - uses: actions/checkout@v2 + + - uses: actions/checkout@v3 with: fetch-depth: 1 + - name: yarn install run: | yarn install + - name: Build packer dist for cypress run: | yarn prepare:packer + - name: yarn install cypress run: | cd e2e-tests yarn install + - name: Run Cypress tests run: | cd e2e-tests yarn test:ci + - name: Integration tests run: | cd integration-tests yarn test:ci -# yarn wait:ci + - name: Filter parser tests if: always() run: | cd packages/filterparser yarn test:ci + - name: Datalib (perspective) tests if: always() run: | cd packages/datalib yarn test:ci + - uses: tanmen/jest-reporter@v1 if: always() with: github-token: ${{ secrets.GITHUB_TOKEN }} result-file: integration-tests/result.json action-name: Integration tests + - uses: tanmen/jest-reporter@v1 if: always() with: github-token: ${{ secrets.GITHUB_TOKEN }} result-file: packages/filterparser/result.json action-name: Filter parser test results + - uses: tanmen/jest-reporter@v1 if: always() with: @@ -99,6 +110,9 @@ jobs: image: gvenzl/oracle-xe:21-slim env: ORACLE_PASSWORD: Pwd2020Db + + mysql-ssh-login: + image: ghcr.io/dbgate/mysql-ssh-login:latest - # cockroachdb: - # image: cockroachdb/cockroach + mysql-ssh-keyfile: + image: ghcr.io/dbgate/mysql-ssh-keyfile:latest diff --git a/yarn.lock b/yarn.lock index 2e843e956..d9955920a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4051,7 +4051,7 @@ cors@^2.8.5: object-assign "^4" vary "^1" -cpu-features@~0.0.9: +cpu-features@~0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== @@ -8741,11 +8741,16 @@ named-placeholders@^1.1.3: dependencies: lru-cache "^7.14.1" -nan@^2.12.1, nan@^2.18.0, nan@^2.19.0: +nan@^2.12.1, nan@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== +nan@^2.20.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" + integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -10878,16 +10883,16 @@ ssf@~0.11.2: dependencies: frac "~1.1.2" -ssh2@^1.11.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b" - integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw== +ssh2@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.16.0.tgz#79221d40cbf4d03d07fe881149de0a9de928c9f0" + integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg== dependencies: asn1 "^0.2.6" bcrypt-pbkdf "^1.0.2" optionalDependencies: - cpu-features "~0.0.9" - nan "^2.18.0" + cpu-features "~0.0.10" + nan "^2.20.0" sshpk@^1.7.0: version "1.18.0"