Skip to content

Add dockerFile option #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ custom:
```
This must be the full image name and tag to use, including the runtime specific tag if applicable.

Alternatively, you can define your Docker image in your own Dockerfile and add the following to your `serverless.yml`:
```yaml
custom:
pythonRequirements:
dockerFile: ./path/to/Dockerfile
```
With `Dockerfile` the path to the Dockerfile that must be in the current folder (or a subfolder).
Please note the `dockerImage` and the `dockerFile` are mutually exclusive.

To install requirements from private git repositories, add the following to your `serverless.yml`:
```yaml
Expand Down
22 changes: 21 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class ServerlessPythonRequirements {
usePipenv: true,
pythonBin: this.serverless.service.provider.runtime || 'python',
dockerizePip: false,
dockerImage: `lambci/lambda:build-${this.serverless.service.provider.runtime}`,
dockerSsh: false,
dockerImage: null,
dockerFile: null,
pipCmdExtraArgs: [],
noDeploy: [
'boto3',
Expand All @@ -45,6 +47,23 @@ class ServerlessPythonRequirements {
if (options.dockerizePip === 'non-linux') {
options.dockerizePip = process.platform !== 'linux';
}
if (options.dockerImage && options.dockerFile) {
throw new Error(
'Python Requirements: you can provide a dockerImage or a dockerFile option, not both.'
);
} else if (!options.dockerFile) {
// If no dockerFile is provided, use default image
const defaultImage = `lambci/lambda:build-${this.serverless.service.provider.runtime}`;
options.dockerImage = options.dockerImage || defaultImage;
}
if (!options.dockerizePip && (options.dockerSsh || options.dockerImage || options.dockerFile)) {
if (!this.warningLogged) {
this.serverless.cli.log(
'WARNING: You provided a docker related option but dockerizePip is set to false.'
);
this.warningLogged = true;
}
}
return options;
}

Expand All @@ -57,6 +76,7 @@ class ServerlessPythonRequirements {
constructor(serverless, options) {
this.serverless = serverless;
this.servicePath = this.serverless.config.servicePath;
this.warningLogged = false;

this.commands = {
requirements: {
Expand Down
59 changes: 59 additions & 0 deletions lib/docker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const {spawnSync} = require('child_process');
const isWsl = require('is-wsl');


function dockerCommand(options) {
const cmd = 'docker';
const ps = spawnSync(cmd, options, {'timeout': 10000, 'encoding': 'utf-8'});
if (ps.error) {
if (ps.error.code === 'ENOENT') {
throw new Error('docker not found! Please install it.');
}
throw new Error(ps.error);
} else if (ps.status !== 0) {
throw new Error(ps.stderr);
}
return ps;
}

/**
* Build the custom Docker image
*/
function buildImage(dockerFile) {
const imageName = 'sls-py-reqs-custom';
const options = [
'build', '-f', dockerFile, '-t', imageName, '.'
];
const ps = dockerCommand(options);
return imageName;
};

/**
* Get bind path depending on os platform
*/
function getBindPath(servicePath) {
// Determine os platform of docker CLI from 'docker version'
const options = ['version', '--format', '{{with .Client}}{{.Os}}{{end}}'];
const ps = dockerCommand(options);
const cliPlatform = ps.stdout.trim();

// Determine bind path
let bindPath;
if (process.platform === 'win32') {
bindPath = servicePath.replace(/\\([^\s])/g, '/$1');
if (cliPlatform === 'windows') {
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
}
} else if (isWsl) {
bindPath = servicePath.replace(/^\/mnt\//, '/');
if (cliPlatform === 'windows') {
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
}
} else {
bindPath = servicePath;
}

return bindPath;
};

module.exports = {buildImage, getBindPath};
41 changes: 12 additions & 29 deletions lib/pip.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const fse = require('fs-extra');
const path = require('path');
const {spawnSync} = require('child_process');
const isWsl = require('is-wsl');
const {quote} = require('shell-quote');
const {buildImage, getBindPath} = require('./docker');

/**
* pip install the requirements to the .serverless/requirements directory
Expand Down Expand Up @@ -53,35 +53,18 @@ function installRequirements() {
if (this.options.dockerizePip) {
cmd = 'docker';

this.serverless.cli.log(`Docker Image: ${this.options.dockerImage}`);

// Determine os platform of docker CLI from 'docker version'
options = ['version', '--format', '{{with .Client}}{{.Os}}{{end}}'];
const ps = spawnSync(cmd, options, {'timeout': 10000, 'encoding': 'utf-8'});
if (ps.error) {
if (ps.error.code === 'ENOENT') {
throw new Error('docker not found! Please install it.');
}
throw new Error(ps.error);
} else if (ps.status !== 0) {
throw new Error(ps.stderr);
}

let bindPath;
const cliPlatform = ps.stdout.trim();
if (process.platform === 'win32') {
bindPath = this.servicePath.replace(/\\([^\s])/g, '/$1');
if (cliPlatform === 'windows') {
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
}
} else if (isWsl) {
bindPath = this.servicePath.replace(/^\/mnt\//, '/');
if (cliPlatform === 'windows') {
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
}
// Build docker image if required
let dockerImage;
if (this.options.dockerFile) {
this.serverless.cli.log(`Building custom docker image from ${this.options.dockerFile}...`);
dockerImage = buildImage(this.options.dockerFile);
} else {
bindPath = this.servicePath;
dockerImage = this.options.dockerImage;
}
this.serverless.cli.log(`Docker Image: ${dockerImage}`);

// Prepare bind path depending on os platform
const bindPath = getBindPath(this.servicePath);

options = [
'run', '--rm',
Expand All @@ -103,7 +86,7 @@ function installRequirements() {
]);
pipCmd = ['/bin/bash', '-c', '"' + pipCmd + ' && ' + chownCmd + '"'];
}
options.push(this.options.dockerImage);
options.push(dockerImage);
options.push(...pipCmd);
} else {
cmd = pipCmd[0];
Expand Down