diff options
Diffstat (limited to 'cypress/dockerNode.ts')
-rw-r--r-- | cypress/dockerNode.ts | 216 |
1 files changed, 160 insertions, 56 deletions
diff --git a/cypress/dockerNode.ts b/cypress/dockerNode.ts index 58544118024..6e21b33101c 100644 --- a/cypress/dockerNode.ts +++ b/cypress/dockerNode.ts @@ -1,23 +1,6 @@ /** - * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ /* eslint-disable no-console */ /* eslint-disable n/no-unpublished-import */ @@ -25,12 +8,14 @@ import Docker from 'dockerode' import waitOn from 'wait-on' -import tar from 'tar' +import { c as createTar } from 'tar' +import path, { basename } from 'path' import { execSync } from 'child_process' +import { existsSync } from 'fs' export const docker = new Docker() -const CONTAINER_NAME = 'nextcloud-cypress-tests-server' +const CONTAINER_NAME = `nextcloud-cypress-tests_${basename(process.cwd()).replace(' ', '')}` const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server' /** @@ -41,28 +26,38 @@ const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server' export const startNextcloud = async function(branch: string = getCurrentGitBranch()): Promise<any> { try { - // Pulling images - console.log('\nPulling images... ⏳') - await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => { - if (err) { - reject(err) - } - // https://github.com/apocas/dockerode/issues/357 - docker.modem.followProgress(stream, onFinished) - - /** - * - * @param err - */ - function onFinished(err) { - if (!err) { - resolve(true) + try { + // Pulling images + console.log('\nPulling images... ⏳') + await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => { + if (err) { + reject(err) + } + if (stream === null) { + reject(new Error('Could not connect to docker, ensure docker is running.')) return } - reject(err) - } - })) - console.log('└─ Done') + + // https://github.com/apocas/dockerode/issues/357 + docker.modem.followProgress(stream, onFinished) + + function onFinished(err) { + if (!err) { + resolve(true) + return + } + reject(err) + } + })) + + const digest = await (await docker.getImage(SERVER_IMAGE).inspect()).RepoDigests.at(0) + const sha = digest?.split('@').at(1) + console.log('├─ Using image ' + sha) + console.log('└─ Done') + } catch (e) { + console.log('└─ Failed to pull images') + throw e + } // Remove old container if exists console.log('\nChecking running containers... 🔍') @@ -87,14 +82,33 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc Image: SERVER_IMAGE, name: CONTAINER_NAME, HostConfig: { - Binds: [], + Mounts: [{ + Target: '/var/www/html/data', + Source: '', + Type: 'tmpfs', + ReadOnly: false, + }], + PortBindings: { + '80/tcp': [{ + HostIP: '0.0.0.0', + HostPort: '8083', + }], + }, + // If running the setup tests, let's bind to host + // to communicate with the github actions DB services + NetworkMode: process.env.SETUP_TESTING === 'true' ? await getGithubNetwork() : undefined, }, Env: [ `BRANCH=${branch}`, + 'APCU=1', ], }) await container.start() + // Set proper permissions for the data folder + await runExec(container, ['chown', '-R', 'www-data:www-data', '/var/www/html/data'], false, 'root') + await runExec(container, ['chmod', '0770', '/var/www/html/data'], false, 'root') + // Get container's IP const ip = await getContainerIP(container) @@ -102,7 +116,7 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc return ip } catch (err) { console.log('└─ Unable to start the container 🛑') - console.log(err) + console.log('\n', err, '\n') stopNextcloud() throw new Error('Unable to start the container') } @@ -122,7 +136,29 @@ export const configureNextcloud = async function() { await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true) await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true) await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true) - await runExec(container, ['php', 'occ', 'config:system:set', 'versions_retention_obligation', '--value', '0, 0'], true) + + // Speed up test and make them less flaky. If a cron execution is needed, it can be triggered manually. + await runExec(container, ['php', 'occ', 'background:cron'], true) + + // Checking apcu + const distributed = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.distributed']) + const local = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.local']) + const hashing = await runExec(container, ['php', 'occ', 'config:system:get', 'hashing_default_password']) + + console.log('├─ Checking APCu configuration... 👀') + if (!distributed.trim().includes('Memcache\\APCu') + || !local.trim().includes('Memcache\\APCu') + || !hashing.trim().includes('true')) { + console.log('└─ APCu is not properly configured 🛑') + throw new Error('APCu is not properly configured') + } + console.log('│ └─ OK !') + + // Saving DB state + console.log('├─ Creating init DB snapshot...') + await runExec(container, ['cp', '/var/www/html/data/owncloud.db', '/var/www/html/data/owncloud.db-init'], true) + console.log('├─ Creating init data backup...') + await runExec(container, ['tar', 'cf', 'data-init.tar', 'admin'], true, undefined, '/var/www/html/data') console.log('└─ Nextcloud is now ready to use 🎉') } @@ -135,19 +171,47 @@ export const configureNextcloud = async function() { */ export const applyChangesToNextcloud = async function() { console.log('\nApply local changes to nextcloud...') - const container = docker.getContainer(CONTAINER_NAME) const htmlPath = '/var/www/html' const folderPaths = [ + './3rdparty', './apps', './core', './dist', './lib', './ocs', - ] + './ocs-provider', + './resources', + './tests', + './console.php', + './cron.php', + './index.php', + './occ', + './public.php', + './remote.php', + './status.php', + './version.php', + ].filter((folderPath) => { + const fullPath = path.resolve(__dirname, '..', folderPath) + + if (existsSync(fullPath)) { + console.log(`├─ Copying ${folderPath}`) + return true + } + return false + }) + + // Don't try to apply changes, when there are none. Otherwise we + // still execute the 'chown' command, which is not needed. + if (folderPaths.length === 0) { + console.log('└─ No local changes found to apply') + return + } + + const container = docker.getContainer(CONTAINER_NAME) // Tar-streaming the above folders into the container - const serverTar = tar.c({ gzip: false }, folderPaths) + const serverTar = createTar({ gzip: false }, folderPaths) await container.putArchive(serverTar, { path: htmlPath, }) @@ -178,18 +242,23 @@ export const stopNextcloud = async function() { * @param {Docker.Container} container the container to get the IP from */ export const getContainerIP = async function( - container = docker.getContainer(CONTAINER_NAME) + container = docker.getContainer(CONTAINER_NAME), ): Promise<string> { let ip = '' let tries = 0 while (ip === '' && tries < 10) { tries++ - await container.inspect(function(err, data) { + container.inspect(function(err, data) { if (err) { throw err } - ip = data?.NetworkSettings?.IPAddress || '' + + if (data?.HostConfig.PortBindings?.['80/tcp']?.[0]?.HostPort) { + ip = `localhost:${data.HostConfig.PortBindings['80/tcp'][0].HostPort}` + } else { + ip = data?.NetworkSettings?.IPAddress || '' + } }) if (ip !== '') { @@ -209,7 +278,15 @@ export const getContainerIP = async function( // https://github.com/cypress-io/cypress/issues/22676 export const waitOnNextcloud = async function(ip: string) { console.log('├─ Waiting for Nextcloud to be ready... ⏳') - await waitOn({ resources: [`http://${ip}/index.php`] }) + await waitOn({ + resources: [`http://${ip}/index.php`], + // wait for nextcloud to be up and return any non error status + validateStatus: (status) => status >= 200 && status < 400, + // timout in ms + timeout: 5 * 60 * 1000, + // timeout for a single HTTP request + httpTimeout: 60 * 1000, + }) console.log('└─ Done') } @@ -217,16 +294,19 @@ const runExec = async function( container: Docker.Container, command: string[], verbose = false, - user = 'www-data' -) { + user = 'www-data', + workdir?: string, +): Promise<string> { const exec = await container.exec({ Cmd: command, + WorkingDir: workdir, AttachStdout: true, AttachStderr: true, User: user, }) return new Promise((resolve, reject) => { + let output = '' exec.start({}, (err, stream) => { if (err) { reject(err) @@ -234,11 +314,17 @@ const runExec = async function( if (stream) { stream.setEncoding('utf-8') stream.on('data', str => { - if (verbose && str.trim() !== '') { - console.log(`├─ ${str.trim().replace(/\n/gi, '\n├─ ')}`) + str = str.trim() + // Remove non printable characters + .replace(/[^\x0A\x0D\x20-\x7E]+/g, '') + // Remove non alphanumeric leading characters + .replace(/^[^a-z]/gi, '') + output += str + if (verbose && str !== '') { + console.log(`├─ ${str.replace(/\n/gi, '\n├─ ')}`) } }) - stream.on('end', resolve) + stream.on('end', () => resolve(output)) } }) }) @@ -251,3 +337,21 @@ const sleep = function(milliseconds: number) { const getCurrentGitBranch = function() { return execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'master' } + +/** + * Get the network name of the github actions network + * This is used to connect to the database services + * started by github actions + */ +const getGithubNetwork = async function(): Promise<string|undefined> { + console.log('├─ Looking for github actions network... 🔍') + const networks = await docker.listNetworks() + const network = networks.find((network) => network.Name.startsWith('github_network')) + if (network) { + console.log('│ └─ Found github actions network: ' + network.Name) + return network.Name + } + + console.log('│ └─ No github actions network found') + return undefined +} |