Skip to content

Commit 50bf258

Browse files
authored
Merge pull request #128 from cgrimal/feature/custom-docker-file
Add dockerFile option
2 parents 98a7c48 + 9ffdf6f commit 50bf258

File tree

4 files changed

+100
-30
lines changed

4 files changed

+100
-30
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ custom:
4040
```
4141
This must be the full image name and tag to use, including the runtime specific tag if applicable.
4242

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

4452
To install requirements from private git repositories, add the following to your `serverless.yml`:
4553
```yaml

index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ class ServerlessPythonRequirements {
2828
usePipenv: true,
2929
pythonBin: this.serverless.service.provider.runtime || 'python',
3030
dockerizePip: false,
31-
dockerImage: `lambci/lambda:build-${this.serverless.service.provider.runtime}`,
31+
dockerSsh: false,
32+
dockerImage: null,
33+
dockerFile: null,
3234
pipCmdExtraArgs: [],
3335
noDeploy: [
3436
'boto3',
@@ -45,6 +47,23 @@ class ServerlessPythonRequirements {
4547
if (options.dockerizePip === 'non-linux') {
4648
options.dockerizePip = process.platform !== 'linux';
4749
}
50+
if (options.dockerImage && options.dockerFile) {
51+
throw new Error(
52+
'Python Requirements: you can provide a dockerImage or a dockerFile option, not both.'
53+
);
54+
} else if (!options.dockerFile) {
55+
// If no dockerFile is provided, use default image
56+
const defaultImage = `lambci/lambda:build-${this.serverless.service.provider.runtime}`;
57+
options.dockerImage = options.dockerImage || defaultImage;
58+
}
59+
if (!options.dockerizePip && (options.dockerSsh || options.dockerImage || options.dockerFile)) {
60+
if (!this.warningLogged) {
61+
this.serverless.cli.log(
62+
'WARNING: You provided a docker related option but dockerizePip is set to false.'
63+
);
64+
this.warningLogged = true;
65+
}
66+
}
4867
return options;
4968
}
5069

@@ -57,6 +76,7 @@ class ServerlessPythonRequirements {
5776
constructor(serverless, options) {
5877
this.serverless = serverless;
5978
this.servicePath = this.serverless.config.servicePath;
79+
this.warningLogged = false;
6080

6181
this.commands = {
6282
requirements: {

lib/docker.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const {spawnSync} = require('child_process');
2+
const isWsl = require('is-wsl');
3+
4+
5+
function dockerCommand(options) {
6+
const cmd = 'docker';
7+
const ps = spawnSync(cmd, options, {'timeout': 10000, 'encoding': 'utf-8'});
8+
if (ps.error) {
9+
if (ps.error.code === 'ENOENT') {
10+
throw new Error('docker not found! Please install it.');
11+
}
12+
throw new Error(ps.error);
13+
} else if (ps.status !== 0) {
14+
throw new Error(ps.stderr);
15+
}
16+
return ps;
17+
}
18+
19+
/**
20+
* Build the custom Docker image
21+
*/
22+
function buildImage(dockerFile) {
23+
const imageName = 'sls-py-reqs-custom';
24+
const options = [
25+
'build', '-f', dockerFile, '-t', imageName, '.'
26+
];
27+
const ps = dockerCommand(options);
28+
return imageName;
29+
};
30+
31+
/**
32+
* Get bind path depending on os platform
33+
*/
34+
function getBindPath(servicePath) {
35+
// Determine os platform of docker CLI from 'docker version'
36+
const options = ['version', '--format', '{{with .Client}}{{.Os}}{{end}}'];
37+
const ps = dockerCommand(options);
38+
const cliPlatform = ps.stdout.trim();
39+
40+
// Determine bind path
41+
let bindPath;
42+
if (process.platform === 'win32') {
43+
bindPath = servicePath.replace(/\\([^\s])/g, '/$1');
44+
if (cliPlatform === 'windows') {
45+
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
46+
}
47+
} else if (isWsl) {
48+
bindPath = servicePath.replace(/^\/mnt\//, '/');
49+
if (cliPlatform === 'windows') {
50+
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
51+
}
52+
} else {
53+
bindPath = servicePath;
54+
}
55+
56+
return bindPath;
57+
};
58+
59+
module.exports = {buildImage, getBindPath};

lib/pip.js

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const fse = require('fs-extra');
22
const path = require('path');
33
const {spawnSync} = require('child_process');
4-
const isWsl = require('is-wsl');
54
const {quote} = require('shell-quote');
5+
const {buildImage, getBindPath} = require('./docker');
66

77
/**
88
* pip install the requirements to the .serverless/requirements directory
@@ -53,35 +53,18 @@ function installRequirements() {
5353
if (this.options.dockerizePip) {
5454
cmd = 'docker';
5555

56-
this.serverless.cli.log(`Docker Image: ${this.options.dockerImage}`);
57-
58-
// Determine os platform of docker CLI from 'docker version'
59-
options = ['version', '--format', '{{with .Client}}{{.Os}}{{end}}'];
60-
const ps = spawnSync(cmd, options, {'timeout': 10000, 'encoding': 'utf-8'});
61-
if (ps.error) {
62-
if (ps.error.code === 'ENOENT') {
63-
throw new Error('docker not found! Please install it.');
64-
}
65-
throw new Error(ps.error);
66-
} else if (ps.status !== 0) {
67-
throw new Error(ps.stderr);
68-
}
69-
70-
let bindPath;
71-
const cliPlatform = ps.stdout.trim();
72-
if (process.platform === 'win32') {
73-
bindPath = this.servicePath.replace(/\\([^\s])/g, '/$1');
74-
if (cliPlatform === 'windows') {
75-
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
76-
}
77-
} else if (isWsl) {
78-
bindPath = this.servicePath.replace(/^\/mnt\//, '/');
79-
if (cliPlatform === 'windows') {
80-
bindPath = bindPath.replace(/^\/(\w)\//i, '$1:/');
81-
}
56+
// Build docker image if required
57+
let dockerImage;
58+
if (this.options.dockerFile) {
59+
this.serverless.cli.log(`Building custom docker image from ${this.options.dockerFile}...`);
60+
dockerImage = buildImage(this.options.dockerFile);
8261
} else {
83-
bindPath = this.servicePath;
62+
dockerImage = this.options.dockerImage;
8463
}
64+
this.serverless.cli.log(`Docker Image: ${dockerImage}`);
65+
66+
// Prepare bind path depending on os platform
67+
const bindPath = getBindPath(this.servicePath);
8568

8669
options = [
8770
'run', '--rm',
@@ -103,7 +86,7 @@ function installRequirements() {
10386
]);
10487
pipCmd = ['/bin/bash', '-c', '"' + pipCmd + ' && ' + chownCmd + '"'];
10588
}
106-
options.push(this.options.dockerImage);
89+
options.push(dockerImage);
10790
options.push(...pipCmd);
10891
} else {
10992
cmd = pipCmd[0];

0 commit comments

Comments
 (0)