aboutsummaryrefslogtreecommitdiffstats
path: root/cypress/dockerNode.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cypress/dockerNode.ts')
-rw-r--r--cypress/dockerNode.ts216
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
+}