From 6a6fe1dcd5b3f3380c938914c979192be742d0c5 Mon Sep 17 00:00:00 2001 From: Harshitha Vegaraju Date: Tue, 21 Jul 2026 11:15:36 -0700 Subject: [PATCH 1/2] Add Go (preview) runtime reference for Azure Functions migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 40bda0fe-2317-44bf-9727-7d60f896dc06 --- .../services/functions/code-migration.md | 4 + .../services/functions/global-rules.md | 2 + .../services/functions/lambda-to-functions.md | 16 +- .../services/functions/runtimes/go.md | 500 ++++++++++++++++++ 4 files changed, 515 insertions(+), 7 deletions(-) create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md index d15618360..75383bbdd 100644 --- a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md @@ -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.1 is installed before running `func init --worker-runtime go`. ## Steps @@ -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 diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/global-rules.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/global-rules.md index abf3e3f68..3ad20905a 100644 --- a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/global-rules.md +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/global-rules.md @@ -18,6 +18,7 @@ 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 @@ -25,6 +26,7 @@ Always use `ask_user` before: - 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 diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/lambda-to-functions.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/lambda-to-functions.md index 9c2e741b8..5fa66a0b4 100644 --- a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/lambda-to-functions.md +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/lambda-to-functions.md @@ -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 @@ -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 diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md new file mode 100644 index 000000000..f9e54aa43 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md @@ -0,0 +1,500 @@ +# 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.1** (`npm i -g azure-functions-core-tools@4 --unsafe-perm true`). + +## Project Setup + +```bash +func init --worker-runtime go +go get github.com/azure/azure-functions-golang-worker +go mod tidy +``` + +`func init --worker-runtime go` generates `host.json`, `local.settings.json`, and `.gitignore` pre-wired with `FUNCTIONS_WORKER_RUNTIME=native` and the correct Core Tools settings — avoids manual config drift. + +> **`local.settings.json` is local-only** — [Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-go) is explicit: "this file isn't published to Azure." The values below are for `func start`, not for the deployed app: +> - `FUNCTIONS_WORKER_RUNTIME=native` — tells Core Tools to load the "native" worker (Go plugs in as a language-agnostic native worker). Core Tools then infers Go by finding `go.mod` in the project. On the deployed **Flex Consumption** app, the runtime is configured via `functionAppConfig.runtime` on the ARM/Bicep resource — not as an app setting. +> - `FUNCTIONS_CLI_NATIVE_LANGUAGE=go` — written by `func init --worker-runtime go` but redundant in practice (Core Tools infers Go from `go.mod`). Not consumed by the deployed Functions host. +> - `AzureWebJobsStorage` — leave empty locally when no trigger needs host storage; set to `UseDevelopmentStorage=true` for Azurite. In Azure, provide the equivalent as an app setting (or better, use the identity-based form: `AzureWebJobsStorage__blobServiceUri` + `__credential=managedidentity` + `__clientId`). + +### Generated `host.json` + +Verified output of `func init --worker-runtime go` (Core Tools 4.12.0) — matches the shared extension bundle range in [code-migration.md](../code-migration.md): + +```json +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} +``` + +### Generated `local.settings.json` + +```json +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "native", + "FUNCTIONS_CLI_NATIVE_LANGUAGE": "go", + "AzureWebJobsStorage": "" + } +} +``` + +## Lambda Migration Rules + +> Shared rules (bindings over SDKs, latest runtime, identity-first auth) → [global-rules.md](../global-rules.md) + +Go-specific: +- **Output bindings other than HTTP are not supported** by the Go worker today. For queue/blob/cosmos/etc. writes, use the Azure SDK for Go directly with `DefaultAzureCredential`. +- **Wrap every user-spawned goroutine** with `sdk.Recover` or `sdk.RecoverTo` — an unrecovered panic in *any* goroutine crashes the whole worker and fails every in-flight invocation on it, not just the one that spawned the goroutine. +- Prefer **core triggers** (typed payloads, no external deps). Use the **Blob extension trigger** (blank import) only when you need a live `*blob.Client` for streaming. +- Log with `slog.InfoContext(ctx, ...)`. The SDK auto-installs an `slog` handler that attaches `invocation_id`, `function_name`, and `trigger_type` to every record. + +## HTTP Trigger + +```go +import ( + "net/http" + "github.com/azure/azure-functions-golang-worker/sdk" + "github.com/azure/azure-functions-golang-worker/worker" +) + +func hello(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello from Go!")) +} + +func main() { + app := sdk.FunctionApp() + app.HTTP("hello", hello, + sdk.WithMethods("GET", "POST"), + sdk.WithAuth("anonymous"), + // sdk.WithRoute("users/{id}"), + ) + worker.Start(app) +} +``` + +> The HTTP output binding is attached implicitly — write the response via `http.ResponseWriter`. + +> **HTTP middleware and streaming.** For wrapping handlers with middleware (timing, tracing, auth), see the upstream [`samples/middleware`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/middleware) sample. For streaming responses (Server-Sent Events, large-object downloads, chunked transfer), see [`samples/httpStreaming`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/httpStreaming) — the HTTP path uses a real loopback `http.Server`, so `http.Flusher`, chunked encoding, and trailers work natively. + +## Blob Storage (extension trigger) + +```go +import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/azure/azure-functions-golang-worker/sdk" + _ "github.com/azure/azure-functions-golang-worker/triggers/blob" // registers factory + "github.com/azure/azure-functions-golang-worker/worker" +) + +func onBlob(ctx context.Context, client *blob.Client) error { + get, err := client.DownloadStream(ctx, nil) + if err != nil { return err } + defer get.Body.Close() + // stream get.Body — supports GB-scale blobs without buffering through gRPC + return nil +} + +func main() { + app := sdk.FunctionApp() + app.Blob("processBlob", onBlob, + sdk.WithPath("samples-workitems/{name}"), + sdk.WithConnection("AzureWebJobsStorage"), + sdk.WithSource("EventGrid"), + ) + worker.Start(app) +} +``` + +> **`sdk.WithSource("EventGrid")` needs Flex Consumption infrastructure setup.** When the blob trigger uses EventGrid source on Flex Consumption (the only plan Go supports today), three additional IaC settings are required or events will not be delivered: `alwaysReady: [{ name: 'blob', instanceCount: 1 }]`, the `AzureWebJobsStorage__queueServiceUri` app setting, and an Event Grid subscription authored in Bicep/ARM rather than via the `az` CLI. Full Bicep + RBAC checklist: [lambda-to-functions.md — Flex Consumption + Blob Trigger with EventGrid Source](../lambda-to-functions.md#flex-consumption--blob-trigger-with-eventgrid-source). + +## Queue Storage + +```go +import ( + "context" + "github.com/azure/azure-functions-golang-worker/sdk" + "github.com/azure/azure-functions-golang-worker/sdk/bindings" +) + +func onQueue(ctx context.Context, msg bindings.QueueMessage) error { + // msg.Id, msg.Body, msg.DequeueCount, msg.InsertionTime, ... + return nil +} + +app.Queue("queueFunc", onQueue, + sdk.WithQueueName("myqueue-items"), + sdk.WithConnection("AzureWebJobsStorage"), +) +``` + +## Timer + +```go +func onTick(ctx context.Context, t bindings.TimerInfo) error { + // t.IsPastDue, t.ScheduleStatus.Last, t.ScheduleStatus.Next + return nil +} + +app.Timer("scheduledTask", onTick, + sdk.WithSchedule("0 */5 * * * *"), +) +``` + +## Event Grid + +```go +func onEvent(ctx context.Context, e bindings.EventGridEvent) error { + // e.Id, e.EventType, e.Subject, e.EventTime, e.Data (json.RawMessage) + return nil +} + +app.EventGrid("eventGridTrigger", onEvent) +``` + +## Cosmos DB (Change Feed) + +```go +func onChanges(ctx context.Context, docs []bindings.CosmosDocument) error { + for _, d := range docs { + _ = d.ID + _ = d.Data // json.RawMessage — unmarshal into your struct + } + return nil +} + +app.CosmosDB("docs", onChanges, + sdk.WithDatabase("ToDoList"), + sdk.WithContainer("Items"), + sdk.WithConnection("CosmosDBConnection"), + sdk.WithCreateLeaseContainerIfNotExists(true), +) +``` + +## Service Bus + +```go +// Queue +func onSBQueue(ctx context.Context, msg bindings.ServiceBusMessage) error { + // msg.MessageId, msg.Body, msg.DeliveryCount, msg.SessionId, ... + return nil +} + +app.ServiceBusQueue("queueFunc", onSBQueue, + sdk.WithQueueName("input-queue"), + sdk.WithConnection("ServiceBusConnection"), +) + +// Topic + Subscription +app.ServiceBusTopic("topicFunc", onSBQueue, + sdk.WithTopicName("orders"), + sdk.WithSubscriptionName("processor"), + sdk.WithConnection("ServiceBusConnection"), +) +``` + +## Event Hubs + +```go +func onEventHub(ctx context.Context, e bindings.EventHubMessage) error { + // e.Body, e.SequenceNumber, e.Offset, e.EnqueuedTimeUtc, e.PartitionKey + return nil +} + +app.EventHub("eventHubTrigger", onEventHub, + sdk.WithEventHubName("input-hub"), + sdk.WithConnection("EventHubConnection"), + sdk.WithConsumerGroup("$Default"), +) +``` + +## SQL (Change Tracking) + +> Enable Change Tracking on the DB and table first +> (`ALTER DATABASE ... SET CHANGE_TRACKING = ON;` then +> `ALTER TABLE dbo.Products ENABLE CHANGE_TRACKING;`). + +```go +type Product struct { + ProductID int `json:"ProductId"` + Name string `json:"Name"` + Cost int `json:"Cost"` +} + +func onSQL(ctx context.Context, changes []bindings.SQLChange) error { + for _, c := range changes { + var p Product + if err := json.Unmarshal(c.Item, &p); err != nil { continue } + _ = c.Operation // Insert | Update | Delete + } + return nil +} + +app.SQL("productsChanged", onSQL, + sdk.WithTable("dbo.Products"), + sdk.WithConnection("AzureWebJobsSqlConnectionString"), +) +``` + +## I/O Outside of Triggers — Use the Azure SDK for Go + +The Go worker is **triggers-only by design**. It ships every trigger the other runtimes have, plus the implicit HTTP response — but no input bindings and no non-HTTP output bindings. This is intentional: writes and lookups use the Azure SDK for Go with `DefaultAzureCredential`, which is the idiomatic Go story. + +> Go is the intentional exception to the "bindings over SDKs" rule in [global-rules.md](../global-rules.md). All non-HTTP I/O in a Go Function app goes through the Azure SDK for Go. + +| Capability | Available on Go worker? | How to do it in Go | +| --- | --- | --- | +| Triggers (HTTP, Timer, Queue, Blob, Cosmos, ServiceBus, EventHub, EventGrid, SQL) | ✅ | See sections above | +| HTTP response | ✅ | Write via `http.ResponseWriter` | +| Blob / Queue / Table / Cosmos / SB / EH / EG input & output | ❌ (by design) | Azure SDK for Go — see patterns below | + +## SDK Patterns for I/O + +**Ground rules:** + +- Use `DefaultAzureCredential`. On UAMI apps, always pass `ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID")` — otherwise it tries SystemAssigned first and fails. See [global-rules.md](../global-rules.md). +- **Construct clients once at package init or in `main`**, not per invocation. The worker hosts many concurrent invocations on one process; per-invocation client construction defeats connection pooling and triggers repeated token acquisition. +- Point every service at its URL via an app setting (e.g., `STORAGE_BLOB_URI=https://myacct.blob.core.windows.net`) — never embed keys. + +```go +import ( + "os" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" +) + +var cred = mustCred() + +func mustCred() *azidentity.DefaultAzureCredential { + c, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID"), + }) + if err != nil { panic(err) } + return c +} +``` + +### Blob — read & write + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + +var blobs, _ = azblob.NewClient(os.Getenv("STORAGE_BLOB_URI"), cred, nil) + +// Read +r, err := blobs.DownloadStream(ctx, "input", "path/name.json", nil) +if err != nil { return err } +defer r.Body.Close() +// stream r.Body + +// Write +_, err = blobs.UploadStream(ctx, "output", "path/name.json", body, nil) +``` + +### Queue — send + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" + +var queue, _ = azqueue.NewQueueClient( + os.Getenv("STORAGE_QUEUE_URI")+"/outqueue", cred, nil) + +_, err := queue.EnqueueMessage(ctx, base64.StdEncoding.EncodeToString(payload), nil) +``` + +> Storage Queue messages must be Base64-encoded when written via SDK — the queue trigger binding does this for you, but the SDK does not. + +### Table — read & upsert + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" + +var tables, _ = aztables.NewServiceClient(os.Getenv("STORAGE_TABLE_URI"), cred, nil) +var products = tables.NewClient("Products") + +// Read one entity +resp, err := products.GetEntity(ctx, "electronics", "sku-42", nil) +var p Product +_ = json.Unmarshal(resp.Value, &p) + +// Upsert +raw, _ := json.Marshal(p) +_, err = products.UpsertEntity(ctx, raw, nil) +``` + +### Cosmos DB — point read & upsert + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" + +var cosmos, _ = azcosmos.NewClient(os.Getenv("COSMOS_ENDPOINT"), cred, nil) +var container, _ = cosmos.NewContainer("mydb", "Items") + +// Point read +pk := azcosmos.NewPartitionKeyString("electronics") +resp, err := container.ReadItem(ctx, pk, "sku-42", nil) + +// Upsert +raw, _ := json.Marshal(item) +_, err = container.UpsertItem(ctx, pk, raw, nil) +``` + +### Service Bus — send + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" + +var sb, _ = azservicebus.NewClient(os.Getenv("SERVICEBUS_FQDN"), cred, nil) +var sender, _ = sb.NewSender("outqueue", nil) // or topic name + +err := sender.SendMessage(ctx, &azservicebus.Message{Body: payload}, nil) +``` + +### Event Hubs — send batch + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + +var producer, _ = azeventhubs.NewProducerClient( + os.Getenv("EVENTHUBS_FQDN"), "outhub", cred, nil) + +batch, _ := producer.NewEventDataBatch(ctx, nil) +_ = batch.AddEventData(&azeventhubs.EventData{Body: payload}, nil) +err := producer.SendEventDataBatch(ctx, batch, nil) +``` + +### Event Grid — publish + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" + +var eg, _ = azeventgrid.NewClient(os.Getenv("EVENTGRID_ENDPOINT"), cred, nil) + +events := []azeventgrid.CloudEvent{{ + Source: to.Ptr("myapp"), Type: to.Ptr("order.created"), + Data: order, DataContentType: to.Ptr("application/json"), +}} +_, err := eg.PublishCloudEvents(ctx, "mytopic", events, nil) +``` + +## Core vs Extension Triggers + +| Criterion | Core (HTTP, Timer, Queue, CosmosDB, EventGrid, EventHub, ServiceBus, SQL) | Extension (Blob) | +| --- | --- | --- | +| Payload size | Bounded (KB–low MB) | Potentially GBs | +| External SDK required | No | Yes (`azblob`, `azidentity`) | +| Data in gRPC message | Yes — typed struct | No — only metadata | +| Streaming | Not needed | Essential — `client.DownloadStream(...)` | +| Activation | Automatic | Blank import: `_ ".../triggers/blob"` | + +## Handler Return Values & Retry Semantics + +Every non-HTTP trigger handler returns `error`. The value is interpreted by the Functions **host** (not the Go worker), so the retry and poison-message behavior is identical to what the same trigger would do in any other runtime: + +- `return nil` → invocation succeeds; the host advances the checkpoint / acknowledges the message / commits the change-feed lease as appropriate. +- `return err` (non-nil) → invocation fails; the host applies the trigger's built-in retry policy, then routes the message to the trigger-specific poison / dead-letter destination. + +| Trigger | Default retry behavior | Poison / dead-letter destination | +|---------|------------------------|-----------------------------------| +| HTTP | No automatic retry — response is returned to the caller | N/A (write status code + body in the handler) | +| Storage Queue | Up to `maxDequeueCount` (default 5) | `-poison` queue | +| Service Bus Queue / Topic | Up to `MaxDeliveryCount` (default 10, entity-level) | Entity's dead-letter subqueue | +| Event Grid | Retried by Event Grid (24 h exponential backoff) | Dead-letter destination configured on the subscription | +| Blob (`sdk.WithSource("EventGrid")`) | Retried by Event Grid, same as above | Dead-letter destination on the Event Grid subscription | +| Event Hubs | No per-message retry — checkpoint advances on return | None built-in — implement dead-lettering in the handler | +| Cosmos DB (change feed) | No per-document retry — lease advances on return | None built-in — implement dead-lettering in the handler | +| Timer | No retry — next occurrence fires on schedule | N/A | +| SQL (change tracking) | Per `host.json` retry policy | Per configured retry sink | + +Configure `host.json`'s [retry policies](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages) to override the defaults where the trigger supports it. + +**Interaction with panic recovery:** `sdk.RecoverTo` converts a panic in a user-spawned goroutine into a non-nil error on the enclosing handler — which then follows the table above. That's why `sdk.RecoverTo` is the correct choice for "failure should cause a retry"; `sdk.Recover` is for best-effort work where losing the panic (and skipping the retry) is acceptable. See the next section for the full pattern. + +## Panic Recovery in Goroutines + +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 +} +``` + +## Logging + +`slog` handler is installed at package init. Every record automatically carries `invocation_id`, `function_name`, and `trigger_type`. + +```go +import "log/slog" + +slog.InfoContext(ctx, "processed order", "order_id", id, "amount", amount) +``` + +Access richer invocation metadata (trace parent, retry count) via `sdk.FromContext(ctx)`. + +> **OpenTelemetry & Application Insights export.** The SDK's `slog` handler covers structured logging out of the box. For distributed tracing and Azure Monitor / App Insights export, see the upstream [`samples/otelTracing`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/otelTracing) sample (OpenTelemetry instrumentation) and [`samples/collectorToAzureMonitor`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/collectorToAzureMonitor) (OTel Collector → App Insights pipeline). + +## Build & Hosting Constraints + +**Hosting plan**: The Go worker is currently only available on **Flex Consumption**. Consumption, Premium, and Dedicated (App Service) plans are not supported. + +**Binary format**: The host expects a **Linux ELF binary named `app`** at the deployment root. `func pack` produces this layout automatically; you don't need to build it yourself for deployment. + +**CGO must be disabled** — the Flex Consumption base image does not include a C toolchain, so `CGO_ENABLED=1` binaries fail to start. This means Go packages that require cgo (e.g., certain SQLite drivers, native crypto wrappers) cannot be used; pick pure-Go alternatives. + +## Local Run & Deployment + +```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 -n --src .zip +``` + +> **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 `func pack` (works on any host OS and stamps the bits correctly), WSL's `zip`, or a CI step that explicitly sets the executable bit. + +> 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. From 4df7a8cb5f64445c66148e526b7c176d2dac0a5d Mon Sep 17 00:00:00 2001 From: Harshitha Vegaraju Date: Mon, 17 Aug 2026 14:31:29 -0700 Subject: [PATCH 2/2] Address Go runtime migration review feedback --- .../services/functions/code-migration.md | 2 +- .../services/functions/runtimes/go.md | 484 ++---------------- .../runtimes/go/core-vs-extension.md | 12 + .../functions/runtimes/go/deployment.md | 52 ++ .../functions/runtimes/go/panic-recovery.md | 33 ++ .../functions/runtimes/go/project-layout.md | 39 ++ .../functions/runtimes/go/retry-semantics.md | 22 + .../runtimes/go/sdk-patterns/README.md | 40 ++ .../runtimes/go/sdk-patterns/blob.md | 18 + .../runtimes/go/sdk-patterns/cosmos.md | 18 + .../runtimes/go/sdk-patterns/event-grid.md | 20 + .../runtimes/go/sdk-patterns/event-hubs.md | 14 + .../runtimes/go/sdk-patterns/queue.md | 19 + .../runtimes/go/sdk-patterns/service-bus.md | 12 + .../runtimes/go/sdk-patterns/table.md | 19 + .../functions/runtimes/go/setup-templates.md | 53 ++ .../functions/runtimes/go/triggers/blob.md | 31 ++ .../functions/runtimes/go/triggers/cosmos.md | 18 + .../runtimes/go/triggers/event-grid.md | 10 + .../runtimes/go/triggers/event-hubs.md | 14 + .../functions/runtimes/go/triggers/http.md | 28 + .../functions/runtimes/go/triggers/queue.md | 19 + .../runtimes/go/triggers/service-bus.md | 21 + .../functions/runtimes/go/triggers/sql.md | 27 + .../functions/runtimes/go/triggers/timer.md | 12 + .../functions/runtimes/go/version-pinning.md | 29 ++ 26 files changed, 623 insertions(+), 443 deletions(-) create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/core-vs-extension.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/deployment.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/panic-recovery.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/project-layout.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/retry-semantics.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/README.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/blob.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/cosmos.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-grid.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-hubs.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/queue.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/service-bus.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/table.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/setup-templates.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/blob.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/cosmos.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-grid.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-hubs.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/http.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/queue.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/service-bus.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/sql.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/timer.md create mode 100644 plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/version-pinning.md diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md index 75383bbdd..7661caec3 100644 --- a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/code-migration.md @@ -18,7 +18,7 @@ Migrate AWS Lambda function code to Azure Functions. - **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.1 is installed before running `func init --worker-runtime go`. +- **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 diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md index f9e54aa43..c52772607 100644 --- a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go.md @@ -3,498 +3,98 @@ > **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.1** (`npm i -g azure-functions-core-tools@4 --unsafe-perm true`). +> 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 ` and the build fails. Sub-packages go in `internal//`. 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 -go get github.com/azure/azure-functions-golang-worker +# 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` pre-wired with `FUNCTIONS_WORKER_RUNTIME=native` and the correct Core Tools settings — avoids manual config drift. - -> **`local.settings.json` is local-only** — [Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-go) is explicit: "this file isn't published to Azure." The values below are for `func start`, not for the deployed app: -> - `FUNCTIONS_WORKER_RUNTIME=native` — tells Core Tools to load the "native" worker (Go plugs in as a language-agnostic native worker). Core Tools then infers Go by finding `go.mod` in the project. On the deployed **Flex Consumption** app, the runtime is configured via `functionAppConfig.runtime` on the ARM/Bicep resource — not as an app setting. -> - `FUNCTIONS_CLI_NATIVE_LANGUAGE=go` — written by `func init --worker-runtime go` but redundant in practice (Core Tools infers Go from `go.mod`). Not consumed by the deployed Functions host. -> - `AzureWebJobsStorage` — leave empty locally when no trigger needs host storage; set to `UseDevelopmentStorage=true` for Azurite. In Azure, provide the equivalent as an app setting (or better, use the identity-based form: `AzureWebJobsStorage__blobServiceUri` + `__credential=managedidentity` + `__clientId`). - -### Generated `host.json` - -Verified output of `func init --worker-runtime go` (Core Tools 4.12.0) — matches the shared extension bundle range in [code-migration.md](../code-migration.md): - -```json -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - }, - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[4.*, 5.0.0)" - } -} -``` +`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). -### Generated `local.settings.json` - -```json -{ - "IsEncrypted": false, - "Values": { - "FUNCTIONS_WORKER_RUNTIME": "native", - "FUNCTIONS_CLI_NATIVE_LANGUAGE": "go", - "AzureWebJobsStorage": "" - } -} -``` +> ⚠️ **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: -- **Output bindings other than HTTP are not supported** by the Go worker today. For queue/blob/cosmos/etc. writes, use the Azure SDK for Go directly with `DefaultAzureCredential`. -- **Wrap every user-spawned goroutine** with `sdk.Recover` or `sdk.RecoverTo` — an unrecovered panic in *any* goroutine crashes the whole worker and fails every in-flight invocation on it, not just the one that spawned the goroutine. -- Prefer **core triggers** (typed payloads, no external deps). Use the **Blob extension trigger** (blank import) only when you need a live `*blob.Client` for streaming. -- Log with `slog.InfoContext(ctx, ...)`. The SDK auto-installs an `slog` handler that attaches `invocation_id`, `function_name`, and `trigger_type` to every record. - -## HTTP Trigger - -```go -import ( - "net/http" - "github.com/azure/azure-functions-golang-worker/sdk" - "github.com/azure/azure-functions-golang-worker/worker" -) - -func hello(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("Hello from Go!")) -} - -func main() { - app := sdk.FunctionApp() - app.HTTP("hello", hello, - sdk.WithMethods("GET", "POST"), - sdk.WithAuth("anonymous"), - // sdk.WithRoute("users/{id}"), - ) - worker.Start(app) -} -``` - -> The HTTP output binding is attached implicitly — write the response via `http.ResponseWriter`. - -> **HTTP middleware and streaming.** For wrapping handlers with middleware (timing, tracing, auth), see the upstream [`samples/middleware`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/middleware) sample. For streaming responses (Server-Sent Events, large-object downloads, chunked transfer), see [`samples/httpStreaming`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/httpStreaming) — the HTTP path uses a real loopback `http.Server`, so `http.Flusher`, chunked encoding, and trailers work natively. - -## Blob Storage (extension trigger) - -```go -import ( - "context" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" - "github.com/azure/azure-functions-golang-worker/sdk" - _ "github.com/azure/azure-functions-golang-worker/triggers/blob" // registers factory - "github.com/azure/azure-functions-golang-worker/worker" -) - -func onBlob(ctx context.Context, client *blob.Client) error { - get, err := client.DownloadStream(ctx, nil) - if err != nil { return err } - defer get.Body.Close() - // stream get.Body — supports GB-scale blobs without buffering through gRPC - return nil -} - -func main() { - app := sdk.FunctionApp() - app.Blob("processBlob", onBlob, - sdk.WithPath("samples-workitems/{name}"), - sdk.WithConnection("AzureWebJobsStorage"), - sdk.WithSource("EventGrid"), - ) - worker.Start(app) -} -``` +- **`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. -> **`sdk.WithSource("EventGrid")` needs Flex Consumption infrastructure setup.** When the blob trigger uses EventGrid source on Flex Consumption (the only plan Go supports today), three additional IaC settings are required or events will not be delivered: `alwaysReady: [{ name: 'blob', instanceCount: 1 }]`, the `AzureWebJobsStorage__queueServiceUri` app setting, and an Event Grid subscription authored in Bicep/ARM rather than via the `az` CLI. Full Bicep + RBAC checklist: [lambda-to-functions.md — Flex Consumption + Blob Trigger with EventGrid Source](../lambda-to-functions.md#flex-consumption--blob-trigger-with-eventgrid-source). +## Triggers -## Queue Storage +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. -```go -import ( - "context" - "github.com/azure/azure-functions-golang-worker/sdk" - "github.com/azure/azure-functions-golang-worker/sdk/bindings" -) - -func onQueue(ctx context.Context, msg bindings.QueueMessage) error { - // msg.Id, msg.Body, msg.DequeueCount, msg.InsertionTime, ... - return nil -} - -app.Queue("queueFunc", onQueue, - sdk.WithQueueName("myqueue-items"), - sdk.WithConnection("AzureWebJobsStorage"), -) -``` - -## Timer - -```go -func onTick(ctx context.Context, t bindings.TimerInfo) error { - // t.IsPastDue, t.ScheduleStatus.Last, t.ScheduleStatus.Next - return nil -} - -app.Timer("scheduledTask", onTick, - sdk.WithSchedule("0 */5 * * * *"), -) -``` - -## Event Grid - -```go -func onEvent(ctx context.Context, e bindings.EventGridEvent) error { - // e.Id, e.EventType, e.Subject, e.EventTime, e.Data (json.RawMessage) - return nil -} - -app.EventGrid("eventGridTrigger", onEvent) -``` - -## Cosmos DB (Change Feed) - -```go -func onChanges(ctx context.Context, docs []bindings.CosmosDocument) error { - for _, d := range docs { - _ = d.ID - _ = d.Data // json.RawMessage — unmarshal into your struct - } - return nil -} - -app.CosmosDB("docs", onChanges, - sdk.WithDatabase("ToDoList"), - sdk.WithContainer("Items"), - sdk.WithConnection("CosmosDBConnection"), - sdk.WithCreateLeaseContainerIfNotExists(true), -) -``` - -## Service Bus - -```go -// Queue -func onSBQueue(ctx context.Context, msg bindings.ServiceBusMessage) error { - // msg.MessageId, msg.Body, msg.DeliveryCount, msg.SessionId, ... - return nil -} - -app.ServiceBusQueue("queueFunc", onSBQueue, - sdk.WithQueueName("input-queue"), - sdk.WithConnection("ServiceBusConnection"), -) - -// Topic + Subscription -app.ServiceBusTopic("topicFunc", onSBQueue, - sdk.WithTopicName("orders"), - sdk.WithSubscriptionName("processor"), - sdk.WithConnection("ServiceBusConnection"), -) -``` - -## Event Hubs - -```go -func onEventHub(ctx context.Context, e bindings.EventHubMessage) error { - // e.Body, e.SequenceNumber, e.Offset, e.EnqueuedTimeUtc, e.PartitionKey - return nil -} - -app.EventHub("eventHubTrigger", onEventHub, - sdk.WithEventHubName("input-hub"), - sdk.WithConnection("EventHubConnection"), - sdk.WithConsumerGroup("$Default"), -) -``` - -## SQL (Change Tracking) - -> Enable Change Tracking on the DB and table first -> (`ALTER DATABASE ... SET CHANGE_TRACKING = ON;` then -> `ALTER TABLE dbo.Products ENABLE CHANGE_TRACKING;`). - -```go -type Product struct { - ProductID int `json:"ProductId"` - Name string `json:"Name"` - Cost int `json:"Cost"` -} - -func onSQL(ctx context.Context, changes []bindings.SQLChange) error { - for _, c := range changes { - var p Product - if err := json.Unmarshal(c.Item, &p); err != nil { continue } - _ = c.Operation // Insert | Update | Delete - } - return nil -} - -app.SQL("productsChanged", onSQL, - sdk.WithTable("dbo.Products"), - sdk.WithConnection("AzureWebJobsSqlConnectionString"), -) -``` +| 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**. It ships every trigger the other runtimes have, plus the implicit HTTP response — but no input bindings and no non-HTTP output bindings. This is intentional: writes and lookups use the Azure SDK for Go with `DefaultAzureCredential`, which is the idiomatic Go story. - -> Go is the intentional exception to the "bindings over SDKs" rule in [global-rules.md](../global-rules.md). All non-HTTP I/O in a Go Function app goes through the Azure SDK for Go. - -| Capability | Available on Go worker? | How to do it in Go | -| --- | --- | --- | -| Triggers (HTTP, Timer, Queue, Blob, Cosmos, ServiceBus, EventHub, EventGrid, SQL) | ✅ | See sections above | -| HTTP response | ✅ | Write via `http.ResponseWriter` | -| Blob / Queue / Table / Cosmos / SB / EH / EG input & output | ❌ (by design) | Azure SDK for Go — see patterns below | +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:** - -- Use `DefaultAzureCredential`. On UAMI apps, always pass `ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID")` — otherwise it tries SystemAssigned first and fails. See [global-rules.md](../global-rules.md). -- **Construct clients once at package init or in `main`**, not per invocation. The worker hosts many concurrent invocations on one process; per-invocation client construction defeats connection pooling and triggers repeated token acquisition. -- Point every service at its URL via an app setting (e.g., `STORAGE_BLOB_URI=https://myacct.blob.core.windows.net`) — never embed keys. - -```go -import ( - "os" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" -) - -var cred = mustCred() - -func mustCred() *azidentity.DefaultAzureCredential { - c, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ - ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID"), - }) - if err != nil { panic(err) } - return c -} -``` - -### Blob — read & write - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" - -var blobs, _ = azblob.NewClient(os.Getenv("STORAGE_BLOB_URI"), cred, nil) - -// Read -r, err := blobs.DownloadStream(ctx, "input", "path/name.json", nil) -if err != nil { return err } -defer r.Body.Close() -// stream r.Body - -// Write -_, err = blobs.UploadStream(ctx, "output", "path/name.json", body, nil) -``` - -### Queue — send - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" - -var queue, _ = azqueue.NewQueueClient( - os.Getenv("STORAGE_QUEUE_URI")+"/outqueue", cred, nil) - -_, err := queue.EnqueueMessage(ctx, base64.StdEncoding.EncodeToString(payload), nil) -``` - -> Storage Queue messages must be Base64-encoded when written via SDK — the queue trigger binding does this for you, but the SDK does not. - -### Table — read & upsert - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" - -var tables, _ = aztables.NewServiceClient(os.Getenv("STORAGE_TABLE_URI"), cred, nil) -var products = tables.NewClient("Products") - -// Read one entity -resp, err := products.GetEntity(ctx, "electronics", "sku-42", nil) -var p Product -_ = json.Unmarshal(resp.Value, &p) - -// Upsert -raw, _ := json.Marshal(p) -_, err = products.UpsertEntity(ctx, raw, nil) -``` - -### Cosmos DB — point read & upsert - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" - -var cosmos, _ = azcosmos.NewClient(os.Getenv("COSMOS_ENDPOINT"), cred, nil) -var container, _ = cosmos.NewContainer("mydb", "Items") - -// Point read -pk := azcosmos.NewPartitionKeyString("electronics") -resp, err := container.ReadItem(ctx, pk, "sku-42", nil) - -// Upsert -raw, _ := json.Marshal(item) -_, err = container.UpsertItem(ctx, pk, raw, nil) -``` - -### Service Bus — send - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" - -var sb, _ = azservicebus.NewClient(os.Getenv("SERVICEBUS_FQDN"), cred, nil) -var sender, _ = sb.NewSender("outqueue", nil) // or topic name - -err := sender.SendMessage(ctx, &azservicebus.Message{Body: payload}, nil) -``` - -### Event Hubs — send batch - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" - -var producer, _ = azeventhubs.NewProducerClient( - os.Getenv("EVENTHUBS_FQDN"), "outhub", cred, nil) - -batch, _ := producer.NewEventDataBatch(ctx, nil) -_ = batch.AddEventData(&azeventhubs.EventData{Body: payload}, nil) -err := producer.SendEventDataBatch(ctx, batch, nil) -``` - -### Event Grid — publish - -```go -import "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" - -var eg, _ = azeventgrid.NewClient(os.Getenv("EVENTGRID_ENDPOINT"), cred, nil) - -events := []azeventgrid.CloudEvent{{ - Source: to.Ptr("myapp"), Type: to.Ptr("order.created"), - Data: order, DataContentType: to.Ptr("application/json"), -}} -_, err := eg.PublishCloudEvents(ctx, "mytopic", events, nil) -``` +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 -| Criterion | Core (HTTP, Timer, Queue, CosmosDB, EventGrid, EventHub, ServiceBus, SQL) | Extension (Blob) | -| --- | --- | --- | -| Payload size | Bounded (KB–low MB) | Potentially GBs | -| External SDK required | No | Yes (`azblob`, `azidentity`) | -| Data in gRPC message | Yes — typed struct | No — only metadata | -| Streaming | Not needed | Essential — `client.DownloadStream(...)` | -| Activation | Automatic | Blank import: `_ ".../triggers/blob"` | +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 -Every non-HTTP trigger handler returns `error`. The value is interpreted by the Functions **host** (not the Go worker), so the retry and poison-message behavior is identical to what the same trigger would do in any other runtime: - -- `return nil` → invocation succeeds; the host advances the checkpoint / acknowledges the message / commits the change-feed lease as appropriate. -- `return err` (non-nil) → invocation fails; the host applies the trigger's built-in retry policy, then routes the message to the trigger-specific poison / dead-letter destination. - -| Trigger | Default retry behavior | Poison / dead-letter destination | -|---------|------------------------|-----------------------------------| -| HTTP | No automatic retry — response is returned to the caller | N/A (write status code + body in the handler) | -| Storage Queue | Up to `maxDequeueCount` (default 5) | `-poison` queue | -| Service Bus Queue / Topic | Up to `MaxDeliveryCount` (default 10, entity-level) | Entity's dead-letter subqueue | -| Event Grid | Retried by Event Grid (24 h exponential backoff) | Dead-letter destination configured on the subscription | -| Blob (`sdk.WithSource("EventGrid")`) | Retried by Event Grid, same as above | Dead-letter destination on the Event Grid subscription | -| Event Hubs | No per-message retry — checkpoint advances on return | None built-in — implement dead-lettering in the handler | -| Cosmos DB (change feed) | No per-document retry — lease advances on return | None built-in — implement dead-lettering in the handler | -| Timer | No retry — next occurrence fires on schedule | N/A | -| SQL (change tracking) | Per `host.json` retry policy | Per configured retry sink | - -Configure `host.json`'s [retry policies](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages) to override the defaults where the trigger supports it. - -**Interaction with panic recovery:** `sdk.RecoverTo` converts a panic in a user-spawned goroutine into a non-nil error on the enclosing handler — which then follows the table above. That's why `sdk.RecoverTo` is the correct choice for "failure should cause a retry"; `sdk.Recover` is for best-effort work where losing the panic (and skipping the retry) is acceptable. See the next section for the full pattern. +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 → `-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, 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 -} -``` +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 -`slog` handler is installed at package init. Every record automatically carries `invocation_id`, `function_name`, and `trigger_type`. +The SDK installs an `slog` handler at package init. Records automatically carry `invocation_id`, `function_name`, `trigger_type`. ```go -import "log/slog" - slog.InfoContext(ctx, "processed order", "order_id", id, "amount", amount) ``` -Access richer invocation metadata (trace parent, retry count) via `sdk.FromContext(ctx)`. - -> **OpenTelemetry & Application Insights export.** The SDK's `slog` handler covers structured logging out of the box. For distributed tracing and Azure Monitor / App Insights export, see the upstream [`samples/otelTracing`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/otelTracing) sample (OpenTelemetry instrumentation) and [`samples/collectorToAzureMonitor`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/collectorToAzureMonitor) (OTel Collector → App Insights pipeline). +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**: The Go worker is currently only available on **Flex Consumption**. Consumption, Premium, and Dedicated (App Service) plans are not supported. - -**Binary format**: The host expects a **Linux ELF binary named `app`** at the deployment root. `func pack` produces this layout automatically; you don't need to build it yourself for deployment. - -**CGO must be disabled** — the Flex Consumption base image does not include a C toolchain, so `CGO_ENABLED=1` binaries fail to start. This means Go packages that require cgo (e.g., certain SQLite drivers, native crypto wrappers) cannot be used; pick pure-Go alternatives. +- **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 # 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 -n --src .zip +func pack # cross-compiles (`CGO_ENABLED=0 GOOS=linux GOARCH=amd64`) and packages an app-at-root zip ``` -> **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 `func pack` (works on any host OS and stamps the bits correctly), WSL's `zip`, or a CI step that explicitly sets the executable bit. +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. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/core-vs-extension.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/core-vs-extension.md new file mode 100644 index 000000000..d95fdc2cd --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/core-vs-extension.md @@ -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. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/deployment.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/deployment.md new file mode 100644 index 000000000..aadc31062 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/deployment.md @@ -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 -n --src .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 .` | `` or `.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. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/panic-recovery.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/panic-recovery.md new file mode 100644 index 000000000..4c7c8f14b --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/panic-recovery.md @@ -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. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/project-layout.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/project-layout.md new file mode 100644 index 000000000..bb59cde0a --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/project-layout.md @@ -0,0 +1,39 @@ +# Project Layout + +> **Overrides [lambda-to-functions.md § Project Structure](../../lambda-to-functions.md#project-structure).** The `src/`-based layout there is a JS/Python convention and breaks Go builds. + +`main.go` and `go.mod` **must sit next to `host.json`** at the project root. `func start` runs `go build` from `host.json`'s directory; `.go` files elsewhere are invisible. + +## Required + +``` +myapp/ +├── go.mod +├── go.sum +├── main.go # entry — calls worker.Start(app) +├── host.json +├── local.settings.json +└── internal/ # OPTIONAL sub-packages + ├── handlers/http.go + └── store/cosmos.go +``` + +## Anti-pattern (breaks `func start`) + +``` +myapp/ +├── host.json # build root +├── go.mod +└── src/ + └── main.go # invisible — build fails +``` + +Error: `no Go files in ` → `Go build failed with exit code 1.` + +## Rules + +- **Never use `src/`** — not idiomatic Go, not recognized by `go build`. Matches [go.dev module layout](https://go.dev/doc/modules/layout) and [worker samples](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples). +- Sub-packages go in **`internal//`** (compiler-private) or top-level named packages (`handlers/`, `store/`). Name by role, not generic buckets. +- Exactly one `go.mod` per app, at project root. +- Run all `func` and `go` commands from project root. +- **Wrapper directory naming**: when the Function app lives inside a wrapper (e.g., alongside migration reports at `-azure/`), name it `func/` or the service name — **never `app/`** (collides with the required binary name `app`, invites hand-zip mistakes like `zip -r deploy.zip app` producing the wrong archive shape). Update `azure.yaml`'s `project:` to match (e.g., `project: ./func`). diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/retry-semantics.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/retry-semantics.md new file mode 100644 index 000000000..c63d16dda --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/retry-semantics.md @@ -0,0 +1,22 @@ +# Handler Return Values & Retry Semantics — Go + +Every non-HTTP trigger handler returns `error`. The value is interpreted by the Functions **host** (not the Go worker), so the retry and poison-message behavior is identical to what the same trigger would do in any other runtime: + +- `return nil` → invocation succeeds; the host advances the checkpoint / acknowledges the message / commits change-feed progress as appropriate. +- `return err` (non-nil) → invocation fails. The next action depends on the trigger: its binding extension can retry or dead-letter the event, or a configured function-level retry policy can rerun the failed execution. + +| Trigger | Default retry behavior | Poison / dead-letter destination | +|---------|------------------------|-----------------------------------| +| HTTP | No automatic retry — response is returned to the caller | N/A (write status code + body in the handler) | +| Storage Queue | Up to `maxDequeueCount` (default 5) | `-poison` queue | +| Service Bus Queue / Topic | Up to `MaxDeliveryCount` (default 10, entity-level) | Entity's dead-letter subqueue | +| Event Grid | Retried by Event Grid (24 h exponential backoff) | Dead-letter destination configured on the subscription | +| Blob (`sdk.WithSource("EventGrid")`) | Retried by Event Grid, same as above | Dead-letter destination on the Event Grid subscription | +| Event Hubs | Configurable function-level retry policy. Checkpoints aren't written until the retry policy for the execution finishes, pausing progress on that partition. | No built-in dead-letter destination | +| Cosmos DB (change feed) | Configurable function-level retry policy reruns a failed execution until success or the maximum retry count is reached. | No built-in dead-letter destination | +| Timer | No retry — next occurrence fires on schedule | N/A | +| SQL (change tracking) | Per `host.json` retry policy | Per configured retry sink | + +Configure [function-level retry policies](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages) for Event Hubs, Cosmos DB, and other supported triggers. Retry policies rerun failed executions; they don't create a poison or dead-letter destination. + +**Interaction with panic recovery:** `sdk.RecoverTo` converts a panic in a user-spawned goroutine into a non-nil error on the enclosing handler — which then follows the table above. That's why `sdk.RecoverTo` is the correct choice for "failure should cause a retry"; `sdk.Recover` is for best-effort work where losing the panic (and skipping the retry) is acceptable. See [panic-recovery.md](./panic-recovery.md) for the full pattern. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/README.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/README.md new file mode 100644 index 000000000..6da8fe0de --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/README.md @@ -0,0 +1,40 @@ +# SDK Patterns for I/O — Ground Rules + +The Go worker is triggers-only by design; non-HTTP output uses the Azure SDK for Go directly. All samples in this directory share the credential and client-lifetime rules below. + +**Ground rules:** + +- Use `DefaultAzureCredential`. On UAMI apps, always pass `ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID")` — otherwise it tries SystemAssigned first and fails. See [global-rules.md](../../../global-rules.md). +- **Construct clients once at package init or in `main`**, not per invocation. The worker hosts many concurrent invocations on one process; per-invocation client construction defeats connection pooling and triggers repeated token acquisition. +- Point every service at its URL via an app setting (e.g., `STORAGE_BLOB_URI=https://myacct.blob.core.windows.net`) — never embed keys. + +```go +import ( + "os" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" +) + +var cred = mustCred() + +func mustCred() *azidentity.DefaultAzureCredential { + c, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + ManagedIdentityClientID: os.Getenv("AZURE_CLIENT_ID"), + }) + if err != nil { panic(err) } + return c +} +``` + +The `cred` variable above is shared by every SDK sample in this directory. + +## Samples + +| Service | Operations | Sample | +| --- | --- | --- | +| Blob | read & write | [blob.md](./blob.md) | +| Queue | send | [queue.md](./queue.md) | +| Table | read & upsert | [table.md](./table.md) | +| Cosmos DB | point read & upsert | [cosmos.md](./cosmos.md) | +| Service Bus | send | [service-bus.md](./service-bus.md) | +| Event Hubs | send batch | [event-hubs.md](./event-hubs.md) | +| Event Grid | publish | [event-grid.md](./event-grid.md) | diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/blob.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/blob.md new file mode 100644 index 000000000..b2347e590 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/blob.md @@ -0,0 +1,18 @@ +# Blob — read & write (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + +var blobs, _ = azblob.NewClient(os.Getenv("STORAGE_BLOB_URI"), cred, nil) + +// Read +r, err := blobs.DownloadStream(ctx, "input", "path/name.json", nil) +if err != nil { return err } +defer r.Body.Close() +// stream r.Body + +// Write +_, err = blobs.UploadStream(ctx, "output", "path/name.json", body, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/cosmos.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/cosmos.md new file mode 100644 index 000000000..15d861aa0 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/cosmos.md @@ -0,0 +1,18 @@ +# Cosmos DB — point read & upsert (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" + +var cosmos, _ = azcosmos.NewClient(os.Getenv("COSMOS_ENDPOINT"), cred, nil) +var container, _ = cosmos.NewContainer("mydb", "Items") + +// Point read +pk := azcosmos.NewPartitionKeyString("electronics") +resp, err := container.ReadItem(ctx, pk, "sku-42", nil) + +// Upsert +raw, _ := json.Marshal(item) +_, err = container.UpsertItem(ctx, pk, raw, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-grid.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-grid.md new file mode 100644 index 000000000..6fac96e25 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-grid.md @@ -0,0 +1,20 @@ +# Event Grid — publish (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import ( + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" +) + +var eg, _ = azeventgrid.NewClient(os.Getenv("EVENTGRID_ENDPOINT"), cred, nil) + +events := []azeventgrid.CloudEvent{{ + Source: to.Ptr("myapp"), Type: to.Ptr("order.created"), + Data: order, DataContentType: to.Ptr("application/json"), +}} +_, err := eg.PublishCloudEvents(ctx, "mytopic", events, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-hubs.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-hubs.md new file mode 100644 index 000000000..aba1efe07 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/event-hubs.md @@ -0,0 +1,14 @@ +# Event Hubs — send batch (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + +var producer, _ = azeventhubs.NewProducerClient( + os.Getenv("EVENTHUBS_FQDN"), "outhub", cred, nil) + +batch, _ := producer.NewEventDataBatch(ctx, nil) +_ = batch.AddEventData(&azeventhubs.EventData{Body: payload}, nil) +err := producer.SendEventDataBatch(ctx, batch, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/queue.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/queue.md new file mode 100644 index 000000000..029d97269 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/queue.md @@ -0,0 +1,19 @@ +# Queue — send (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import ( + "encoding/base64" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" +) + +var queue, _ = azqueue.NewQueueClient( + os.Getenv("STORAGE_QUEUE_URI")+"/outqueue", cred, nil) + +_, err := queue.EnqueueMessage(ctx, base64.StdEncoding.EncodeToString(payload), nil) +``` + +> Storage Queue messages must be Base64-encoded when written via SDK — the queue trigger binding does this for you, but the SDK does not. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/service-bus.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/service-bus.md new file mode 100644 index 000000000..9a63da187 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/service-bus.md @@ -0,0 +1,12 @@ +# Service Bus — send (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" + +var sb, _ = azservicebus.NewClient(os.Getenv("SERVICEBUS_FQDN"), cred, nil) +var sender, _ = sb.NewSender("outqueue", nil) // or topic name + +err := sender.SendMessage(ctx, &azservicebus.Message{Body: payload}, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/table.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/table.md new file mode 100644 index 000000000..4325e2b83 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/sdk-patterns/table.md @@ -0,0 +1,19 @@ +# Table — read & upsert (Azure SDK for Go) + +> Shared ground rules (credential setup, client lifetime, URL app settings) → [README.md](./README.md). + +```go +import "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" + +var tables, _ = aztables.NewServiceClient(os.Getenv("STORAGE_TABLE_URI"), cred, nil) +var products = tables.NewClient("Products") + +// Read one entity +resp, err := products.GetEntity(ctx, "electronics", "sku-42", nil) +var p Product +_ = json.Unmarshal(resp.Value, &p) + +// Upsert +raw, _ := json.Marshal(p) +_, err = products.UpsertEntity(ctx, raw, nil) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/setup-templates.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/setup-templates.md new file mode 100644 index 000000000..aa9e4a9b5 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/setup-templates.md @@ -0,0 +1,53 @@ +# Generated `host.json` / `local.settings.json` — Go + +Verified output of `func init --worker-runtime go` (Core Tools 4.12.0). The `host.json` matches the shared extension bundle range in [code-migration.md](../../code-migration.md). + +## `host.json` + +```json +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} +``` + +## `local.settings.json` + +```json +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "native", + "FUNCTIONS_CLI_NATIVE_LANGUAGE": "go", + "AzureWebJobsStorage": "" + } +} +``` + +> **`local.settings.json` is local-only** — [Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-go) is explicit: "this file isn't published to Azure." The values above are for `func start`, not for the deployed app: +> - `FUNCTIONS_WORKER_RUNTIME=native` — tells Core Tools to load the "native" worker; Core Tools infers Go by finding `go.mod`. **On the deployed Flex Consumption app, do NOT set this app setting** — the runtime is controlled by `functionAppConfig.runtime = { name: 'go', version: '1.0' }` on the ARM/Bicep resource. Adding `FUNCTIONS_WORKER_RUNTIME` in Azure will conflict with the Flex `runtime` block. +> - `FUNCTIONS_CLI_NATIVE_LANGUAGE=go` — written by `func init --worker-runtime go` but redundant in practice (Core Tools infers Go from `go.mod`). Not consumed by the deployed Functions host. +> - `AzureWebJobsStorage` — leave empty locally when no trigger needs host storage; set to `UseDevelopmentStorage=true` for Azurite. In Azure, use the identity-based form: `AzureWebJobsStorage__blobServiceUri` + `AzureWebJobsStorage__credential=managedidentity` + `AzureWebJobsStorage__clientId=`. + +## Deployed app requirements (Flex Consumption) + +For the Bicep/Terraform to provision the function app correctly: + +- `functionAppConfig.runtime = { name: 'go', version: '1.0' }` (Bicep) / `runtime = { name = "go", version = "1.0" }` (Terraform). +- `siteConfig.http20Enabled: false` — **required during Go public preview**; deployments without this fail at runtime. +- Flex Consumption plan (SKU `FC1`) — Consumption / Premium / Dedicated are not supported. +- Linux only. No Durable Functions. No deployment slots. +- Do NOT set `FUNCTIONS_WORKER_RUNTIME` as an app setting. + +For complete packaging and infrastructure guidance, see [deployment.md](./deployment.md). diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/blob.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/blob.md new file mode 100644 index 000000000..4e4269b5c --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/blob.md @@ -0,0 +1,31 @@ +# Blob Storage Trigger — Go (extension trigger) + +```go +import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/azure/azure-functions-golang-worker/sdk" + _ "github.com/azure/azure-functions-golang-worker/triggers/blob" // registers factory + "github.com/azure/azure-functions-golang-worker/worker" +) + +func onBlob(ctx context.Context, client *blob.Client) error { + get, err := client.DownloadStream(ctx, nil) + if err != nil { return err } + defer get.Body.Close() + // stream get.Body — supports GB-scale blobs without buffering through gRPC + return nil +} + +func main() { + app := sdk.FunctionApp() + app.Blob("processBlob", onBlob, + sdk.WithPath("samples-workitems/{name}"), + sdk.WithConnection("AzureWebJobsStorage"), + sdk.WithSource("EventGrid"), + ) + worker.Start(app) +} +``` + +> **`sdk.WithSource("EventGrid")` needs Flex Consumption infrastructure setup.** When the blob trigger uses EventGrid source on Flex Consumption (the only plan Go supports today), three additional IaC settings are required or events will not be delivered: `alwaysReady: [{ name: 'blob', instanceCount: 1 }]`, the `AzureWebJobsStorage__queueServiceUri` app setting, and an Event Grid subscription authored in Bicep/ARM rather than via the `az` CLI. Full Bicep + RBAC checklist: [lambda-to-functions.md — Flex Consumption + Blob Trigger with EventGrid Source](../../../lambda-to-functions.md#flex-consumption--blob-trigger-with-eventgrid-source). diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/cosmos.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/cosmos.md new file mode 100644 index 000000000..8455a147e --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/cosmos.md @@ -0,0 +1,18 @@ +# Cosmos DB Trigger — Go (Change Feed) + +```go +func onChanges(ctx context.Context, docs []bindings.CosmosDocument) error { + for _, d := range docs { + _ = d.ID + _ = d.Data // json.RawMessage — unmarshal into your struct + } + return nil +} + +app.CosmosDB("docs", onChanges, + sdk.WithDatabase("ToDoList"), + sdk.WithContainer("Items"), + sdk.WithConnection("CosmosDBConnection"), + sdk.WithCreateLeaseContainerIfNotExists(true), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-grid.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-grid.md new file mode 100644 index 000000000..78869f18c --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-grid.md @@ -0,0 +1,10 @@ +# Event Grid Trigger — Go + +```go +func onEvent(ctx context.Context, e bindings.EventGridEvent) error { + // e.Id, e.EventType, e.Subject, e.EventTime, e.Data (json.RawMessage) + return nil +} + +app.EventGrid("eventGridTrigger", onEvent) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-hubs.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-hubs.md new file mode 100644 index 000000000..66439806c --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/event-hubs.md @@ -0,0 +1,14 @@ +# Event Hubs Trigger — Go + +```go +func onEventHub(ctx context.Context, e bindings.EventHubMessage) error { + // e.Body, e.SequenceNumber, e.Offset, e.EnqueuedTimeUtc, e.PartitionKey + return nil +} + +app.EventHub("eventHubTrigger", onEventHub, + sdk.WithEventHubName("input-hub"), + sdk.WithConnection("EventHubConnection"), + sdk.WithConsumerGroup("$Default"), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/http.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/http.md new file mode 100644 index 000000000..0da1d2092 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/http.md @@ -0,0 +1,28 @@ +# HTTP Trigger — Go + +```go +import ( + "net/http" + "github.com/azure/azure-functions-golang-worker/sdk" + "github.com/azure/azure-functions-golang-worker/worker" +) + +func hello(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello from Go!")) +} + +func main() { + app := sdk.FunctionApp() + app.HTTP("hello", hello, + sdk.WithMethods("GET", "POST"), + sdk.WithAuth("anonymous"), + // sdk.WithRoute("users/{id}"), + ) + worker.Start(app) +} +``` + +> The HTTP output binding is attached implicitly — write the response via `http.ResponseWriter`. + +> **HTTP middleware and streaming.** For wrapping handlers with middleware (timing, tracing, auth), see the upstream [`samples/middleware`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/middleware) sample. For streaming responses (Server-Sent Events, large-object downloads, chunked transfer), see [`samples/httpStreaming`](https://github.com/Azure/azure-functions-golang-worker/tree/main/samples/httpStreaming) — the HTTP path uses a real loopback `http.Server`, so `http.Flusher`, chunked encoding, and trailers work natively. diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/queue.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/queue.md new file mode 100644 index 000000000..88712e2aa --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/queue.md @@ -0,0 +1,19 @@ +# Queue Storage Trigger — Go + +```go +import ( + "context" + "github.com/azure/azure-functions-golang-worker/sdk" + "github.com/azure/azure-functions-golang-worker/sdk/bindings" +) + +func onQueue(ctx context.Context, msg bindings.QueueMessage) error { + // msg.Id, msg.Body, msg.DequeueCount, msg.InsertionTime, ... + return nil +} + +app.Queue("queueFunc", onQueue, + sdk.WithQueueName("myqueue-items"), + sdk.WithConnection("AzureWebJobsStorage"), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/service-bus.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/service-bus.md new file mode 100644 index 000000000..1d98ad66a --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/service-bus.md @@ -0,0 +1,21 @@ +# Service Bus Trigger — Go + +```go +// Queue +func onSBQueue(ctx context.Context, msg bindings.ServiceBusMessage) error { + // msg.MessageId, msg.Body, msg.DeliveryCount, msg.SessionId, ... + return nil +} + +app.ServiceBusQueue("queueFunc", onSBQueue, + sdk.WithQueueName("input-queue"), + sdk.WithConnection("ServiceBusConnection"), +) + +// Topic + Subscription +app.ServiceBusTopic("topicFunc", onSBQueue, + sdk.WithTopicName("orders"), + sdk.WithSubscriptionName("processor"), + sdk.WithConnection("ServiceBusConnection"), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/sql.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/sql.md new file mode 100644 index 000000000..2cb7bee65 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/sql.md @@ -0,0 +1,27 @@ +# SQL Trigger — Go (Change Tracking) + +> Enable Change Tracking on the DB and table first +> (`ALTER DATABASE ... SET CHANGE_TRACKING = ON;` then +> `ALTER TABLE dbo.Products ENABLE CHANGE_TRACKING;`). + +```go +type Product struct { + ProductID int `json:"ProductId"` + Name string `json:"Name"` + Cost int `json:"Cost"` +} + +func onSQL(ctx context.Context, changes []bindings.SQLChange) error { + for _, c := range changes { + var p Product + if err := json.Unmarshal(c.Item, &p); err != nil { continue } + _ = c.Operation // Insert | Update | Delete + } + return nil +} + +app.SQL("productsChanged", onSQL, + sdk.WithTable("dbo.Products"), + sdk.WithConnection("AzureWebJobsSqlConnectionString"), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/timer.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/timer.md new file mode 100644 index 000000000..88239ef4e --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/triggers/timer.md @@ -0,0 +1,12 @@ +# Timer Trigger — Go + +```go +func onTick(ctx context.Context, t bindings.TimerInfo) error { + // t.IsPastDue, t.ScheduleStatus.Last, t.ScheduleStatus.Next + return nil +} + +app.Timer("scheduledTask", onTick, + sdk.WithSchedule("0 */5 * * * *"), +) +``` diff --git a/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/version-pinning.md b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/version-pinning.md new file mode 100644 index 000000000..3a3deb635 --- /dev/null +++ b/plugins/azure-skills/skills/azure-cloud-migrate/references/services/functions/runtimes/go/version-pinning.md @@ -0,0 +1,29 @@ +# Pinning the Golang Worker Module Version + +The worker module is preview-only, and its tagged `vX.Y.Z-preview` releases often lag active development on `main`. Never hand-author the `require` line — always let the Go toolchain resolve and write it. + +## Enumerate available versions first + +```bash +go list -m -versions github.com/azure/azure-functions-golang-worker +``` + +Or check the [Releases page](https://github.com/Azure/azure-functions-golang-worker/releases). Inspect the latest tag's date against activity on `main`. + +## Pick a pin strategy + +| Situation | Command | +| --- | --- | +| Latest preview tag looks recent | `go get github.com/azure/azure-functions-golang-worker@v0.6.0-preview` (substitute current tag) | +| Latest tag looks stale relative to `main` | `go get github.com/azure/azure-functions-golang-worker@main` (resolves to a pseudo-version like `v0.6.1-0.20260721153000-abcdef123456`) | +| Reproducing a known-good commit | `go get github.com/azure/azure-functions-golang-worker@` | + +## Never do this + +Do **not** author `require github.com/azure/azure-functions-golang-worker v0.0.0` (or any unresolved version) by hand. `go mod tidy` fails with: + +``` +reading github.com/azure/azure-functions-golang-worker/go.mod at revision v0.0.0: unknown revision v0.0.0 +``` + +The same rule applies to every sub-package import (`.../sdk`, `.../worker`, `.../triggers/blob`, `.../sdk/bindings`, etc.) — they share the parent module's version, so **do not add separate `require` lines for them**. One `go get` on the root module pins them all.