Skip to content

Commit fb7a757

Browse files
committed
add container
1 parent d93f542 commit fb7a757

File tree

15 files changed

+609
-72
lines changed

15 files changed

+609
-72
lines changed

deploy/lib/createContainers.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
const BbPromise = require('bluebird');
4+
const constants = require('./constants');
5+
6+
module.exports = {
7+
createContainers() {
8+
return BbPromise.bind(this)
9+
.then(this.getContainers)
10+
.then(this.createOrUpdateContainers);
11+
},
12+
13+
getContainers() {
14+
const containersUrl = `namespaces/${this.namespace.id}/containers`;
15+
return this.provider.apiManager.get(containersUrl)
16+
.then(response => response.data.containers)
17+
.catch((err) => {
18+
throw new Error(err.response.data.message)
19+
})
20+
},
21+
22+
createOrUpdateContainers(foundContainers) {
23+
const containers = this.provider.serverless.service.custom.containers;
24+
const containerNames = Object.keys(containers);
25+
const promises = containerNames.map((containerName) => {
26+
const container = Object.assign(containers[containerName], { name: containerName });
27+
const foundContainer = foundContainers.find(c => c.name === container.name);
28+
return foundContainer ? this.updateContainer(container, foundContainer) : this.createSingleContainer(container);
29+
});
30+
31+
return Promise.all(promises)
32+
.then((updatedContainers) => {
33+
this.containers = updatedContainers;
34+
});
35+
},
36+
37+
createSingleContainer(container) {
38+
const params = {
39+
name: container.name,
40+
environment_variables: container.env,
41+
namespace_id: this.namespace.id,
42+
memory_limit: container.memoryLimit,
43+
min_scale: container.minScale,
44+
max_scale: container.maxScale,
45+
timeout: container.timeout
46+
};
47+
48+
this.serverless.cli.log(`Creating container ${container.name}...`);
49+
50+
return this.provider.apiManager.post('containers', params)
51+
.then(response => Object.assign(response.data, { directory: container.directory }))
52+
.catch((err) => {
53+
throw new Error(err.response.data.message)
54+
})
55+
},
56+
57+
updateContainer(container, foundContainer) {
58+
const params = {
59+
redeploy: false,
60+
environment_variables: container.env,
61+
memory_limit: container.memoryLimit,
62+
min_scale: container.minScale,
63+
max_scale: container.maxScale,
64+
timeout: container.timeout
65+
}
66+
67+
const updateUrl = `containers/${foundContainer.id}`;
68+
this.serverless.cli.log(`Updating container ${container.name}...`);
69+
return this.provider.apiManager.patch(updateUrl, params)
70+
.then(response => Object.assign(response.data, { directory: container.directory }))
71+
.catch((err) => {
72+
throw new Error(err.response.data.message)
73+
})
74+
},
75+
};

deploy/lib/createNamespace.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ module.exports = {
5959
setTimeout(() => resolve(this.waitNamespaceIsReady()), 1000);
6060
});
6161
}
62+
this.saveNamespaceToProvider(response.data);
6263
return true;
6364
});
6465
},

deploy/lib/deployContainers.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const BbPromise = require('bluebird');
4+
5+
module.exports = {
6+
deployContainers() {
7+
this.serverless.cli.log('Deploying Containers...');
8+
return BbPromise.bind(this)
9+
.then(this.deployEachContainer)
10+
.then(() => this.serverless.cli.log('Waiting for container deployments, this may take multiple minutes...'))
11+
.then(this.waitContainersAreDeployed)
12+
.catch((err) => {
13+
throw new Error(err.response.data.message)
14+
});
15+
},
16+
17+
deployEachContainer() {
18+
const promises = this.containers.map(
19+
container => this.provider.apiManager.post(`containers/${container.id}/deploy`, {})
20+
.then(response => response.data)
21+
.catch((err) => {
22+
throw new Error(err.response.data.message)
23+
}),
24+
);
25+
26+
return Promise.all(promises);
27+
},
28+
29+
waitContainersAreDeployed() {
30+
return this.provider.apiManager.get(`namespaces/${this.namespace.id}/containers`)
31+
.then((response) => {
32+
const containers = response.data.containers || [];
33+
let containersAreReady = true;
34+
for (let i = 0; i < containers.length; i += 1) {
35+
const container = response.data.containers[i];
36+
if (container.status === 'error') {
37+
throw new Error(container.error_message)
38+
}
39+
if (container.status !== 'ready') {
40+
containersAreReady = false;
41+
break;
42+
}
43+
}
44+
if (!containersAreReady) {
45+
return new Promise((resolve) => {
46+
setTimeout(() => resolve(this.waitContainersAreDeployed()), 5000);
47+
});
48+
}
49+
50+
// Print every containers endpoint
51+
return containers.forEach(container => this.serverless.cli.log(`Container ${container.name} has been deployed to: ${container.endpoint}`));
52+
});
53+
},
54+
};

