diff --git a/index.js b/index.js index b444a9b6..f2dad0c4 100644 --- a/index.js +++ b/index.js @@ -44,6 +44,7 @@ class ServerlessPythonRequirements { ? 'python.exe' : this.serverless.service.provider.runtime || 'python', dockerizePip: false, + dockerInDockerPath: false, dockerSsh: false, dockerImage: null, dockerFile: null, diff --git a/lib/docker.js b/lib/docker.js index 46bbe028..7b4cd940 100644 --- a/lib/docker.js +++ b/lib/docker.js @@ -2,6 +2,35 @@ const { spawnSync } = require('child_process'); const isWsl = require('is-wsl'); const fse = require('fs-extra'); const path = require('path'); +let cachedContainerId = false; + +/** + * A helper function to determine if we are inside Docker + * @return {boolean} if we are inside docker or not + */ +function areInsideDocker() { + return fse.existsSync('/.dockerenv') +} + +/** + * A helper function to determine the container id when inside docker, caching results + * @return {string} sha container id, or returns false on error/failure + */ +function getContainerIdFromWithinContainer() { + if (cachedContainerId){ return cachedContainerId; } + try { + // First, detect our container id by using /proc/self/cgroup trick + cgroup = fse.readFileSync('/proc/self/cgroup', { encoding: 'utf-8' }); + // Parse the stdout and extract the systemd line, and the the last tokenized item in that line + pos1 = cgroup.toString('utf8').indexOf("systemd"); + cachedContainerId = cgroup.toString('utf8').substr(pos1, cgroup.toString('utf8').indexOf("\n", pos1)-pos1).split('/').pop(); + return cachedContainerId + } catch (e) { + console.error("Docker-in-Docker: Unable to get container id from within container, cgroup might not be present"); + console.error(e); + return false; + } +} /** * Helper function to run a docker command @@ -98,12 +127,38 @@ function tryBindPath(serverless, bindPath, testFile) { * Get bind path depending on os platform * @param {object} serverless * @param {string} servicePath + * @param {Object} options serverless cli options to alter logic * @return {string} The bind path. */ -function getBindPath(serverless, servicePath) { +function getBindPath(serverless, servicePath, options) { + serverless.cli.log(`Docker-In-Docker: servicePath: ${servicePath}`); // Determine bind path if (process.platform !== 'win32' && !isWsl) { - return servicePath; + // Detect if we're trying to do docker-in-docker + if (options.dockerizePip && areInsideDocker()) { + serverless.cli.log(`Docker-In-Docker: We have detected an docker-in-docker configuration. NOTE: This feature is in beta for this plugin, verbose output for now`); + // Check if we want to specify our own path... + if (options.dockerInDockerPath) { + serverless.cli.log(`Docker-In-Docker: User-specified docker-in-docker current working directory path on host: ${options.dockerInDockerPath}`); + return options.dockerInDockerPath + "/.serverless" + } + // Get our container id from within docker-in-docker + let containerId = getContainerIdFromWithinContainer(); + if (!containerId) { + console.log(`Docker-In-Docker: Unable to get container ID, falling back to local path. WARNING this will probably not work...`); + return servicePath; + } + serverless.cli.log(`Docker-In-Docker: Detected container: ${containerId}`); + // Inspect this container to get the root volume mount + data = dockerCommand(['inspect', containerId]).output[1]; + result = JSON.parse(data)[0]['GraphDriver']['Data']['MergedDir']; + serverless.cli.log(`Docker-In-Docker: Found docker-in-docker root volume mounted from: ` + JSON.stringify(result)) + serverless.cli.log(`Docker-In-Docker: Using: ` + result + servicePath); + return result + servicePath + } else { + // Check if we're inside docker + return servicePath; + } } // test docker is available @@ -173,4 +228,4 @@ function getDockerUid(bindPath) { return ps.stdout.trim(); } -module.exports = { buildImage, getBindPath, getDockerUid }; +module.exports = { buildImage, getBindPath, getDockerUid, areInsideDocker }; diff --git a/lib/pip.js b/lib/pip.js index 14864794..4fe4a808 100644 --- a/lib/pip.js +++ b/lib/pip.js @@ -195,7 +195,7 @@ function installRequirements(targetFolder, serverless, options) { serverless.cli.log(`Docker Image: ${dockerImage}`); // Prepare bind path depending on os platform - const bindPath = dockerPathForWin(getBindPath(serverless, targetFolder)); + let bindPath = dockerPathForWin(getBindPath(serverless, targetFolder, options)); dockerCmd.push('docker', 'run', '--rm', '-v', `${bindPath}:/var/task:z`); if (options.dockerSsh) { @@ -227,8 +227,7 @@ function installRequirements(targetFolder, serverless, options) { fse.closeSync( fse.openSync(path.join(downloadCacheDir, 'requirements.txt'), 'w') ); - const windowsized = getBindPath(serverless, downloadCacheDir); - // And now push it to a volume mount and to pip... + let windowsized = getBindPath(serverless, downloadCacheDir, options); dockerCmd.push('-v', `${windowsized}:${dockerDownloadCacheDir}:z`); pipCmd.push('--cache-dir', dockerDownloadCacheDir); } diff --git a/lib/shared.js b/lib/shared.js index 34f61eb2..6c59a8fe 100644 --- a/lib/shared.js +++ b/lib/shared.js @@ -4,6 +4,8 @@ const glob = require('glob-all'); const path = require('path'); const fse = require('fs-extra'); const sha256File = require('sha256-file'); +const { areInsideDocker } = require('./docker'); + /** * This helper will check if we're using static cache and have max @@ -72,6 +74,11 @@ function getRequirementsWorkingPath( return path.join(getUserCachePath(options), subfolder); } + // If we're in docker-in-docker we must use a path that can be mounted in a sub-docker container, so we'll put a folder at the root + if (options.dockerizePip && areInsideDocker()) { + return path.join('/', '_slspyreqs'); + } + // If we don't want to use the static cache, then fallback to the way things used to work return path.join(requirementsTxtDirectory, 'requirements'); }