Skip to content
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
57 changes: 52 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="./doc/scud-logo.png" height="240" />
<img src="./doc/scud-logo-v2.svg" height="240" />
<h3 align="center">scud</h3>
<p align="center"><strong>serverless Golang made simple, secure, and fast</strong></p>

Expand Down Expand Up @@ -47,6 +47,7 @@ With SCUD, you can deliver fast, secure, and maintainable serverless APIs withou
- [Linker Flags and Version Injection](#linker-flags-and-version-injection)
- [Lambda Environment Variables](#lambda-environment-variables)
- [Architecture: Graviton vs x86\_64](#architecture-graviton-vs-x86_64)
- [CGO / C Libraries](#cgo--c-libraries)
- [Container images](#container-images)
- [Universal Function](#universal-function)
- [Custom Go Environment](#custom-go-environment)
Expand Down Expand Up @@ -133,9 +134,11 @@ scud.NewFunctionGo(stack, jsii.String("Handler"),
SourceCodeModule: "github.com/fogfish/scud",
SourceCodeLambda: "test/lambda/go",
SourceCodeVersion: "v1.2.3", // Injects -X main.version=v1.2.3
GoVar: map[string]string{
"main.buildTime": "2024-01-01", // Injects -X main.buildTime=2024-01-01
"main.commit": "abc123", // Injects -X main.commit=abc123
Toolchain: &scud.Toolchain{
LDVars:: map[string]string{
"main.buildTime": "2024-01-01", // Injects -X main.buildTime=2024-01-01
"main.commit": "abc123", // Injects -X main.commit=abc123
},
},
},
)
Expand Down Expand Up @@ -185,11 +188,55 @@ scud.NewFunctionGo(scope, jsii.String("test"),
&scud.FunctionGoProps{
SourceCodeModule: "github.com/fogfish/scud",
SourceCodeLambda: "test/lambda/go",
GoEnv: map[string]string{"GOARCH": "amd64"},
Toolchain: &scud.Toolchain{
GoEnv: map[string]string{"GOARCH": "amd64"},
},
},
)
```

### CGO / C Libraries

CGO is disabled by default. Use standard Golang environment variable `"CGO_ENABLED"` to enable.

```go
scud.NewFunctionGo(scope, jsii.String("test"),
&scud.FunctionGoProps{
SourceCodeModule: "github.com/fogfish/scud",
SourceCodeLambda: "test/lambda/go",
Toolchain: &scud.Toolchain{
GoEnv: map[string]string{"CGO_ENABLED": "1"},
},
},
)
```

When developing on macOS, building Go binaries that depend on CGO or C libraries may fail or produce incompatible artifacts. Use a Linux cross-compiler (e.g. musl toolchain).

```bash
brew install FiloSottile/musl-cross/musl-cross
```

```go
scud.NewFunctionGo(scope, jsii.String("test"),
&scud.FunctionGoProps{
SourceCodeModule: "github.com/fogfish/scud",
SourceCodeLambda: "test/lambda/go",
Toolchain: &scud.Toolchain{
GoEnv: map[string]string{
"CGO_ENABLED": "1",
"CC": "aarch64-linux-musl-gcc",
},
LDVars: []string{"-linkmode external", `-extldflags "-static"`},
},
},
)
```

Known limitations
* Some C libraries cannot be statically linked
* Libraries that require glibc may not build correctly on macOS
* In such cases, builds must be performed on a Linux system

### Container images

Expand Down
18 changes: 5 additions & 13 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,8 @@ type ContainerGoProps struct {
// -ldflags '-X main.version=...'
SourceCodeVersion string

// Variables and its values passed as linker flags
// -ldflags '-X key1=val1 -X key2=val2 ...'
GoVar map[string]string

// Go environment, default includes
// GOOS=linux
// GOARCH=arm64
// CGO_ENABLED=0
GoEnv map[string]string
// Toolchain configuration for building Go Lambda function
Toolchain *Toolchain

// Static files included into container, the path is relative to module
StaticAssets []string
Expand Down Expand Up @@ -92,8 +85,8 @@ func NewContainerGo(scope constructs.Construct, id *string, spec *ContainerGoPro
platContainer := "linux/arm64"
platCode := awsecrassets.Platform_LINUX_ARM64()
props.Architecture = awslambda.Architecture_ARM_64()
if spec.GoEnv != nil {
switch spec.GoEnv["GOARCH"] {
if spec.Toolchain != nil && spec.Toolchain.GoEnv != nil {
switch spec.Toolchain.GoEnv["GOARCH"] {
case "amd64":
platContainer = "linux/amd64"
props.Architecture = awslambda.Architecture_X86_64()
Expand All @@ -109,8 +102,7 @@ func NewContainerGo(scope constructs.Construct, id *string, spec *ContainerGoPro
spec.SourceCodeModule,
spec.SourceCodeLambda,
spec.SourceCodeVersion,
spec.GoVar,
spec.GoEnv,
spec.Toolchain,
)

path := filepath.Join(os.TempDir(), spec.SourceCodeModule, spec.SourceCodeLambda)
Expand Down
51 changes: 51 additions & 0 deletions doc/scud-logo-v2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 36 additions & 20 deletions gocc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ import (
"github.com/aws/jsii-runtime-go"
)

// Configure Go compiler and Linter
type Toolchain struct {
// Go environment, default includes
// GOOS=linux
// GOARCH=arm64
// CGO_ENABLED=0
GoEnv map[string]string

// Linker flags passed to the Go linker (-ldflags).
LDFlags []string

// Go variables injected at link time using -X key=value.
// Example: -ldflags "-X main.version=1.0.0"
LDVars map[string]string
}

type GoCompiler struct {
sourceCode string
sourceCodePackage string
sourceCodeLambda string
sourceCodeVersion string
govar map[string]string
goenv map[string]string
config *Toolchain
}

const goBinary = "bootstrap"
Expand All @@ -29,24 +44,22 @@ func NewGoCompiler(
sourceCodePackage string,
sourceCodeLambda string,
sourceCodeVersion string,
govar map[string]string,
goenv map[string]string,
config *Toolchain,
) *GoCompiler {
if goenv == nil {
goenv = map[string]string{}
}

if govar == nil {
govar = map[string]string{}
if config == nil {
config = &Toolchain{
GoEnv: map[string]string{},
LDVars: map[string]string{},
LDFlags: []string{},
}
}

return &GoCompiler{
sourceCode: filepath.Join(sourceCodePackage, sourceCodeLambda),
sourceCodePackage: sourceCodePackage,
sourceCodeLambda: sourceCodeLambda,
sourceCodeVersion: sourceCodeVersion,
govar: govar,
goenv: goenv,
config: config,
}
}

Expand All @@ -61,11 +74,14 @@ func (g *GoCompiler) TryBundle(outputDir *string, options *awscdk.BundlingOption
goflags := []string{"build", "-tags", "lambda.norpc"}

ldflags := []string{"-s", "-w"}
if len(g.config.LDFlags) > 0 {
ldflags = append(ldflags, g.config.LDFlags...)
}
if g.sourceCodeVersion != "" {
ldflags = append(ldflags, fmt.Sprintf("-X main.version=%s", g.sourceCodeVersion))
}

for name, value := range g.govar {
for name, value := range g.config.LDVars {
ldflags = append(ldflags, fmt.Sprintf("-X %s=%s", name, value))
}
goflags = append(goflags, "-ldflags", strings.Join(ldflags, " "))
Expand Down Expand Up @@ -117,8 +133,8 @@ func (g *GoCompiler) cmdEnv() []string {
"GOROOT",
"GOMODCACHE",
} {
if _, exists := g.goenv[envvar]; !exists {
g.goenv[envvar] = os.Getenv(envvar)
if _, exists := g.config.GoEnv[envvar]; !exists {
g.config.GoEnv[envvar] = os.Getenv(envvar)
}
}

Expand All @@ -127,21 +143,21 @@ func (g *GoCompiler) cmdEnv() []string {
"GOARCH": "arm64",
"CGO_ENABLED": "0",
} {
if _, exists := g.goenv[envvar]; !exists {
g.goenv[envvar] = defval
if _, exists := g.config.GoEnv[envvar]; !exists {
g.config.GoEnv[envvar] = defval
}
}

for envvar, gen := range map[string]func() string{
"GOCACHE": g.goCache,
} {
if _, exists := g.goenv[envvar]; !exists {
g.goenv[envvar] = gen()
if _, exists := g.config.GoEnv[envvar]; !exists {
g.config.GoEnv[envvar] = gen()
}
}

env := make([]string, 0)
for key, val := range g.goenv {
for key, val := range g.config.GoEnv {
env = append(env, key+"="+val)
}
return env
Expand Down
18 changes: 5 additions & 13 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,8 @@ type FunctionGoProps struct {
// -ldflags '-X main.version=...'
SourceCodeVersion string

// Variables and its values passed as linker flags
// -ldflags '-X key1=val1 -X key2=val2 ...'
GoVar map[string]string

// Go environment, default includes
// GOOS=linux
// GOARCH=arm64
// CGO_ENABLED=0
GoEnv map[string]string
// Toolchain configuration for building Go Lambda function
Toolchain *Toolchain
}

func (*FunctionGoProps) HKT1(awslambda.Function) {}
Expand Down Expand Up @@ -80,8 +73,8 @@ func NewFunctionGo(scope constructs.Construct, id *string, spec *FunctionGoProps

// arm64 is default deployment
props.Architecture = awslambda.Architecture_ARM_64()
if spec.GoEnv != nil {
switch spec.GoEnv["GOARCH"] {
if spec.Toolchain != nil && spec.Toolchain.GoEnv != nil {
switch spec.Toolchain.GoEnv["GOARCH"] {
case "amd64":
props.Architecture = awslambda.Architecture_X86_64()
case "arm64":
Expand All @@ -93,8 +86,7 @@ func NewFunctionGo(scope constructs.Construct, id *string, spec *FunctionGoProps
spec.SourceCodeModule,
spec.SourceCodeLambda,
spec.SourceCodeVersion,
spec.GoVar,
spec.GoEnv,
spec.Toolchain,
)
props.Code = AssetCodeGo(gocc)
props.Handler = jsii.String(goBinary)
Expand Down
Loading
Loading