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 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]
@@ -0,0 +1,4 @@
1
+ # Ignore artifacts:
2
+ build/
3
+ coverage/
4
+ node_modules/
@@ -0,0 +1 @@
1
+ {}
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## v0.0.1 (2023-10-26)
2
+
3
+
4
+ - chore: enable release pipeline (#5)
5
+ - chore: add debug logs (#4)
6
+ - chore: add start compilation logs (#3)
7
+ - fix: missing system env vars to execute comp[ilation command (#2)
8
+ - chore: first commit
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
+ [![npm](https://img.shields.io/npm/v/serverless-plugin-go)](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
+ }