123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- /**
- * 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 */
- /* eslint-disable n/no-extraneous-import */
- import Docker from 'dockerode'
- import waitOn from 'wait-on'
- 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_${basename(process.cwd()).replace(' ', '')}`
- const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server'
- /**
- * Start the testing container
- *
- * @param {string} branch the branch of your current work
- */
- export const startNextcloud = async function(branch: string = getCurrentGitBranch()): Promise<any> {
- try {
- 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
- }
- // https://github.com/apocas/dockerode/issues/357
- docker.modem.followProgress(stream, onFinished)
- function onFinished(err) {
- if (!err) {
- resolve(true)
- return
- }
- reject(err)
- }
- }))
- console.log('└─ Done')
- } catch (e) {
- console.log('└─ Failed to pull images')
- throw e
- }
- // Remove old container if exists
- console.log('\nChecking running containers... 🔍')
- try {
- const oldContainer = docker.getContainer(CONTAINER_NAME)
- const oldContainerData = await oldContainer.inspect()
- if (oldContainerData) {
- console.log('├─ Existing running container found')
- console.log('├─ Removing... ⏳')
- // Forcing any remnants to be removed just in case
- await oldContainer.remove({ force: true })
- console.log('└─ Done')
- }
- } catch (error) {
- console.log('└─ None found!')
- }
- // Starting container
- console.log('\nStarting Nextcloud container... 🚀')
- console.log(`├─ Using branch '${branch}'`)
- const container = await docker.createContainer({
- Image: SERVER_IMAGE,
- name: CONTAINER_NAME,
- HostConfig: {
- Mounts: [{
- Target: '/var/www/html/data',
- Source: '',
- Type: 'tmpfs',
- ReadOnly: false,
- }],
- },
- 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')
- // Init Nextcloud
- // await runExec(container, ['initnc.sh'], true, 'root')
- // Get container's IP
- const ip = await getContainerIP(container)
- console.log(`├─ Nextcloud container's IP is ${ip} 🌏`)
- return ip
- } catch (err) {
- console.log('└─ Unable to start the container 🛑')
- console.log('\n', err, '\n')
- stopNextcloud()
- throw new Error('Unable to start the container')
- }
- }
- /**
- * Configure Nextcloud
- */
- export const configureNextcloud = async function() {
- console.log('\nConfiguring nextcloud...')
- const container = docker.getContainer(CONTAINER_NAME)
- await runExec(container, ['php', 'occ', '--version'], true)
- // Be consistent for screenshots
- await runExec(container, ['php', 'occ', 'config:system:set', 'default_language', '--value', 'en'], true)
- await runExec(container, ['php', 'occ', 'config:system:set', 'force_language', '--value', 'en'], true)
- 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)
- // 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 🎉')
- }
- /**
- * Applying local changes to the container
- * Only triggered if we're not in CI. Otherwise the
- * continuous-integration-shallow-server image will
- * already fetch the proper branch.
- */
- export const applyChangesToNextcloud = async function() {
- console.log('\nApply local changes to nextcloud...')
- const htmlPath = '/var/www/html'
- const folderPaths = [
- './3rdparty',
- './apps',
- './core',
- './dist',
- './lib',
- './ocs',
- './ocs-provider',
- './resources',
- './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 = createTar({ gzip: false }, folderPaths)
- await container.putArchive(serverTar, {
- path: htmlPath,
- })
- // Making sure we have the proper permissions
- await runExec(container, ['chown', '-R', 'www-data:www-data', htmlPath], false, 'root')
- console.log('└─ Changes applied successfully 🎉')
- }
- /**
- * Force stop the testing container
- */
- export const stopNextcloud = async function() {
- try {
- const container = docker.getContainer(CONTAINER_NAME)
- console.log('Stopping Nextcloud container...')
- container.remove({ force: true })
- console.log('└─ Nextcloud container removed 🥀')
- } catch (err) {
- console.log(err)
- }
- }
- /**
- * Get the testing container's IP
- *
- * @param {Docker.Container} container the container to get the IP from
- */
- export const getContainerIP = async function(
- container = docker.getContainer(CONTAINER_NAME),
- ): Promise<string> {
- let ip = ''
- let tries = 0
- while (ip === '' && tries < 10) {
- tries++
- await container.inspect(function(err, data) {
- if (err) {
- throw err
- }
- ip = data?.NetworkSettings?.IPAddress || ''
- })
- if (ip !== '') {
- break
- }
- await sleep(1000 * tries)
- }
- return ip
- }
- // Would be simpler to start the container from cypress.config.ts,
- // but when checking out different branches, it can take a few seconds
- // Until we can properly configure the baseUrl retry intervals,
- // We need to make sure the server is already running before cypress
- // 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`],
- // 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')
- }
- const runExec = async function(
- container: Docker.Container,
- command: string[],
- verbose = false,
- 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)
- }
- if (stream) {
- stream.setEncoding('utf-8')
- stream.on('data', str => {
- 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(output))
- }
- })
- })
- }
- const sleep = function(milliseconds: number) {
- return new Promise((resolve) => setTimeout(resolve, milliseconds))
- }
- const getCurrentGitBranch = function() {
- return execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'master'
- }
|