Skip to content

Commit a759d99

Browse files
Single source for containers/functions (#88)
1 parent 8bec05a commit a759d99

File tree

10 files changed

+739
-444
lines changed

10 files changed

+739
-444
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Serverless Framework handles everything from creating namespaces to function/cod
4444
- [Golang](#golang)
4545
- [Events](#events)
4646
- [Custom domains](#custom-domains)
47+
- [Ways to deploy functions](#ways-to-deploy-functions)
4748
- [Managing containers](#managing-containers)
4849
- [Logs](#logs)
4950
- [Info](#info)
@@ -364,6 +365,17 @@ Custom Domains configurations will be available after the first deploy.
364365
If you create a domain with other tools (Scaleway's Console, CLI or API) you must refer created domain into your serverless
365366
configuration file. Otherwise it will be deleted as Serverless Framework will give the priority to its configuration.
366367

368+
### Ways to deploy functions
369+
370+
There are multiple ways to create Scaleway Serverless functions/containers : CLI, API, Console, Serverless Framework, Terraform...
371+
372+
Using the `serverless deploy` command will apply the configuration located in your `serverless.yml` and remove functions that are not
373+
in the file to ensure a single source of truth.
374+
375+
This can be controlled using the `singleSource` option. By default its value is `false`.
376+
377+
If `singleSource` is set to `true`, functions and containers not defined in your serverless config file will be removed on the next `serverless deploy` command.
378+
367379
### Managing containers
368380

369381
**Requirements:** You need to have Docker installed to be able to build and push your image to your Scaleway registry.

deploy/lib/createContainers.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const BbPromise = require('bluebird');
4+
const singleSource = require('../../shared/singleSource');
45
const secrets = require('../../shared/secrets');
56

67
module.exports = {
@@ -10,12 +11,35 @@ module.exports = {
1011
.then(this.createOrUpdateContainers);
1112
},
1213

14+
deleteContainersByIds(containersIdsToDelete) {
15+
containersIdsToDelete.forEach((containerIdToDelete) => {
16+
this.deleteContainer(containerIdToDelete).then((res) => {
17+
this.serverless.cli.log(
18+
`Container ${res.name} removed from config file, deleting it...`
19+
);
20+
this.waitForContainerStatus(containerIdToDelete, "deleted").then(
21+
this.serverless.cli.log(`Container ${res.name} deleted`)
22+
);
23+
});
24+
});
25+
},
26+
1327
createOrUpdateContainers(foundContainers) {
1428
const { containers } = this.provider.serverless.service.custom;
15-
const containerNames = Object.keys(containers);
16-
const promises = containerNames.map((containerName) => {
29+
30+
const deleteData = singleSource.getElementsToDelete(
31+
this.serverless.configurationInput.singleSource,
32+
foundContainers,
33+
Object.keys(containers),
34+
);
35+
36+
this.deleteContainersByIds(deleteData.elementsIdsToRemove);
37+
38+
const promises = deleteData.serviceNamesRet.map((containerName) => {
1739
const container = Object.assign(containers[containerName], { name: containerName });
40+
1841
const foundContainer = foundContainers.find(c => c.name === container.name);
42+
1943
return foundContainer
2044
? this.updateSingleContainer(container, foundContainer)
2145
: this.createSingleContainer(container);

deploy/lib/createFunctions.js

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const BbPromise = require('bluebird');
44
const secrets = require('../../shared/secrets');
5+
const singleSource = require('../../shared/singleSource');
56
const { RUNTIME_STATUS_AVAILABLE } = require('../../shared/runtimes');
67

78
module.exports = {
@@ -11,22 +12,45 @@ module.exports = {
1112
.then(this.createOrUpdateFunctions);
1213
},
1314

15+
deleteFunctionsByIds(funcIdsToDelete) {
16+
funcIdsToDelete.forEach((funcIdToDelete) => {
17+
this.deleteFunction(funcIdToDelete).then((res) => {
18+
this.serverless.cli.log(
19+
`Function ${res.name} removed from config file, deleting it...`
20+
);
21+
this.waitForFunctionStatus(funcIdToDelete, "deleted").then(
22+
this.serverless.cli.log(`Function ${res.name} deleted`)
23+
);
24+
});
25+
});
26+
},
27+
1428
createOrUpdateFunctions(foundFunctions) {
1529
const { functions } = this.provider.serverless.service;
1630

17-
const functionNames = Object.keys(functions);
18-
const promises = functionNames.map((functionName) => {
19-
const func = Object.assign(functions[functionName], { name: functionName });
31+
const deleteData = singleSource.getElementsToDelete(
32+
this.serverless.configurationInput.singleSource,
33+
foundFunctions,
34+
Object.keys(functions),
35+
);
36+
37+
this.deleteFunctionsByIds(deleteData.elementsIdsToRemove);
38+
39+
const promises = deleteData.serviceNamesRet.map((functionName) => {
40+
const func = Object.assign(functions[functionName], {
41+
name: functionName,
42+
});
43+
2044
const foundFunc = foundFunctions.find((f) => f.name === func.name);
45+
2146
return foundFunc
2247
? this.updateSingleFunction(func, foundFunc)
2348
: this.createSingleFunction(func);
2449
});
2550

26-
return Promise.all(promises)
27-
.then((updatedFunctions) => {
28-
this.functions = updatedFunctions;
29-
});
51+
return Promise.all(promises).then((updatedFunctions) => {
52+
this.functions = updatedFunctions;
53+
});
3054
},
3155

3256
applyDomains(funcId, customDomains) {
@@ -40,7 +64,11 @@ module.exports = {
4064
existingDomains.push({ hostname: domain.hostname, id: domain.id });
4165
});
4266

43-
if (customDomains !== undefined && customDomains !== null && customDomains.length > 0) {
67+
if (
68+
customDomains !== undefined &&
69+
customDomains !== null &&
70+
customDomains.length > 0
71+
) {
4472
customDomains.forEach((customDomain) => {
4573
domainsIdToDelete.push(customDomain.id);
4674

@@ -59,8 +87,10 @@ module.exports = {
5987
}
6088

6189
existingDomains.forEach((existingDomain) => {
62-
if ((customDomains === undefined || customDomains === null)
63-
&& existingDomain.id !== undefined) {
90+
if (
91+
(customDomains === undefined || customDomains === null) &&
92+
existingDomain.id !== undefined
93+
) {
6494
domainsIdToDelete.push(existingDomain.id);
6595
} else if (!customDomains.includes(existingDomain.hostname)) {
6696
domainsIdToDelete.push(existingDomain.id);
@@ -74,36 +104,46 @@ module.exports = {
74104
.then((res) => {
75105
this.serverless.cli.log(`Creating domain ${res.hostname}`);
76106
})
77-
.then(() => {}, (reason) => {
78-
this.serverless.cli.log(`Error on domain : ${newDomain}, reason : ${reason.message}`);
79-
80-
if (reason.message.includes("could not validate")) {
81-
this.serverless.cli.log("Ensure CNAME configuration is ok, it can take some time for a record to propagate");
107+
.then(
108+
() => {},
109+
(reason) => {
110+
this.serverless.cli.log(
111+
`Error on domain : ${newDomain}, reason : ${reason.message}`
112+
);
113+
114+
if (reason.message.includes("could not validate")) {
115+
this.serverless.cli.log(
116+
"Ensure CNAME configuration is ok, it can take some time for a record to propagate"
117+
);
118+
}
82119
}
83-
});
120+
);
84121
});
85122

86123
domainsIdToDelete.forEach((domainId) => {
87124
if (domainId === undefined) {
88125
return;
89126
}
90-
this.deleteDomain(domainId)
91-
.then((res) => {
92-
this.serverless.cli.log(`Deleting domain ${res.hostname}`);
93-
});
127+
this.deleteDomain(domainId).then((res) => {
128+
this.serverless.cli.log(`Deleting domain ${res.hostname}`);
129+
});
94130
});
95131
});
96132
},
97133

98134
validateRuntime(func, existingRuntimes, logger) {
99-
const existingRuntimesGroupedByLanguage = existingRuntimes
100-
.reduce((r, a) => {
135+
const existingRuntimesGroupedByLanguage = existingRuntimes.reduce(
136+
(r, a) => {
101137
r[a.language] = r[a.language] || [];
102138
r[a.language].push(a);
103139
return r;
104-
}, Object.create(null));
140+
},
141+
Object.create(null)
142+
);
105143

106-
const existingRuntimesByName = Object.values(existingRuntimesGroupedByLanguage)
144+
const existingRuntimesByName = Object.values(
145+
existingRuntimesGroupedByLanguage
146+
)
107147
.flat()
108148
.reduce((map, r) => {
109149
map[r.name] = { status: r.status, statusMessage: r.status_message };
@@ -116,7 +156,11 @@ module.exports = {
116156
const runtime = existingRuntimesByName[currentRuntime];
117157
if (runtime.status !== RUNTIME_STATUS_AVAILABLE) {
118158
let warnMessage = `WARNING: Runtime ${currentRuntime} is in status ${runtime.status}`;
119-
if (runtime.statusMessage !== null && runtime.statusMessage !== undefined && runtime.statusMessage !== '') {
159+
if (
160+
runtime.statusMessage !== null &&
161+
runtime.statusMessage !== undefined &&
162+
runtime.statusMessage !== ""
163+
) {
120164
warnMessage += `: ${runtime.statusMessage}`;
121165
}
122166
logger.log(warnMessage);
@@ -126,9 +170,11 @@ module.exports = {
126170

127171
let errorMessage = `Runtime "${currentRuntime}" does not exist`;
128172
if (existingRuntimes.length > 0) {
129-
errorMessage += `, must be one of: ${Object.keys(existingRuntimesByName).join(', ')}`;
173+
errorMessage += `, must be one of: ${Object.keys(
174+
existingRuntimesByName
175+
).join(", ")}`;
130176
} else {
131-
errorMessage += ': cannot list runtimes';
177+
errorMessage += ": cannot list runtimes";
132178
}
133179

134180
throw new Error(errorMessage);
@@ -139,7 +185,7 @@ module.exports = {
139185
name: func.name,
140186
environment_variables: func.env,
141187
secret_environment_variables: secrets.convertObjectToModelSecretsArray(
142-
func.secret,
188+
func.secret
143189
),
144190
namespace_id: this.namespace.id,
145191
memory_limit: func.memoryLimit,
@@ -152,7 +198,11 @@ module.exports = {
152198
};
153199

154200
const availableRuntimes = await this.listRuntimes();
155-
params.runtime = this.validateRuntime(func, availableRuntimes, this.serverless.cli);
201+
params.runtime = this.validateRuntime(
202+
func,
203+
availableRuntimes,
204+
this.serverless.cli
205+
);
156206

157207
// checking if there is custom_domains set on function creation.
158208
if (func.custom_domains && func.custom_domains.length > 0) {
@@ -161,8 +211,10 @@ module.exports = {
161211
}
162212

163213
this.serverless.cli.log(`Creating function ${func.name}...`);
164-
return this.createFunction(params)
165-
.then((response) => Object.assign(response, { handler: func.handler }));
214+
215+
return this.createFunction(params).then((response) =>
216+
Object.assign(response, { handler: func.handler })
217+
);
166218
},
167219

168220
async updateSingleFunction(func, foundFunc) {
@@ -172,7 +224,7 @@ module.exports = {
172224
secret_environment_variables: await secrets.mergeSecretEnvVars(
173225
foundFunc.secret_environment_variables,
174226
secrets.convertObjectToModelSecretsArray(func.secret),
175-
this.serverless.cli,
227+
this.serverless.cli
176228
),
177229
memory_limit: func.memoryLimit,
178230
min_scale: func.minScale,
@@ -184,14 +236,19 @@ module.exports = {
184236
};
185237

186238
const availableRuntimes = await this.listRuntimes();
187-
params.runtime = this.validateRuntime(func, availableRuntimes, this.serverless.cli);
239+
params.runtime = this.validateRuntime(
240+
func,
241+
availableRuntimes,
242+
this.serverless.cli
243+
);
188244

189245
this.serverless.cli.log(`Updating function ${func.name}...`);
190246

191247
// assign domains
192248
this.applyDomains(foundFunc.id, func.custom_domains);
193249

194-
return this.updateFunction(foundFunc.id, params)
195-
.then((response) => Object.assign(response, { handler: func.handler }));
250+
return this.updateFunction(foundFunc.id, params).then((response) =>
251+
Object.assign(response, { handler: func.handler })
252+
);
196253
},
197254
};

examples/nodejs/serverless.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
service: scaleway-nodeXX
22
configValidationMode: off
3+
singleSource: false
34
provider:
45
name: scaleway
56
runtime: node16 # Available node runtimes are listed in documentation

0 commit comments

Comments
 (0)