serverless-plugin-go 0.1.2 → 0.1.3
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 +1 -1
- package/CHANGELOG.md +12 -0
- package/index.js +13 -5
- package/index.test.js +106 -13
- package/package.json +2 -2
package/.cz.toml
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v0.1.3 (2026-11-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
- fix: per-function GOARCH and deterministic zip artifacts (#12)
|
|
5
|
+
- - Build env is now computed per function with an explicit GOARCH
|
|
6
|
+
(arm64/amd64). Previously config.env was mutated, so once an arm64
|
|
7
|
+
function compiled, every following x86_64 function was built as ARM,
|
|
8
|
+
and x86_64 builds on Apple Silicon used the host arch.
|
|
9
|
+
- Zip entries use a fixed mtime so identical binaries yield
|
|
10
|
+
byte-identical artifacts (stable hashes across builds).
|
|
11
|
+
- Bump mocha 10 -> 12 (mocha 10 fails to start on Node 26).
|
|
12
|
+
|
|
1
13
|
## v0.1.2 (2026-03-31)
|
|
2
14
|
|
|
3
15
|
|
package/index.js
CHANGED
|
@@ -12,6 +12,9 @@ const supportedRuntimes = ["provided.al2", "provided.al2023"];
|
|
|
12
12
|
|
|
13
13
|
const logPrefix = "GoPlugin";
|
|
14
14
|
|
|
15
|
+
// DOS zip timestamps can't go before 1980.
|
|
16
|
+
const zipEpoch = new Date(1980, 0, 1);
|
|
17
|
+
|
|
15
18
|
export default class Go {
|
|
16
19
|
constructor(serverless, options) {
|
|
17
20
|
this.readFileSync = readFileSync;
|
|
@@ -149,9 +152,12 @@ export default class Go {
|
|
|
149
152
|
return;
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
// Per-function env: never mutate shared config, and always set GOARCH so the
|
|
156
|
+
// host arch (e.g. Apple Silicon) never leaks into an x86_64 Lambda.
|
|
157
|
+
const env = {
|
|
158
|
+
...config.env,
|
|
159
|
+
GOARCH: arch === "arm64" ? "arm64" : "amd64",
|
|
160
|
+
};
|
|
155
161
|
|
|
156
162
|
const absHandler = path.resolve(config.baseDir);
|
|
157
163
|
const absBin = path.resolve(config.binDir);
|
|
@@ -172,7 +178,7 @@ export default class Go {
|
|
|
172
178
|
compileBinPath = path.relative(cwd, compileBinPath);
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
this.logDebug(`[${name}] env: ${JSON.stringify(
|
|
181
|
+
this.logDebug(`[${name}] env: ${JSON.stringify(env)}`);
|
|
176
182
|
this.logDebug(`[${name}] cwd: ${cwd}`);
|
|
177
183
|
this.logDebug(`[${name}] arch: ${arch}`);
|
|
178
184
|
|
|
@@ -181,7 +187,7 @@ export default class Go {
|
|
|
181
187
|
compileBinPath,
|
|
182
188
|
handler,
|
|
183
189
|
cwd,
|
|
184
|
-
|
|
190
|
+
env,
|
|
185
191
|
arch,
|
|
186
192
|
);
|
|
187
193
|
|
|
@@ -261,6 +267,8 @@ export default class Go {
|
|
|
261
267
|
*/
|
|
262
268
|
packageBootstrap(name, binPath) {
|
|
263
269
|
this.zip.addFile("bootstrap", this.readFileSync(binPath), "", 0o755);
|
|
270
|
+
// Fixed mtime so identical binaries produce byte-identical zips (stable hashes).
|
|
271
|
+
this.zip.getEntry("bootstrap").header.time = zipEpoch;
|
|
264
272
|
|
|
265
273
|
const zipPath = binPath + ".zip";
|
|
266
274
|
this.zip.writeZip(zipPath);
|
package/index.test.js
CHANGED
|
@@ -2,6 +2,11 @@ import sinon from "sinon";
|
|
|
2
2
|
import chai from "chai";
|
|
3
3
|
import sinonChai from "sinon-chai";
|
|
4
4
|
|
|
5
|
+
import AdmZip from "adm-zip";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
5
10
|
import Go from "./index.js";
|
|
6
11
|
import ServerlessMock from "./mocks/serverless.mock.js";
|
|
7
12
|
|
|
@@ -27,6 +32,7 @@ describe("Go Plugin", () => {
|
|
|
27
32
|
admZipStub = {
|
|
28
33
|
addFile: sinon.stub(),
|
|
29
34
|
writeZip: sinon.stub(),
|
|
35
|
+
getEntry: sinon.stub().returns({ header: {} }),
|
|
30
36
|
};
|
|
31
37
|
});
|
|
32
38
|
|
|
@@ -86,7 +92,10 @@ describe("Go Plugin", () => {
|
|
|
86
92
|
`go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
|
|
87
93
|
{
|
|
88
94
|
cwd: ".",
|
|
89
|
-
env: {
|
|
95
|
+
env: {
|
|
96
|
+
...process.env,
|
|
97
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
98
|
+
},
|
|
90
99
|
},
|
|
91
100
|
);
|
|
92
101
|
|
|
@@ -98,7 +107,10 @@ describe("Go Plugin", () => {
|
|
|
98
107
|
`go build -ldflags="-s -w" -o .bin/testFunc3 functions/func3`,
|
|
99
108
|
{
|
|
100
109
|
cwd: ".",
|
|
101
|
-
env: {
|
|
110
|
+
env: {
|
|
111
|
+
...process.env,
|
|
112
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
113
|
+
},
|
|
102
114
|
},
|
|
103
115
|
);
|
|
104
116
|
|
|
@@ -110,7 +122,10 @@ describe("Go Plugin", () => {
|
|
|
110
122
|
`go build -ldflags="-s -w" -o .bin/testFunc5 functions/func5/main.go`,
|
|
111
123
|
{
|
|
112
124
|
cwd: ".",
|
|
113
|
-
env: {
|
|
125
|
+
env: {
|
|
126
|
+
...process.env,
|
|
127
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
128
|
+
},
|
|
114
129
|
},
|
|
115
130
|
);
|
|
116
131
|
|
|
@@ -122,7 +137,10 @@ describe("Go Plugin", () => {
|
|
|
122
137
|
`go build -ldflags="-s -w" -o .bin/testFunc6 functions/func6`,
|
|
123
138
|
{
|
|
124
139
|
cwd: ".",
|
|
125
|
-
env: {
|
|
140
|
+
env: {
|
|
141
|
+
...process.env,
|
|
142
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
143
|
+
},
|
|
126
144
|
},
|
|
127
145
|
);
|
|
128
146
|
});
|
|
@@ -155,7 +173,10 @@ describe("Go Plugin", () => {
|
|
|
155
173
|
`go build -o .bin/testFunc1 functions/func1/main.go`,
|
|
156
174
|
{
|
|
157
175
|
cwd: ".",
|
|
158
|
-
env: {
|
|
176
|
+
env: {
|
|
177
|
+
...process.env,
|
|
178
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
179
|
+
},
|
|
159
180
|
},
|
|
160
181
|
);
|
|
161
182
|
});
|
|
@@ -222,7 +243,10 @@ describe("Go Plugin", () => {
|
|
|
222
243
|
`go build -ldflags="-s -w" -o ../.bin/testFunc1 functions/func1/main.go`,
|
|
223
244
|
{
|
|
224
245
|
cwd: "gopath",
|
|
225
|
-
env: {
|
|
246
|
+
env: {
|
|
247
|
+
...process.env,
|
|
248
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
249
|
+
},
|
|
226
250
|
},
|
|
227
251
|
);
|
|
228
252
|
});
|
|
@@ -323,7 +347,10 @@ describe("Go Plugin", () => {
|
|
|
323
347
|
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
324
348
|
{
|
|
325
349
|
cwd: ".",
|
|
326
|
-
env: {
|
|
350
|
+
env: {
|
|
351
|
+
...process.env,
|
|
352
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
353
|
+
},
|
|
327
354
|
},
|
|
328
355
|
);
|
|
329
356
|
});
|
|
@@ -365,7 +392,10 @@ describe("Go Plugin", () => {
|
|
|
365
392
|
`go build -ldflags="-s -w" -o ../../.bin/testFunc1 .`,
|
|
366
393
|
{
|
|
367
394
|
cwd: "functions/func1",
|
|
368
|
-
env: {
|
|
395
|
+
env: {
|
|
396
|
+
...process.env,
|
|
397
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
398
|
+
},
|
|
369
399
|
},
|
|
370
400
|
);
|
|
371
401
|
|
|
@@ -377,7 +407,10 @@ describe("Go Plugin", () => {
|
|
|
377
407
|
`go build -ldflags="-s -w" -o ../../.bin/testFunc2 main.go`,
|
|
378
408
|
{
|
|
379
409
|
cwd: "functions/func2",
|
|
380
|
-
env: {
|
|
410
|
+
env: {
|
|
411
|
+
...process.env,
|
|
412
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
413
|
+
},
|
|
381
414
|
},
|
|
382
415
|
);
|
|
383
416
|
});
|
|
@@ -503,14 +536,20 @@ describe("Go Plugin", () => {
|
|
|
503
536
|
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
504
537
|
{
|
|
505
538
|
cwd: ".",
|
|
506
|
-
env: {
|
|
539
|
+
env: {
|
|
540
|
+
...process.env,
|
|
541
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
542
|
+
},
|
|
507
543
|
},
|
|
508
544
|
);
|
|
509
545
|
expect(execStub).to.have.been.calledWith(
|
|
510
546
|
`go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`,
|
|
511
547
|
{
|
|
512
548
|
cwd: ".",
|
|
513
|
-
env: {
|
|
549
|
+
env: {
|
|
550
|
+
...process.env,
|
|
551
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
552
|
+
},
|
|
514
553
|
},
|
|
515
554
|
);
|
|
516
555
|
|
|
@@ -557,7 +596,10 @@ describe("Go Plugin", () => {
|
|
|
557
596
|
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
558
597
|
{
|
|
559
598
|
cwd: ".",
|
|
560
|
-
env: {
|
|
599
|
+
env: {
|
|
600
|
+
...process.env,
|
|
601
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
602
|
+
},
|
|
561
603
|
},
|
|
562
604
|
);
|
|
563
605
|
|
|
@@ -604,11 +646,62 @@ describe("Go Plugin", () => {
|
|
|
604
646
|
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`,
|
|
605
647
|
{
|
|
606
648
|
cwd: ".",
|
|
607
|
-
env: {
|
|
649
|
+
env: {
|
|
650
|
+
...process.env,
|
|
651
|
+
...{ CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64" },
|
|
652
|
+
},
|
|
608
653
|
},
|
|
609
654
|
);
|
|
610
655
|
|
|
611
656
|
// Total calls should be 3: 2 beforeBuild commands + 1 compilation command
|
|
612
657
|
expect(execStub.callCount).to.equal(3);
|
|
613
658
|
});
|
|
659
|
+
it("Does not leak arm64 GOARCH into x86_64 functions", async () => {
|
|
660
|
+
config.service.setFunctions({
|
|
661
|
+
armFunc: {
|
|
662
|
+
name: "armFunc",
|
|
663
|
+
runtime: "provided.al2023",
|
|
664
|
+
architecture: "arm64",
|
|
665
|
+
handler: "functions/arm",
|
|
666
|
+
},
|
|
667
|
+
x86Func: {
|
|
668
|
+
name: "x86Func",
|
|
669
|
+
runtime: "provided.al2023",
|
|
670
|
+
handler: "functions/x86",
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
const plugin = new Go(config);
|
|
675
|
+
plugin.exec = execStub;
|
|
676
|
+
plugin.readFileSync = readFileSyncStub;
|
|
677
|
+
plugin.zip = admZipStub;
|
|
678
|
+
|
|
679
|
+
await plugin.compileFunctions();
|
|
680
|
+
|
|
681
|
+
const envOf = (bin) =>
|
|
682
|
+
execStub.getCalls().find((c) => c.args[0].includes(bin)).args[1].env;
|
|
683
|
+
expect(envOf(".bin/armFunc").GOARCH).to.equal("arm64");
|
|
684
|
+
expect(envOf(".bin/x86Func").GOARCH).to.equal("amd64");
|
|
685
|
+
expect(plugin.config.env.GOARCH).to.equal(undefined);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("Produces byte-identical zips for identical binaries", async () => {
|
|
689
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sp-go-"));
|
|
690
|
+
const bin = path.join(dir, "fn");
|
|
691
|
+
fs.writeFileSync(bin, "fake binary content");
|
|
692
|
+
config.service.setFunctions({ fn: { name: "fn", handler: "fn" } });
|
|
693
|
+
|
|
694
|
+
const plugin = new Go(config);
|
|
695
|
+
plugin.zip = new AdmZip();
|
|
696
|
+
|
|
697
|
+
plugin.packageBootstrap("fn", bin);
|
|
698
|
+
const first = fs.readFileSync(bin + ".zip");
|
|
699
|
+
await new Promise((r) => setTimeout(r, 2100)); // DOS mtime has 2s resolution
|
|
700
|
+
plugin.zip = new AdmZip();
|
|
701
|
+
plugin.packageBootstrap("fn", bin);
|
|
702
|
+
const second = fs.readFileSync(bin + ".zip");
|
|
703
|
+
|
|
704
|
+
fs.rmSync(dir, { recursive: true });
|
|
705
|
+
expect(first.equals(second)).to.equal(true);
|
|
706
|
+
}).timeout(5000);
|
|
614
707
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-plugin-go",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Serverless Framework plugin that compiles Go functions on the fly",
|
|
6
6
|
"main": "index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"eslint-plugin-import": "^2.29.0",
|
|
55
55
|
"eslint-plugin-prettier": "^5.0.1",
|
|
56
56
|
"fs": "^0.0.1-security",
|
|
57
|
-
"mocha": "^
|
|
57
|
+
"mocha": "^12.0.3",
|
|
58
58
|
"prettier": "^3.0.3",
|
|
59
59
|
"sinon": "^17.0.0",
|
|
60
60
|
"sinon-chai": "^3.7.0"
|