deploy/lib/pushContainers.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
const BbPromise = require('bluebird');
4+
5+
const Docker = require('dockerode');
6+
const tar = require('tar-fs');
7+
const docker = new Docker();
8+
9+
const promisifyStream = (stream, verbose) => new BbPromise((resolve, reject) => {
10+
stream.on('data', data => {
11+
if (verbose) {
12+
console.log(data.toString().replace('\n',''))
13+
}
14+
})
15+
stream.on('end', resolve)
16+
stream.on('error', reject)
17+
});
18+
19+
module.exports = {
20+
pushContainers() {
21+
return BbPromise.bind(this)
22+
.then(this.buildAndPushContainer)
23+
},
24+
25+
26+
buildAndPushContainer() {
27+
const auth = {
28+
username: 'any',
29+
password: this.provider.scwToken
30+
}
31+
32+
const containerNames = Object.keys(this.containers);
33+
const promises = containerNames.map((containerName) => {
34+
35+
const container = this.containers[containerName]
36+
const tarStream = tar.pack(`./${container.directory}`)
37+
const imageName = `${this.namespace.registry_endpoint}/${container.name}:latest`
38+
this.serverless.cli.log(`Building and pushing container ${container.name} to: ${imageName} ...`);
39+
return new BbPromise((resolve) => {
40+
docker.buildImage(tarStream, {t: imageName})
41+
.then(stream => promisifyStream(stream, this.provider.options.verbose))
42+
.then(() => docker.getImage(imageName))
43+
.then((image) => image.push(auth))
44+
.then(stream => promisifyStream(stream, this.provider.options.verbose))
45+
.then(() => resolve())
46+
});
47+
});
48+
49+
return Promise.all(promises)
50+
},
51+
52+
};

deploy/scalewayDeploy.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ const validate = require('../shared/validate');
33
const setUpDeployment = require('../shared/setUpDeployment');
44
const createNamespace = require('./lib/createNamespace');
55
const createFunctions = require('./lib/createFunctions');
6+
const createContainers = require('./lib/createContainers');
7+
const pushContainers = require('./lib/pushContainers');
68
const uploadCode = require('./lib/uploadCode');
79
const deployFunctions = require('./lib/deployFunctions');
10+
const deployContainers = require('./lib/deployContainers');
811
const namespaceUtils = require('../shared/namespace');
912

1013
class ScalewayDeploy {
@@ -19,11 +22,33 @@ class ScalewayDeploy {
1922
setUpDeployment,
2023
createNamespace,
2124
createFunctions,
25+
createContainers,
26+
pushContainers,
2227
uploadCode,
2328
deployFunctions,
29+
deployContainers,
2430
namespaceUtils,
2531
);
2632

33+
function chainContainers() {
34+
if (this.provider.serverless.service.custom &&
35+
this.provider.serverless.service.custom.containers &&
36+
Object.keys(this.provider.serverless.service.custom.containers).length !== 0) {
37+
return this.createContainers()
38+
.then(this.pushContainers)
39+
.then(this.deployContainers)
40+
}
41+
};
42+
43+
function chainFunctions() {
44+
if (this.provider.serverless.service.functions &&
45+
Object.keys(this.provider.serverless.service.functions).length !== 0) {
46+
return this.createFunctions()
47+
.then(this.uploadCode)
48+
.then(this.deployFunctions)
49+
}
50+
};
51+
2752
this.hooks = {
2853
// Validate serverless.yml, set up default values, configure deployment...
2954
'before:deploy:deploy': () => BbPromise.bind(this)
@@ -35,12 +60,11 @@ class ScalewayDeploy {
3560
// - Create each functions in API if it does not exist
3661
// - Zip code - zip each function
3762
// - Get Presigned URL and Push code for each function to S3
38-
// - Deploy each function
63+
// - Deploy each function / container
3964
'deploy:deploy': () => BbPromise.bind(this)
4065
.then(this.createNamespace)
41-
.then(this.createFunctions)
42-
.then(this.uploadCode)
43-
.then(this.deployFunctions),
66+
.then(chainContainers)
67+
.then(chainFunctions)
4468
};
4569
}
4670
}

