Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ Migrate AWS Lambda function code to Azure Functions.

- If runtime is Python or Node.js: **do NOT create function.json files**
- If runtime is .NET (in-process or isolated) or Java: **do NOT hand-author function.json** — bindings metadata is generated from attributes/annotations at build time
- If runtime is Go: **do NOT create function.json files** — triggers are declared in code via `sdk.FunctionApp()` functional options and indexed by the Go worker at startup. See [runtimes/go.md](runtimes/go.md).
- Use extension bundle version `[4.*, 5.0.0)` in host.json
- Use latest programming model (v4 for JavaScript, v2 for Python)
- **Always use bindings and triggers instead of SDKs** — For blob read/write, use `input.storageBlob()` / `output.storageBlob()` with `extraInputs`/`extraOutputs`. For queues, use `app.storageQueue()` or `app.serviceBusQueue()`. Only use SDK when there is no equivalent binding (e.g., Azure AI, custom HTTP calls)
- **Go exception**: The Go worker (preview) supports **triggers only** — the sole output binding is HTTP. All other I/O (blob writes, queue sends, cosmos upserts, service bus sends, event grid publishes, table reads/writes, etc.) uses the Azure SDK for Go with `DefaultAzureCredential`. See [runtimes/go.md](runtimes/go.md#io-outside-of-triggers--use-the-azure-sdk-for-go) for the full SDK-vs-binding capability matrix and idiomatic patterns.
- **Always use the latest supported language runtime** — Consult [supported languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages) and select the newest GA version. Do NOT default to an older LTS version when a newer version is available on Azure Functions.
- **Preview runtimes require explicit user confirmation** — Go is currently in **public preview** on Azure Functions. Before selecting Go as the target runtime, use `ask_user` to confirm the user accepts a preview runtime (API surface may change; not covered by production SLA). Also verify Core Tools ≥ 4.12 is installed before running `func init --worker-runtime go`.

## Steps

Expand Down Expand Up @@ -119,6 +122,7 @@ Load the appropriate runtime reference for the target language:
| Python (v2) | [runtimes/python.md](runtimes/python.md) |
| C# (Isolated Worker) | [runtimes/csharp.md](runtimes/csharp.md) |
| Java | [runtimes/java.md](runtimes/java.md) |
| Go (preview) | [runtimes/go.md](runtimes/go.md) |
| PowerShell | [runtimes/powershell.md](runtimes/powershell.md) |

## Scenario-Specific Guidance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ Always use `ask_user` before:
- Selecting Azure region/location
- Deploying infrastructure
- Making breaking changes to existing code
- **Selecting a preview-status language runtime** (e.g., Go on Azure Functions). Confirm the user accepts that the runtime's API surface may change and is not covered by the production SLA before scaffolding the project.

## Best Practices

- Always use `mcp_azure_mcp_get_azure_bestpractices` tool before generating Azure code
- Prefer managed identity over connection strings
- **Always use the latest supported language runtime** — check [supported languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages) for the newest GA version. Never default to older versions
- **Always prefer bindings over SDKs** — use `input.storageBlob()`, `output.storageBlob()`, `app.storageQueue()`, etc. instead of `BlobServiceClient`, `QueueClient`, or other SDK clients. Only use SDK when no binding exists for the service
- **Go exception**: The Azure Functions Go worker (preview) supports **triggers only**; HTTP is the only output binding. All other I/O (blob, queue, cosmos, service bus, event hub, event grid, table, SQL writes) goes through the Azure SDK for Go with `DefaultAzureCredential`. This is intentional and idiomatic for Go — do not attempt to synthesize non-existent Go bindings. See [runtimes/go.md](runtimes/go.md#io-outside-of-triggers--use-the-azure-sdk-for-go).
- Follow Azure naming conventions
- Use Flex Consumption hosting plan for new Functions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ Detailed guidance for migrating AWS Lambda functions to Azure Functions.

## Programming Model Mapping

| AWS Lambda | Azure Functions |
|------------|-----------------|
| `exports.handler` | `app.http()`, `app.storageBlob()`, etc. (v4) |
| `event` object | `request` / `blob` / trigger-specific param |
| `context` object | `context` (InvocationContext) |
| `callback` | Return value |
| `function.json` (v1-v3) | Inline bindings in code (v4 JS, v2 Python) |
| AWS Lambda | Azure Functions (JS v4 / Python v2) | Azure Functions (Go worker, preview) |
|------------|-----------------|-----------------|
| `exports.handler` | `app.http()`, `app.storageBlob()`, etc. (v4) | `app.HTTP(name, handler, opts...)`, `app.Blob(...)`, `app.Queue(...)`, etc. |
| `event` object | `request` / `blob` / trigger-specific param | Typed struct param (e.g., `bindings.QueueMessage`, `bindings.EventGridEvent`, `*blob.Client` for the blob extension trigger) |
| `context` object | `context` (InvocationContext) | `context.Context` (with invocation metadata via `sdk.FromContext(ctx)`) |
| `callback` | Return value | `error` return value (`nil` = success, non-nil = host retries per trigger policy) |
| `function.json` (v1-v3) | Inline bindings in code (v4 JS, v2 Python) | Declared in code via `sdk.FunctionApp()` + functional options; no `function.json` |
| Lambda destinations (on failure → SQS/SNS) | Trigger-specific retry + poison queue | Non-nil `error` return → host retries; poison-message behavior per trigger. See [runtimes/go.md](runtimes/go.md) |

## Trigger Mapping

Expand All @@ -52,6 +53,7 @@ For language-specific migration rules, correct/incorrect patterns, and code exam
| TypeScript (v4) | [runtimes/typescript.md](runtimes/typescript.md) |
| C# (Isolated Worker) | [runtimes/csharp.md](runtimes/csharp.md) |
| Java | [runtimes/java.md](runtimes/java.md) |
| Go (preview) | [runtimes/go.md — Lambda Migration Rules](runtimes/go.md#lambda-migration-rules) |
| PowerShell | [runtimes/powershell.md](runtimes/powershell.md) |

## Project Structure
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Go — Azure Functions Go Worker Triggers & Bindings

> **Model**: [`github.com/azure/azure-functions-golang-worker`](https://github.com/Azure/azure-functions-golang-worker) (**preview**).
> No `function.json` — triggers are declared in code via `sdk.FunctionApp()` + functional options.
> Entry point: `main.go` calling `worker.Start(app)`.
> Requires **Azure Functions Core Tools ≥ 4.12** (`npm i -g azure-functions-core-tools@4 --unsafe-perm true`).

## Project Layout

**`main.go`, `go.mod`, and `host.json` must live in the same directory** (project root). `func start` runs `go build` from `host.json`'s directory — `.go` files under `src/` produce `no Go files in <dir>` and the build fails. Sub-packages go in `internal/<role>/`. This overrides the generic `src/` layout in [lambda-to-functions.md § Project Structure](../lambda-to-functions.md#project-structure). Full layout, anti-pattern, and rules → [go/project-layout.md](./go/project-layout.md).

## Project Setup

Run these commands **from the project root** (see [Project Layout](#project-layout)):

```bash
func init --worker-runtime go
# Discover available versions first, then pin explicitly (see note below).
go list -m -versions github.com/azure/azure-functions-golang-worker
go get github.com/azure/azure-functions-golang-worker@main
go mod tidy
```

`func init --worker-runtime go` generates `host.json`, `local.settings.json`, and `.gitignore`. Verified templates + per-setting reference → [go/setup-templates.md](./go/setup-templates.md).

> ⚠️ **Never hand-author `require ... v0.0.0`** — the module is preview-only, has no `v0.0.0` tag, and sub-packages share the parent's version (no separate `require` lines). Let `go get @main` (or `@vX.Y.Z-preview`) resolve it. Full guidance → [go/version-pinning.md](./go/version-pinning.md).

## Lambda Migration Rules

> Shared rules (bindings over SDKs, latest runtime, identity-first auth) → [global-rules.md](../global-rules.md)

Go-specific:
- **`main.go`, `go.mod`, `host.json` co-located at project root** — never `src/`. See [Project Layout](#project-layout).
- **Non-HTTP output bindings unsupported** — use the Azure SDK for Go with `DefaultAzureCredential`.
- **Wrap every user-spawned goroutine** with `sdk.Recover` / `sdk.RecoverTo`. An unrecovered panic crashes the whole worker.
- Prefer **core triggers**. Use the **Blob extension trigger** (blank import) only when you need a live `*blob.Client` for streaming.
- Log with `slog.InfoContext(ctx, ...)`. The SDK's handler attaches `invocation_id`, `function_name`, `trigger_type` automatically.

## Triggers

Each entry links to a runnable `main.go`-style sample. All non-HTTP handlers return `error` — see [Handler Return Values & Retry Semantics](#handler-return-values--retry-semantics) for what the value means.

| Trigger | SDK method | Sample |
| --- | --- | --- |
| HTTP | `app.HTTP` | [triggers/http.md](./go/triggers/http.md) |
| Blob Storage (extension) | `app.Blob` | [triggers/blob.md](./go/triggers/blob.md) |
| Queue Storage | `app.Queue` | [triggers/queue.md](./go/triggers/queue.md) |
| Timer | `app.Timer` | [triggers/timer.md](./go/triggers/timer.md) |
| Event Grid | `app.EventGrid` | [triggers/event-grid.md](./go/triggers/event-grid.md) |
| Cosmos DB (change feed) | `app.CosmosDB` | [triggers/cosmos.md](./go/triggers/cosmos.md) |
| Service Bus (queue / topic) | `app.ServiceBusQueue`, `app.ServiceBusTopic` | [triggers/service-bus.md](./go/triggers/service-bus.md) |
| Event Hubs | `app.EventHub` | [triggers/event-hubs.md](./go/triggers/event-hubs.md) |
| SQL (change tracking) | `app.SQL` | [triggers/sql.md](./go/triggers/sql.md) |

## I/O Outside of Triggers — Use the Azure SDK for Go

The Go worker is **triggers-only by design** — no input bindings, no non-HTTP output bindings. This is the intentional exception to the "bindings over SDKs" rule in [global-rules.md](../global-rules.md); all non-HTTP I/O uses the Azure SDK for Go with `DefaultAzureCredential`.

## SDK Patterns for I/O

Ground rules (credentials, client lifetime, URL app settings) and per-service samples (Blob, Queue, Table, Cosmos, Service Bus, Event Hubs, Event Grid) → [go/sdk-patterns/README.md](./go/sdk-patterns/README.md).

## Core vs Extension Triggers

Most triggers are **core** (typed payload in gRPC message, no external SDK). **Blob** is the only extension trigger — requires blank import and streaming via `azblob`. Decision table → [go/core-vs-extension.md](./go/core-vs-extension.md).

## Handler Return Values & Retry Semantics

Non-HTTP handlers return `error`; the Functions host interprets the value according to the trigger's built-in retry behavior or configured function-level retry policy. Full per-trigger table (Storage Queue → `<name>-poison`, Service Bus → DLQ, Event Grid → 24 h backoff, Event Hubs / Cosmos → function-level retry policies) → [go/retry-semantics.md](./go/retry-semantics.md).

## Panic Recovery in Goroutines

An unrecovered panic in **any** goroutine terminates the entire worker process and fails every concurrent invocation. Always guard user-spawned goroutines with `sdk.Recover` (best-effort) or `sdk.RecoverTo` (propagates as handler error, triggers host retry). Full patterns including `errgroup` composition → [go/panic-recovery.md](./go/panic-recovery.md).

## Logging

The SDK installs an `slog` handler at package init. Records automatically carry `invocation_id`, `function_name`, `trigger_type`.

```go
slog.InfoContext(ctx, "processed order", "order_id", id, "amount", amount)
```

Richer metadata (trace parent, retry count) via `sdk.FromContext(ctx)`. Distributed tracing → upstream [`samples/otelTracing`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/otelTracing), [`samples/collectorToAzureMonitor`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/collectorToAzureMonitor).

## Build & Hosting Constraints

- **Hosting plan**: Flex Consumption only.
- **Binary format**: Linux ELF named **exactly `app`** (lowercase, no extension) at the deployment zip root. Host executes `/home/site/wwwroot/app` — any other name/location fails to start. `func pack` produces this; for hand-builds see [go/deployment.md § Hand-building the binary](./go/deployment.md#hand-building-the-binary).
- **`CGO_ENABLED=0`** — Flex Consumption base image has no C toolchain. Pick pure-Go alternatives (e.g., non-cgo SQLite driver).

## Local Run & Deployment

```bash
func start # auto-compiles the Go module, hosts locally
func pack # cross-compiles (`CGO_ENABLED=0 GOOS=linux GOARCH=amd64`) and packages an app-at-root zip
```

The `func pack` zip works with any Functions deployment path (`azd deploy`, `func azure functionapp publish`, `az functionapp deployment source config-zip`). Full deploy commands and the Windows/`Compress-Archive` executable-bit gotcha → [go/deployment.md](./go/deployment.md).

> Full reference: [Azure Functions Go developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-go) — and the [azure-functions-golang-worker README](https://github.com/Azure/azure-functions-golang-worker) / [samples/](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples) for the preview SDK surface.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Core vs Extension Triggers

Most Go worker triggers are **core** — payload arrives as a typed struct in the gRPC invocation message, no external SDK needed, no explicit activation. **Blob** is the only **extension** trigger today.

| Criterion | Core (HTTP, Timer, Queue, CosmosDB, EventGrid, EventHub, ServiceBus, SQL) | Extension (Blob) |
| --- | --- | --- |
| Payload size | Bounded (KB–low MB) | Potentially GBs |
| External SDK | No | Yes (`azblob`, `azidentity`) |
| Data in gRPC message | Yes — typed struct | Metadata only; stream via `client.DownloadStream` |
| Activation | Automatic | Blank import: `_ ".../triggers/blob"` |

**Rule**: prefer core triggers. Use the Blob extension trigger only when you need a live `*blob.Client` for streaming.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Local Run & Deployment — Go

```bash
func start # auto-compiles the Go module, hosts locally
func pack # produces a zip with the cross-compiled `app` binary at the root
```

Run `func pack` from a directory scaffolded by `func init --worker-runtime go` — it handles the `CGO_ENABLED=0 GOOS=linux GOARCH=amd64` cross-compile and packages the artifact for you. The resulting zip works with any Functions deployment path: `azd deploy`, `func azure functionapp publish`, or [zip push deployment](https://learn.microsoft.com/en-us/azure/azure-functions/deployment-zip-push):

```bash
az functionapp deployment source config-zip \
-g <RG> -n <APP_NAME> --src <appname>.zip
```

## Infrastructure

Go on Flex Consumption requires runtime-specific IaC settings (`runtime.name='go'`, `runtime.version='1.0'`, `http20Enabled=false`, no `FUNCTIONS_WORKER_RUNTIME` app setting). For infrastructure creation and full deployment via `azd up`, hand off to the `azure-prepare` skill. Discover the current Go templates with `functions_template_get(language: "go")`; use a matching published template when available, otherwise generate equivalent Bicep or Terraform with the settings above.

## Hand-building the binary

⚠️ **The compiled binary MUST be named exactly `app`** (lowercase, no extension) and sit at the **root** of the deployment zip. The Flex Consumption host executes `/home/site/wwwroot/app` — any other filename or location fails to start with no useful error.

`func pack` handles this for you. If you build by hand, use one of:

```bash
# Path A — you invoke `func pack --no-build` afterwards.
# func pack expects the binary at bin/app (matches its own local layout).
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/app .
func pack --no-build

# Path B — you zip the artifact yourself. Build to app at project root, then zip so `app` is at zip root.
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
zip -X deploy.zip app host.json # `-X` preserves Unix perms; add other runtime files as needed
```

**Anti-patterns** (all fail at start with no diagnostic):

| Command | Produces | Why it fails |
| --- | --- | --- |
| `go build .` | `<module-name>` or `<module-name>.exe` | Wrong filename |
| `go build -o myapp .` | `myapp` | Wrong filename |
| `go build -o app.exe .` | `app.exe` | Windows extension |
| `go build -o bin/app .` then zipping `bin/` into the archive | `bin/app` at zip root | Binary must be at zip root, not in `bin/` |

## Hand-rolling the zip

If you build the deployment zip yourself (e.g., a Windows CI job that skips `func pack`), the `app` entry must carry Unix executable permissions (mode `0755` or `0777`) in the zip's external attributes — this is a zip-format-level bit, not an NTFS ACL. Windows tools like PowerShell's `Compress-Archive` and Explorer's "Send to → Compressed folder" emit DOS-mode zips with no Unix permission bits; the host will fail to exec `app` on Linux with a permission-denied error.

Use one of:
- `func pack` — works on any host OS and stamps the bits correctly (preferred).
- WSL's `zip` (or Linux/macOS `zip -X`).
- A CI step that explicitly sets the executable bit before/after zipping.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Panic Recovery in Goroutines — Go

An unrecovered panic in **any** goroutine terminates the entire worker process, failing every concurrent invocation across every function on that worker. Always guard goroutines you start yourself.

**Best-effort work** (`sdk.Recover`) — fire-and-forget, keeps the worker alive:

```go
go func() {
defer sdk.Recover(ctx) // must be the FIRST defer (runs LAST)
defer wg.Done()
warmCache(ctx)
}()
```

**Failure-propagating work** (`sdk.RecoverTo` — preferred pattern uses `errgroup`):

```go
import "golang.org/x/sync/errgroup"

func onEventHub(ctx context.Context, events []bindings.EventHubMessage) error {
g, ctx := errgroup.WithContext(ctx)
for _, e := range events {
e := e
g.Go(func() (err error) {
defer sdk.RecoverTo(ctx, &err)
return process(ctx, e)
})
}
return g.Wait() // non-nil -> invocation fails -> host retries
}
```

See [retry-semantics.md](./retry-semantics.md) for how the returned error is interpreted by the host.
Loading
Loading