serverless-plugin-go 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cz.toml +15 -0
- package/.eslintrc.json +19 -0
- package/.pre-commit-config.yaml +21 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/index.js +316 -0
- package/index.test.js +349 -0
- package/mocks/serverless.mock.js +17 -0
- package/mocks/service.mock.js +36 -0
- package/package.json +61 -0
package/.cz.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[tool]
|
|
2
|
+
[tool.commitizen]
|
|
3
|
+
name = "cz_customize"
|
|
4
|
+
version = "0.0.1"
|
|
5
|
+
tag_format = "v$version"
|
|
6
|
+
bump_message = "bump: release $current_version → $new_version [skip-ci]"
|
|
7
|
+
update_changelog_on_bump = true
|
|
8
|
+
version_files = [
|
|
9
|
+
"package.json:version",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[tool.commitizen.customize]
|
|
13
|
+
schema_pattern = "(break|build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump|deps)(\\(\\S+\\))?!?:(\\s.*)"
|
|
14
|
+
bump_pattern = "^(break|build|feat|fix|refactor|style|test|revert|deps|docs|ci|chore)"
|
|
15
|
+
bump_map = {"break" = "MAJOR", "build" = "MINOR", "feat" = "MINOR", "revert" = "MINOR", "fix" = "PATCH", "refactor" = "PATCH", "style" = "PATCH", "test" = "PATCH", "ci" = "PATCH", "deps" = "PATCH", "chore" = "PATCH", "docs" = "PATCH"}
|
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"plugin:prettier/recommended"
|
|
4
|
+
],
|
|
5
|
+
"plugins": [
|
|
6
|
+
"prettier"
|
|
7
|
+
],
|
|
8
|
+
"rules": {
|
|
9
|
+
"prettier/prettier": "error"
|
|
10
|
+
},
|
|
11
|
+
"parserOptions": {
|
|
12
|
+
"ecmaVersion": "latest",
|
|
13
|
+
"sourceType": "module"
|
|
14
|
+
},
|
|
15
|
+
"env": {
|
|
16
|
+
"es6": true,
|
|
17
|
+
"node": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# See https://pre-commit.com for more information
|
|
2
|
+
# See https://pre-commit.com/hooks.html for more hooks
|
|
3
|
+
repos:
|
|
4
|
+
- repo: https://github.com/pre-commit/mirrors-eslint
|
|
5
|
+
rev: v8.50.0
|
|
6
|
+
hooks:
|
|
7
|
+
- id: eslint
|
|
8
|
+
files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
|
9
|
+
types: [file]
|
|
10
|
+
|
|
11
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
12
|
+
rev: v4.4.0
|
|
13
|
+
hooks:
|
|
14
|
+
- id: check-yaml
|
|
15
|
+
- id: check-added-large-files
|
|
16
|
+
|
|
17
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
18
|
+
rev: 3.10.0
|
|
19
|
+
hooks:
|
|
20
|
+
- id: commitizen
|
|
21
|
+
stages: [commit-msg]
|
package/.prettierignore
ADDED
package/.prettierrc.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Maciej Winnicki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# ⚡️Serverless Framework Go Plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/serverless-plugin-go)
|
|
4
|
+
|
|
5
|
+
`serverless-plugin-go` is a Serverless Framework plugin that compiles Go functions on the fly. You don't need to do it manually before `serverless deploy`. Once the plugin is installed it will happen automatically.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Concurrent compilation happens across all CPU cores.
|
|
10
|
+
- Support for both `serverless deploy` and `serverless deploy function` commands.
|
|
11
|
+
- Support for `serverless invoke local` command.
|
|
12
|
+
- Additional command `serverless go build`.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
1. Install the plugin
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm i --save-dev serverless-plugin-go
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
1. Add it to your `serverless.yaml`
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
plugins:
|
|
26
|
+
- 'serverless-plugin-go'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
1. Replace every Go function's `handler` with `*.go` file path or a package path. E.g.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
functions:
|
|
33
|
+
example:
|
|
34
|
+
runtime: 'provided.al2'
|
|
35
|
+
handler: 'functions/example/main.go' # or just functions/example
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Default values:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
custom:
|
|
44
|
+
go:
|
|
45
|
+
baseDir: '.' # folder where go.mod file lives, if set `handler` property should be set relatively to that folder
|
|
46
|
+
binDir: '.bin' # target folder for binary files
|
|
47
|
+
cmd: 'go build -ldflags="-s -w"' # compile command
|
|
48
|
+
monorepo: false # if enabled, builds function every directory (useful for monorepo where go.mod is managed by each function
|
|
49
|
+
env: # environment variables used to compile binaries
|
|
50
|
+
GOOS: 'linux' # Default compile OS
|
|
51
|
+
CGO_ENABLED: '0' # By default CGO is disabled
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### How does it work?
|
|
55
|
+
|
|
56
|
+
The plugin compiles every Go function defined in `serverless.yaml` into `.bin` directory. After that it internally changes `handler` so
|
|
57
|
+
that the Serverless Framework will deploy the compiled file not the source file.
|
|
58
|
+
|
|
59
|
+
For every matched function it also overrides `package` parameter to the next config:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
individually: true
|
|
63
|
+
artifact: <path to the generated zip with go binary>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### How to run Golang Lambda on ARM?
|
|
67
|
+
|
|
68
|
+
Change architecture field from `provider` or function configuration to `arm64`:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
provider:
|
|
72
|
+
architecture: 'arm64'
|
|
73
|
+
runtime: 'provided.al2'
|
|
74
|
+
|
|
75
|
+
# If you define architecture at function level, this will have preference instead the provider configuration
|
|
76
|
+
|
|
77
|
+
functions:
|
|
78
|
+
test:
|
|
79
|
+
architecture: 'arm64'
|
|
80
|
+
handler: test/main.go
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Caveats
|
|
84
|
+
|
|
85
|
+
This implementation doesn't allow to add any other files to the lambda artifact apart from the binary. To address this problem you should
|
|
86
|
+
use Lambda Layers to allow your lambdas to hold external files.
|
|
87
|
+
|
|
88
|
+
## Credits
|
|
89
|
+
|
|
90
|
+
This repo is forked from [serverless-go-plugin][1] created by [mthenw][2] and modified to fix some issues and reach the new AWS standards
|
|
91
|
+
(go1.x provider deprecation).
|
|
92
|
+
|
|
93
|
+
[1]: https://github.com/mthenw/serverless-go-plugin
|
|
94
|
+
[2]: https://github.com/mthenw
|
package/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import util from "util";
|
|
2
|
+
import cp from "child_process";
|
|
3
|
+
import prettyHrtime from "pretty-hrtime";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import AdmZip from "adm-zip";
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
|
|
9
|
+
// SupportedRuntimes Runtimes that are allowed to run Golang lambdas.
|
|
10
|
+
const supportedRuntimes = ["provided.al2"];
|
|
11
|
+
|
|
12
|
+
const logPrefix = "GoPlugin";
|
|
13
|
+
|
|
14
|
+
export default class Go {
|
|
15
|
+
constructor(serverless, options) {
|
|
16
|
+
this.readFileSync = readFileSync;
|
|
17
|
+
this.zip = new AdmZip();
|
|
18
|
+
this.exec = util.promisify(cp.exec);
|
|
19
|
+
|
|
20
|
+
this.serverless = serverless;
|
|
21
|
+
this.options = options || {};
|
|
22
|
+
|
|
23
|
+
this.defaultConfig = {
|
|
24
|
+
baseDir: ".",
|
|
25
|
+
binDir: ".bin",
|
|
26
|
+
env: {
|
|
27
|
+
CGO_ENABLED: "0",
|
|
28
|
+
GOOS: "linux",
|
|
29
|
+
},
|
|
30
|
+
cmd: 'go build -ldflags="-s -w"',
|
|
31
|
+
monorepo: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.hooks = {
|
|
35
|
+
"before:deploy:function:packageFunction": this.compileFunction.bind(this),
|
|
36
|
+
"before:package:createDeploymentArtifacts":
|
|
37
|
+
this.compileFunctions.bind(this),
|
|
38
|
+
// Because of https://github.com/serverless/serverless/blob/master/lib/plugins/aws/invokeLocal/index.js#L361
|
|
39
|
+
// plugin needs to compile a function and then ignore packaging.
|
|
40
|
+
"before:invoke:local:invoke": this.compileToInvoke.bind(this),
|
|
41
|
+
"go:build:build": this.compileFunctions.bind(this),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.commands = {
|
|
45
|
+
go: {
|
|
46
|
+
usage: "Manage Go functions",
|
|
47
|
+
lifecycleEvents: ["go"],
|
|
48
|
+
commands: {
|
|
49
|
+
build: {
|
|
50
|
+
usage: "Build all Go functions",
|
|
51
|
+
lifecycleEvents: ["build"],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Execute single function compilation and package.
|
|
60
|
+
*/
|
|
61
|
+
async compileFunction() {
|
|
62
|
+
const name = this.options.function;
|
|
63
|
+
const func = this.serverless.service.functions[this.options.function];
|
|
64
|
+
|
|
65
|
+
this.logInfo(`Starting compilation...`);
|
|
66
|
+
|
|
67
|
+
const timeStart = process.hrtime();
|
|
68
|
+
await this.compile(name, func);
|
|
69
|
+
const timeEnd = process.hrtime(timeStart);
|
|
70
|
+
|
|
71
|
+
this.logInfo(`Compilation time (${name}): ${prettyHrtime(timeEnd)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load all configured functions and execute compilation according it's configuration.
|
|
76
|
+
*/
|
|
77
|
+
async compileFunctions() {
|
|
78
|
+
let names = Object.keys(this.serverless.service.functions);
|
|
79
|
+
|
|
80
|
+
this.logInfo(`Starting compilation...`);
|
|
81
|
+
|
|
82
|
+
const timeStart = process.hrtime();
|
|
83
|
+
|
|
84
|
+
let compiles = [];
|
|
85
|
+
|
|
86
|
+
for (let funcName of names) {
|
|
87
|
+
const func = this.serverless.service.functions[funcName];
|
|
88
|
+
compiles.push(this.compile(funcName, func));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await Promise.all(compiles);
|
|
92
|
+
|
|
93
|
+
const timeEnd = process.hrtime(timeStart);
|
|
94
|
+
|
|
95
|
+
this.logInfo(`Compilation time: ${prettyHrtime(timeEnd)}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Execute single function compilation and do not create the final artifact.
|
|
100
|
+
* This is used when `invoke` command is performed.
|
|
101
|
+
*/
|
|
102
|
+
async compileToInvoke() {
|
|
103
|
+
const name = this.options.function;
|
|
104
|
+
const func = this.serverless.service.functions[this.options.function];
|
|
105
|
+
|
|
106
|
+
this.logInfo(`Starting compilation...`);
|
|
107
|
+
|
|
108
|
+
const timeStart = process.hrtime();
|
|
109
|
+
await this.compile(name, func, false);
|
|
110
|
+
const timeEnd = process.hrtime(timeStart);
|
|
111
|
+
|
|
112
|
+
this.logInfo(`Compilation time (${name}): ${prettyHrtime(timeEnd)}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Collect configuration to build binaries and create the final artifact if needed
|
|
117
|
+
*
|
|
118
|
+
* @param {string} name name of the node function
|
|
119
|
+
* @param {Object} func function configuration object
|
|
120
|
+
* @param {bool} artifact flag to create or not the final artifact
|
|
121
|
+
*
|
|
122
|
+
* @throws {Error} if compilation command fails
|
|
123
|
+
*/
|
|
124
|
+
async compile(name, func, artifact = true) {
|
|
125
|
+
const config = this.getConfig();
|
|
126
|
+
const arch = this.getFunctionArch(name);
|
|
127
|
+
const runtime = this.getFunctionRuntime(name);
|
|
128
|
+
|
|
129
|
+
if (!supportedRuntimes.includes(runtime)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (arch == "arm64") {
|
|
134
|
+
config.env["GOARCH"] = "arm64";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const absHandler = path.resolve(config.baseDir);
|
|
138
|
+
const absBin = path.resolve(config.binDir);
|
|
139
|
+
|
|
140
|
+
let compileBinPath = path.join(path.relative(absHandler, absBin), name); // binPath is based on cwd no baseDir
|
|
141
|
+
let cwd = config.baseDir;
|
|
142
|
+
let handler = func.handler;
|
|
143
|
+
|
|
144
|
+
if (config.monorepo) {
|
|
145
|
+
cwd = path.relative(absHandler, func.handler);
|
|
146
|
+
handler = ".";
|
|
147
|
+
|
|
148
|
+
if (func.handler.endsWith(".go")) {
|
|
149
|
+
cwd = path.relative(absHandler, path.dirname(func.handler));
|
|
150
|
+
handler = path.basename(func.handler);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
compileBinPath = path.relative(cwd, compileBinPath);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.logDebug(`[${name}] env: ${JSON.stringify(config.env)}`);
|
|
157
|
+
this.logDebug(`[${name}] cwd: ${cwd}`);
|
|
158
|
+
this.logDebug(`[${name}] arch: ${arch}`);
|
|
159
|
+
|
|
160
|
+
await this.execCompilation(
|
|
161
|
+
config.cmd,
|
|
162
|
+
compileBinPath,
|
|
163
|
+
handler,
|
|
164
|
+
cwd,
|
|
165
|
+
config.env,
|
|
166
|
+
arch,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
this.logInfo(`Compiled function: ${chalk.magenta(name)}`);
|
|
170
|
+
|
|
171
|
+
let binPath = path.join(config.binDir, name);
|
|
172
|
+
|
|
173
|
+
if (process.platform === "win32") {
|
|
174
|
+
binPath = binPath.replace(/\\/g, "/");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.serverless.service.functions[name].handler = binPath;
|
|
178
|
+
|
|
179
|
+
if (artifact) {
|
|
180
|
+
this.packageBootstrap(name, binPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Execute compilation command from collected configuration.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} cmd Compilation command
|
|
188
|
+
* @param {string} out Binary out path
|
|
189
|
+
* @param {string} main Path to the main package or file
|
|
190
|
+
* @param {string} cwd Working directory
|
|
191
|
+
* @param {Object} env Set of environment variables to execute compilation
|
|
192
|
+
* @param {string} arch Compilation architecture
|
|
193
|
+
*/
|
|
194
|
+
async execCompilation(cmd, out, main, cwd, env, arch) {
|
|
195
|
+
let command = `${cmd} -o ${out} ${main}`;
|
|
196
|
+
|
|
197
|
+
let execOpts = { cwd: cwd, env: { ...process.env, ...env } };
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await this.exec(command, execOpts);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
this.logError(`Error compiling function (cwd: ${cwd}): ${e.message}`);
|
|
203
|
+
|
|
204
|
+
throw new Error(`error compiling function (cwd: ${cwd})`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create package to deploy lambda
|
|
210
|
+
*
|
|
211
|
+
* @param {string} name Name of the function config
|
|
212
|
+
* @param {Object} baseConfig Configuration object
|
|
213
|
+
* @param {string} binPath Path to generated binary
|
|
214
|
+
*/
|
|
215
|
+
packageBootstrap(name, binPath) {
|
|
216
|
+
this.zip.addFile("bootstrap", this.readFileSync(binPath), "", 0o755);
|
|
217
|
+
|
|
218
|
+
const zipPath = binPath + ".zip";
|
|
219
|
+
this.zip.writeZip(zipPath);
|
|
220
|
+
|
|
221
|
+
this.serverless.service.functions[name].package = {
|
|
222
|
+
individually: true,
|
|
223
|
+
artifact: zipPath,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Merge default con
|
|
229
|
+
* @returns {Object}
|
|
230
|
+
*/
|
|
231
|
+
getConfig() {
|
|
232
|
+
let config = { ...this.defaultConfig };
|
|
233
|
+
const service = this.serverless.service;
|
|
234
|
+
|
|
235
|
+
if (service.custom && service.custom.go) {
|
|
236
|
+
config = { ...config, ...service.custom.go };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (let env of Object.keys(config.env)) {
|
|
240
|
+
config.env[env] = `${config.env[env]}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return config;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Read provider and function configuration and return the compilation architecture
|
|
248
|
+
*
|
|
249
|
+
* @param {string} funcName Function name to get compilation architecture.
|
|
250
|
+
*
|
|
251
|
+
* @returns Architecture name
|
|
252
|
+
*/
|
|
253
|
+
getFunctionArch(funcName) {
|
|
254
|
+
const funcConf = this.serverless.service.functions[funcName];
|
|
255
|
+
const provider = this.serverless.service.provider;
|
|
256
|
+
|
|
257
|
+
let arch = provider.architecture || "x86_64";
|
|
258
|
+
|
|
259
|
+
if (funcConf.architecture) {
|
|
260
|
+
arch = funcConf.architecture;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return arch;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check provider and function architecture and determine the compilation arch
|
|
268
|
+
*
|
|
269
|
+
* @param {string} funcName Function name to collect architecture
|
|
270
|
+
*
|
|
271
|
+
* @returns architecture name
|
|
272
|
+
*/
|
|
273
|
+
getFunctionRuntime(funcName) {
|
|
274
|
+
const funcConf = this.serverless.service.functions[funcName];
|
|
275
|
+
const provider = this.serverless.service.provider;
|
|
276
|
+
|
|
277
|
+
let runtime = provider.runtime;
|
|
278
|
+
|
|
279
|
+
if (funcConf.runtime) {
|
|
280
|
+
runtime = funcConf.runtime;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return runtime;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Print a plugin debug log message
|
|
288
|
+
*
|
|
289
|
+
* @param {string} message Log message
|
|
290
|
+
*/
|
|
291
|
+
logDebug(message) {
|
|
292
|
+
if (!this.options.verbose) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.serverless.cli.consoleLog(`${chalk.yellow(logPrefix)}: ${message}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Print a plugin info log message
|
|
301
|
+
*
|
|
302
|
+
* @param {string} message Log message
|
|
303
|
+
*/
|
|
304
|
+
logInfo(message) {
|
|
305
|
+
this.serverless.cli.consoleLog(`${chalk.cyan(logPrefix)}: ${message}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Print a plugin error log message
|
|
310
|
+
*
|
|
311
|
+
* @param {string} message Log message
|
|
312
|
+
*/
|
|
313
|
+
logError(message) {
|
|
314
|
+
this.serverless.cli.consoleLog(`${chalk.red(logPrefix)}: ${message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
package/index.test.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import sinon from "sinon";
|
|
2
|
+
import chai from "chai";
|
|
3
|
+
import sinonChai from "sinon-chai";
|
|
4
|
+
|
|
5
|
+
import Go from "./index.js";
|
|
6
|
+
import ServerlessMock from "./mocks/serverless.mock.js";
|
|
7
|
+
|
|
8
|
+
const expect = chai.expect;
|
|
9
|
+
|
|
10
|
+
chai.use(sinonChai);
|
|
11
|
+
|
|
12
|
+
describe("Go Plugin", () => {
|
|
13
|
+
let config = new ServerlessMock();
|
|
14
|
+
let sandbox;
|
|
15
|
+
let execStub;
|
|
16
|
+
let readFileSyncStub;
|
|
17
|
+
let admZipStub;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
sandbox = sinon.createSandbox();
|
|
21
|
+
execStub = sandbox.stub().resolves({ stdin: null, stdout: null });
|
|
22
|
+
readFileSyncStub = sinon
|
|
23
|
+
.stub()
|
|
24
|
+
.withArgs(".bin/testFunc1")
|
|
25
|
+
.returns("fake binary content");
|
|
26
|
+
|
|
27
|
+
admZipStub = {
|
|
28
|
+
addFile: sinon.stub(),
|
|
29
|
+
writeZip: sinon.stub(),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
config.service.restore();
|
|
35
|
+
sandbox.restore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("Compiles only Go functions with allowed providers", async () => {
|
|
39
|
+
config.service.setFunctions({
|
|
40
|
+
testFunc1: {
|
|
41
|
+
name: "testFunc1",
|
|
42
|
+
runtime: "nodejs10.x",
|
|
43
|
+
handler: "functions/func1",
|
|
44
|
+
},
|
|
45
|
+
testFunc2: {
|
|
46
|
+
name: "testFunc2",
|
|
47
|
+
runtime: "provided.al2",
|
|
48
|
+
handler: "functions/func2/main.go",
|
|
49
|
+
},
|
|
50
|
+
testFunc3: {
|
|
51
|
+
name: "testFunc3",
|
|
52
|
+
runtime: "provided.al2",
|
|
53
|
+
handler: "functions/func3",
|
|
54
|
+
},
|
|
55
|
+
testFunc4: {
|
|
56
|
+
name: "testFunc4",
|
|
57
|
+
runtime: "go1.x",
|
|
58
|
+
handler: "functions/func4",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const plugin = new Go(config);
|
|
63
|
+
plugin.exec = execStub;
|
|
64
|
+
plugin.readFileSync = readFileSyncStub;
|
|
65
|
+
plugin.zip = admZipStub;
|
|
66
|
+
|
|
67
|
+
// when
|
|
68
|
+
await plugin.compileFunctions();
|
|
69
|
+
|
|
70
|
+
// then
|
|
71
|
+
expect(config.service.functions.testFunc2.handler).to.equal(
|
|
72
|
+
`.bin/testFunc2`,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(execStub).to.have.been.calledWith(
|
|
76
|
+
`go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
|
|
77
|
+
{
|
|
78
|
+
cwd: ".",
|
|
79
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(config.service.functions.testFunc3.handler).to.equal(
|
|
84
|
+
`.bin/testFunc3`,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(execStub).to.have.been.calledWith(
|
|
88
|
+
`go build -ldflags="-s -w" -o .bin/testFunc3 functions/func3`,
|
|
89
|
+
{
|
|
90
|
+
cwd: ".",
|
|
91
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("Compiles Go function with custom command", async () => {
|
|
97
|
+
config.service
|
|
98
|
+
.setCustom({
|
|
99
|
+
go: {
|
|
100
|
+
cmd: "go build",
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
.setFunctions({
|
|
104
|
+
testFunc1: {
|
|
105
|
+
name: "testFunc1",
|
|
106
|
+
runtime: "provided.al2",
|
|
107
|
+
handler: "functions/func1/main.go",
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const plugin = new Go(config);
|
|
112
|
+
plugin.exec = execStub;
|
|
113
|
+
plugin.readFileSync = readFileSyncStub;
|
|
114
|
+
plugin.zip = admZipStub;
|
|
115
|
+
|
|
116
|
+
// when
|
|
117
|
+
await plugin.compileFunctions();
|
|
118
|
+
|
|
119
|
+
// then
|
|
120
|
+
expect(execStub).to.have.been.calledOnceWith(
|
|
121
|
+
`go build -o .bin/testFunc1 functions/func1/main.go`,
|
|
122
|
+
{
|
|
123
|
+
cwd: ".",
|
|
124
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("Compiles Go function with arm architecture", async () => {
|
|
130
|
+
config.service
|
|
131
|
+
.setProvider({
|
|
132
|
+
architecture: "arm64",
|
|
133
|
+
})
|
|
134
|
+
.setFunctions({
|
|
135
|
+
testFunc1: {
|
|
136
|
+
name: "testFunc1",
|
|
137
|
+
runtime: "provided.al2",
|
|
138
|
+
handler: "functions/func1/main.go",
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const plugin = new Go(config);
|
|
143
|
+
plugin.exec = execStub;
|
|
144
|
+
plugin.readFileSync = readFileSyncStub;
|
|
145
|
+
plugin.zip = admZipStub;
|
|
146
|
+
|
|
147
|
+
// when
|
|
148
|
+
await plugin.compileFunctions();
|
|
149
|
+
|
|
150
|
+
// then
|
|
151
|
+
expect(execStub).to.have.been.calledOnceWith(
|
|
152
|
+
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
153
|
+
{
|
|
154
|
+
cwd: ".",
|
|
155
|
+
env: {
|
|
156
|
+
...process.env,
|
|
157
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "arm64" },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("Compiles Go function with custom base dir", async () => {
|
|
164
|
+
config.service
|
|
165
|
+
.setCustom({
|
|
166
|
+
go: {
|
|
167
|
+
baseDir: "gopath",
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
.setFunctions({
|
|
171
|
+
testFunc1: {
|
|
172
|
+
name: "testFunc1",
|
|
173
|
+
runtime: "provided.al2",
|
|
174
|
+
handler: "functions/func1/main.go",
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const plugin = new Go(config);
|
|
179
|
+
plugin.exec = execStub;
|
|
180
|
+
plugin.readFileSync = readFileSyncStub;
|
|
181
|
+
plugin.zip = admZipStub;
|
|
182
|
+
|
|
183
|
+
// when
|
|
184
|
+
await plugin.compileFunctions();
|
|
185
|
+
|
|
186
|
+
// then
|
|
187
|
+
expect(execStub).to.have.been.calledOnceWith(
|
|
188
|
+
`go build -ldflags="-s -w" -o ../.bin/testFunc1 functions/func1/main.go`,
|
|
189
|
+
{
|
|
190
|
+
cwd: "gopath",
|
|
191
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("Throw error if compilation fails", async () => {
|
|
197
|
+
execStub.throws();
|
|
198
|
+
|
|
199
|
+
config.service.setFunctions({
|
|
200
|
+
testFunc1: {
|
|
201
|
+
name: "testFunc1",
|
|
202
|
+
runtime: "provided.al2",
|
|
203
|
+
handler: "functions/func1/main.go",
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const plugin = new Go(config);
|
|
208
|
+
plugin.exec = execStub;
|
|
209
|
+
plugin.readFileSync = readFileSyncStub;
|
|
210
|
+
plugin.zip = admZipStub;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
await plugin.compileFunctions();
|
|
214
|
+
} catch (e) {
|
|
215
|
+
expect(e.message).to.equal(`error compiling function (cwd: .)`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
expect.fail("Expected to throw an Error");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("Compiles Go function with global runtime defined", async () => {
|
|
223
|
+
config.service
|
|
224
|
+
.setProvider({
|
|
225
|
+
runtime: "provided.al2",
|
|
226
|
+
})
|
|
227
|
+
.setFunctions({
|
|
228
|
+
testFunc1: {
|
|
229
|
+
name: "testFunc1",
|
|
230
|
+
handler: "functions/func1/main.go",
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const plugin = new Go(config);
|
|
235
|
+
plugin.exec = execStub;
|
|
236
|
+
plugin.readFileSync = readFileSyncStub;
|
|
237
|
+
plugin.zip = admZipStub;
|
|
238
|
+
|
|
239
|
+
// when
|
|
240
|
+
await plugin.hooks["before:package:createDeploymentArtifacts"]();
|
|
241
|
+
|
|
242
|
+
// then
|
|
243
|
+
expect(execStub).to.have.been.calledOnce;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("Compiles single Go function", async () => {
|
|
247
|
+
config.service.setFunctions({
|
|
248
|
+
testFunc1: {
|
|
249
|
+
name: "testFunc1",
|
|
250
|
+
runtime: "provided.al2",
|
|
251
|
+
handler: "functions/func1/main.go",
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const plugin = new Go(config, { function: "testFunc1" });
|
|
256
|
+
plugin.exec = execStub;
|
|
257
|
+
plugin.readFileSync = readFileSyncStub;
|
|
258
|
+
plugin.zip = admZipStub;
|
|
259
|
+
|
|
260
|
+
// when
|
|
261
|
+
await plugin.hooks["before:deploy:function:packageFunction"]();
|
|
262
|
+
|
|
263
|
+
// then
|
|
264
|
+
expect(execStub).to.have.been.calledOnceWith(
|
|
265
|
+
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
266
|
+
{
|
|
267
|
+
cwd: ".",
|
|
268
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("Compiles Go function with monorepo", async () => {
|
|
274
|
+
config.service
|
|
275
|
+
.setCustom({
|
|
276
|
+
go: {
|
|
277
|
+
monorepo: true,
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
.setFunctions({
|
|
281
|
+
testFunc1: {
|
|
282
|
+
name: "testFunc1",
|
|
283
|
+
runtime: "provided.al2",
|
|
284
|
+
handler: "functions/func1",
|
|
285
|
+
},
|
|
286
|
+
testFunc2: {
|
|
287
|
+
name: "testFunc2",
|
|
288
|
+
runtime: "provided.al2",
|
|
289
|
+
handler: "functions/func2/main.go",
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const plugin = new Go(config);
|
|
294
|
+
plugin.exec = execStub;
|
|
295
|
+
plugin.readFileSync = readFileSyncStub;
|
|
296
|
+
plugin.zip = admZipStub;
|
|
297
|
+
|
|
298
|
+
// when
|
|
299
|
+
await plugin.hooks["before:package:createDeploymentArtifacts"]();
|
|
300
|
+
|
|
301
|
+
// then
|
|
302
|
+
expect(config.service.functions.testFunc1.handler).to.equal(
|
|
303
|
+
`.bin/testFunc1`,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(execStub).to.have.been.calledWith(
|
|
307
|
+
`go build -ldflags="-s -w" -o ../../.bin/testFunc1 .`,
|
|
308
|
+
{
|
|
309
|
+
cwd: "functions/func1",
|
|
310
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(config.service.functions.testFunc2.handler).to.equal(
|
|
315
|
+
`.bin/testFunc2`,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(execStub).to.have.been.calledWith(
|
|
319
|
+
`go build -ldflags="-s -w" -o ../../.bin/testFunc2 main.go`,
|
|
320
|
+
{
|
|
321
|
+
cwd: "functions/func2",
|
|
322
|
+
env: { ...process.env, ...{ CGO_ENABLED: "0", GOOS: "linux" } },
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("compiles Go functions", async () => {
|
|
328
|
+
config.service.setFunctions({
|
|
329
|
+
testFunc1: {
|
|
330
|
+
name: "testFunc1",
|
|
331
|
+
runtime: "provided.al2",
|
|
332
|
+
handler: "functions/func2/main.go",
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const plugin = new Go(config);
|
|
337
|
+
plugin.exec = execStub;
|
|
338
|
+
plugin.readFileSync = readFileSyncStub;
|
|
339
|
+
plugin.zip = admZipStub;
|
|
340
|
+
|
|
341
|
+
// when
|
|
342
|
+
await plugin.hooks["go:build:build"]();
|
|
343
|
+
|
|
344
|
+
// then
|
|
345
|
+
expect(config.service.functions.testFunc1.handler).to.equal(
|
|
346
|
+
`.bin/testFunc1`,
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import sinon from "sinon";
|
|
2
|
+
|
|
3
|
+
import ServiceMock from "./service.mock.js";
|
|
4
|
+
|
|
5
|
+
export default class ServerlessMock {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.service = new ServiceMock();
|
|
8
|
+
|
|
9
|
+
this.cli = {
|
|
10
|
+
consoleLog: sinon.stub(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getProvider() {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default class ServiceMock {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.custom = {};
|
|
4
|
+
this.functions = {};
|
|
5
|
+
this.provider = {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
setFunctions(functions) {
|
|
9
|
+
this.functions = functions;
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
setCustom(custom) {
|
|
14
|
+
this.custom = custom;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setProvider(provider) {
|
|
19
|
+
this.provider = provider;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getAllFunctions() {
|
|
24
|
+
return Object.keys(this.functions);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getFunction(name) {
|
|
28
|
+
return this.functions[name];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
restore() {
|
|
32
|
+
this.custom = {};
|
|
33
|
+
this.functions = {};
|
|
34
|
+
this.provider = {};
|
|
35
|
+
}
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "serverless-plugin-go",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Serverless Framework plugin that compiles Go functions on the fly",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "npx eslint --fix .",
|
|
9
|
+
"format": "npx prettier --write .",
|
|
10
|
+
"test": "npx mocha index.test.js",
|
|
11
|
+
"depsUpgrade": "node ./scripts/updateDeps.cjs",
|
|
12
|
+
"pre-commit": "pre-commit install --hook-type commit-msg"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Drafteame/serverless-plugin-go.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"serverless",
|
|
20
|
+
"serverless framework",
|
|
21
|
+
"serverless plugin",
|
|
22
|
+
"amazon web services",
|
|
23
|
+
"aws",
|
|
24
|
+
"lambda",
|
|
25
|
+
"golang",
|
|
26
|
+
"go"
|
|
27
|
+
],
|
|
28
|
+
"author": "Maciej Winnicki <maciej.winnicki@gmail.com>",
|
|
29
|
+
"contributors": [
|
|
30
|
+
{
|
|
31
|
+
"name": "Eduardo Aguilar",
|
|
32
|
+
"email": "dante.aguilar41@gmail.com",
|
|
33
|
+
"url": "https://github.com/danteay"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Drafteame/serverless-plugin-go/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/Drafteame/serverless-plugin-go#readme",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"adm-zip": "^0.5.10",
|
|
43
|
+
"chalk": "^5.3.0",
|
|
44
|
+
"pretty-hrtime": "^1.0.3",
|
|
45
|
+
"util": "^0.12.5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"chai": "^4.3.10",
|
|
49
|
+
"child_process": "^1.0.2",
|
|
50
|
+
"eslint": "^8.52.0",
|
|
51
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
52
|
+
"eslint-config-prettier": "^9.0.0",
|
|
53
|
+
"eslint-plugin-import": "^2.29.0",
|
|
54
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
55
|
+
"fs": "^0.0.1-security",
|
|
56
|
+
"mocha": "^10.2.0",
|
|
57
|
+
"prettier": "^3.0.3",
|
|
58
|
+
"sinon": "^17.0.0",
|
|
59
|
+
"sinon-chai": "^3.7.0"
|
|
60
|
+
}
|
|
61
|
+
}
|