Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify multiple runtimes #23

Merged
merged 2 commits into from
Feb 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ custom:
cgo: 0 # CGO_ENABLED flag
cmd: GOOS=linux go build -ldflags="-s -w"' # compile command
monorepo: false # if enabled, builds function every directory (useful for monorepo where go.mod is managed by each function
supportedRuntimes: ["go1.x"] # the plugin compiles a function only if runtime is declared here (either on function or provider level)
buildProvidedRuntimeAsBootstrap: false # if enabled, builds and archive function with only single "bootstrap" binary (useful for runtimes like provided.al2)
```

## How does it work?

The plugin compiles every Go function defined in `serverless.yaml` into `.bin` directory. After that it internally changes `handler` so that the Serverless Framework will deploy the compiled file not the source file. The plugin compiles a function only if `runtime` (either on function or provider level) is set to Go (`go1.x`).
The plugin compiles every Go function defined in `serverless.yaml` into `.bin` directory. After that it internally changes `handler` so that the Serverless Framework will deploy the compiled file not the source file.

For every matched function it also overrides `package` parameter to

Expand All @@ -69,3 +71,16 @@ exclude:
include:
- `<path to the compiled file and any files that you defined to be included>`
```

## How to run Golang Lambda on ARM?

1. Add `provided.al2` to `supportedRuntimes` and enable `buildProvidedRuntimeAsBootstrap` in plugin config
2. Append `GOARCH=arm64` to your compile command (`cmd` line)
3. Change architecture and runtime in global config:
```yaml
provider:
architecture: arm64
runtime: provided.al2
```

**Warning!** First deploy may result in small downtime (~few seconds) of lambda, use some deployment strategy like canary for safer rollout.
37 changes: 30 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ const os = require("os");
const prettyHrtime = require("pretty-hrtime");
const chalk = require("chalk");
const path = require("path");
const AdmZip = require("adm-zip");
const { readFileSync } = require("fs");

const ConfigDefaults = {
baseDir: ".",
binDir: ".bin",
cgo: 0,
cmd: 'GOOS=linux go build -ldflags="-s -w"',
monorepo: false,
supportedRuntimes: ["go1.x"],
buildProvidedRuntimeAsBootstrap: false,
};

const GoRuntime = "go1.x";
// amazonProvidedRuntimes contains Amazon Linux runtimes. Update this array after each new version release.
const amazonProvidedRuntimes = ["provided.al2"];

module.exports = class Plugin {
constructor(serverless, options) {
Expand Down Expand Up @@ -97,7 +102,7 @@ module.exports = class Plugin {
const config = this.getConfig();

const runtime = func.runtime || this.serverless.service.provider.runtime;
if (runtime !== GoRuntime) {
if (!config.supportedRuntimes.includes(runtime)) {
return;
}

Expand Down Expand Up @@ -143,11 +148,8 @@ module.exports = class Plugin {
binPath = binPath.replace(/\\/g, "/");
}
this.serverless.service.functions[name].handler = binPath;
const packageConfig = {
individually: true,
exclude: [`./**`],
include: [binPath],
};
const packageConfig = this.generatePackageConfig(runtime, config, binPath);

if (this.serverless.service.functions[name].package) {
packageConfig.include = packageConfig.include.concat(
this.serverless.service.functions[name].package.include
Expand All @@ -156,6 +158,27 @@ module.exports = class Plugin {
this.serverless.service.functions[name].package = packageConfig;
}

generatePackageConfig(runtime, config, binPath) {
if (
config.buildProvidedRuntimeAsBootstrap &&
amazonProvidedRuntimes.includes(runtime)
) {
const zip = new AdmZip();
zip.addFile("bootstrap", readFileSync(binPath), "", 0o755);
const zipPath = binPath + ".zip";
zip.writeZip(zipPath);
return {
individually: true,
artifact: zipPath,
};
}
return {
individually: true,
exclude: [`./**`],
include: [binPath],
};
}

getConfig() {
let config = ConfigDefaults;
if (this.serverless.service.custom && this.serverless.service.custom.go) {
Expand Down
103 changes: 103 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@ describe("Go Plugin", () => {
let sandbox;
let execStub;
let Plugin;
let readFileSyncStub;

beforeEach(() => {
sandbox = sinon.createSandbox();
execStub = sandbox.stub().resolves({ stdin: null, stdout: null });
readFileSyncStub = sinon
.stub()
.withArgs(".bin/testFunc1")
.returns("fake binary content");
const admZipInstance = {
addFile: sinon.stub(),
writeZip: sinon.stub(),
};
const admZipStub = sinon.stub().callsFake(() => admZipInstance);
Plugin = proxyquire("./index.js", {
fs: { readFileSync: readFileSyncStub },
"adm-zip": admZipStub,
util: {
promisify: () => execStub,
},
Expand Down Expand Up @@ -75,6 +87,52 @@ describe("Go Plugin", () => {
);
});

it("compiles function with supported runtime", async () => {
// given
const config = merge(
{
service: {
custom: {
go: {
supportedRuntimes: ["go1.x", "provided.al2"],
},
},
functions: {
testFunc1: {
name: "testFunc1",
runtime: "provided.al2",
handler: "functions/func1/main.go",
},
testFunc2: {
name: "testFunc2",
runtime: "go1.x",
handler: "functions/func2/main.go",
},
},
},
},
serverlessStub
);
const plugin = new Plugin(config);

// when
await plugin.hooks["before:package:createDeploymentArtifacts"]();

// then
expect(config.service.functions.testFunc2.handler).to.equal(
`.bin/testFunc2`
);
expect(execStub).to.have.been.calledWith(
`go build -ldflags="-s -w" -o .bin/testFunc2 functions/func2/main.go`
);
expect(config.service.functions.testFunc1.handler).to.equal(
`.bin/testFunc1`
);
expect(execStub).to.have.been.calledWith(
`go build -ldflags="-s -w" -o .bin/testFunc1 functions/func1/main.go`
);
});

it("compiles Go function w/ custom command", async () => {
// given
const config = merge(
Expand Down Expand Up @@ -332,6 +390,51 @@ describe("Go Plugin", () => {
);
expect(execStub.secondCall.args[1].cwd).to.equal("functions/func2");
});

it("compiles bootstrap", async () => {
// given
const config = merge(
{
service: {
custom: {
go: {
supportedRuntimes: ["go1.x", "provided.al2"],
buildProvidedRuntimeAsBootstrap: true,
},
},
functions: {
testFunc1: {
name: "testFunc1",
runtime: "provided.al2",
handler: "functions/func1",
},
testFunc2: {
name: "testFunc2",
runtime: "go1.x",
handler: "functions/func1/main.go",
},
},
},
},
serverlessStub
);
const plugin = new Plugin(config);

// when
await plugin.hooks["before:package:createDeploymentArtifacts"]();

// then
expect(config.service.functions.testFunc1.package).to.deep.equal({
individually: true,
artifact: ".bin/testFunc1.zip",
});

expect(config.service.functions.testFunc2.package).to.deep.equal({
individually: true,
exclude: ["./**"],
include: [".bin/testFunc2"],
});
});
});

describe(`serverless go build`, () => {
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"homepage": "https://github.com/mthenw/serverless-go-plugin#readme",
"dependencies": {
"adm-zip": "^0.5.9",
"chalk": "^2.4.2",
"lodash.merge": "^4.6.2",
"p-map": "^3.0.0",
Expand Down