docs/README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ provider:
9090
# node8, node10 for JavaScript
9191
# python (2.7), python3 (3.7) for Python
9292
# golang
93+
# You don't need to specify a runtime if you deploy only containers
9394
runtime: <runtime>
9495
# See documentation below for environment
9596
env:
@@ -110,10 +111,47 @@ functions:
110111
env:
111112
MY_VARIABLE: "my-value"
112113
```
114+
### Managing containers
115+
116+
**Requirements:** You need to have Docker installed to be able to build and push your image to your Scaleway registry.
117+
118+
You must define your containers inside the `custom.containers` field in your serverless.yml manifest.
119+
Each container must specify the relative path of its application directory (containing the Dockerfile, and all files related to the application to deploy):
120+
121+
```yml
122+
custom:
123+
containers:
124+
myContainer:
125+
directory: my-container-directory
126+
# Environment only available in this function
127+
env:
128+
MY_VARIABLE: "my-value"
129+
```
130+
131+
Here is an example of the files you should have, the `directory` containing your Dockerfile ans scripts is `my-container-directory`.
132+
133+
```
134+
.
135+
├── my-container-directory
136+
│   ├── Dockerfile
137+
│   ├── requirements.txt
138+
│ ├── server.py
139+
│   └── (...)
140+
├── node_modules
141+
│   ├── serverless-scaleway-functions
142+
│ └── (...)
143+
├── package-lock.json
144+
├── package.json
145+
└── serverless.yml
146+
```
147+
148+
Scaleway's platform will automatically inject a PORT environment variable on which your server should be listening for incoming traffic. By default, this PORT is 8080.
149+
150+
You may use the [container example](../examples/container) to getting started.
113151
114152
### Runtime and Functions Handler
115153
116-
You must specify your functions runtime inside `provider.runtime` key inside your serverless.yml file.
154+
You must specify your functions runtime inside `provider.runtime` key inside your serverless.yml file. It is not necessary if you wish to deploy containers only.
117155
118156
#### Runtimes
119157
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3-alpine
2+
WORKDIR /usr/src/app
3+
4+
ENV PORT 8080
5+
EXPOSE 8080
6+
7+
COPY requirements.txt .
8+
RUN pip install -qr requirements.txt
9+
COPY server.py .
10+
11+
CMD ["python3", "./server.py"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Flask==1.0.2
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from flask import Flask
2+
3+
DEFAULT_PORT = "8080"
4+
MESSAGE = "Hello, World from Scaleway Container !\n"
5+
6+
app = Flask(__name__)
7+
8+
@app.route("/")
9+
def root():
10+
result = MESSAGE.encode("utf-8")
11+
return result
12+
13+
if __name__ == "__main__":
14+
# Scaleway's system will inject a PORT environment variable on which your application should start the server.
15+
port_env = os.getenv("PORT", DEFAULT_PORT)
16+
port = int(port_env)
17+
app.run(debug=True, host="0.0.0.0", port=port)

examples/container/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "container-scaleway-starter",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1"
7+
},
8+
"keywords": [],
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {},
12+
"devDependencies": {
13+
"serverless-scaleway-functions": "^0.1.6"
14+
},
15+
"description": ""
16+
}
17+

0 commit comments

Comments
 (0)