Add Go Azure Functions framework service support#8599
Conversation
📋 Milestone: June 2026This work is tracked for June 2026. The team will review it soon! |
There was a problem hiding this comment.
Pull request overview
This PR is a draft POC that adds first-class Go as a supported language for Azure Functions services in azd core, enabling azd up/deploy for Go-based Function Apps (Flex Consumption) by introducing a Go framework service, Go CLI tool wrapper, and related schema/tests.
Changes:
- Added
goas a validservices.<name>.languagevalue (schema + language parsing), plus IoC registration for the new framework service. - Implemented a Go Azure Functions framework service to restore modules, cross-compile a Linux binary, and stage a Functions deployment package.
- Updated packaging to preserve Unix file permissions in zip deploy artifacts and added unit + functional coverage for the new Go Functions sample.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| schemas/v1.0/azure.yaml.json | Adds go to the stable schema language enum. |
| schemas/alpha/azure.yaml.json | Adds go to the alpha schema language enum. |
| cli/azd/test/functional/up_test.go | Adds an end-to-end functional test for provisioning/deploying a Go Function App sample. |
| cli/azd/test/functional/testdata/samples/gofuncapp/main.go | New “hello world” Go Functions app using the Go worker SDK. |
| cli/azd/test/functional/testdata/samples/gofuncapp/infra/resources.bicep | New Flex Consumption Function App + storage + RBAC infra for the Go sample. |
| cli/azd/test/functional/testdata/samples/gofuncapp/infra/main.parameters.json | Parameters file for the Go sample deployment. |
| cli/azd/test/functional/testdata/samples/gofuncapp/infra/main.bicep | Subscription-scope RG creation + module wiring for the Go sample. |
| cli/azd/test/functional/testdata/samples/gofuncapp/host.json | New host.json included in the Go sample. |
| cli/azd/test/functional/testdata/samples/gofuncapp/go.sum.txt | Dependencies for the Go sample (stored as .txt for embedding). |
| cli/azd/test/functional/testdata/samples/gofuncapp/go.mod.txt | Module definition for the Go sample (stored as .txt for embedding). |
| cli/azd/test/functional/testdata/samples/gofuncapp/azure.yaml | New sample project definition using host: function and language: go. |
| cli/azd/pkg/tools/golang/golang.go | New ExternalTool wrapper for the go CLI (version check, build, mod download). |
| cli/azd/pkg/rzip/rzip.go | Preserves Unix file permissions in zip entries (needed for executable Go binaries). |
| cli/azd/pkg/project/service_target_functionapp.go | Sets default remoteBuild behavior for Go Function Apps to false. |
| cli/azd/pkg/project/framework_service.go | Adds ServiceLanguageGo + alias parsing. |
| cli/azd/pkg/project/framework_service_go.go | Implements Go framework service: restore/build/package for Azure Functions deploy. |
| cli/azd/pkg/project/framework_service_go_test.go | Adds unit tests for Go framework service behavior. |
| cli/azd/cmd/container.go | Registers Go CLI singleton and Go framework service in the IoC container. |
Adds a new 'go' service language for Azure Functions, enabling `azd up` for Go-based Function Apps on Flex Consumption. This follows the pattern of existing language framework services (Python, Node, Java, .NET). Changes: - Go CLI tool wrapper (pkg/tools/golang/) with min version Go 1.24 - Framework service (pkg/project/framework_service_go.go): Restore, Build (cross-compile GOOS=linux GOARCH=amd64), Package (app binary + host.json) - IoC container registration in cmd/container.go - Schema updates for azure.yaml (v1.0 and alpha) - Function app target: Go returns false for remote build - Fix rzip to preserve Unix file permissions in zip archives - Unit tests for all framework service lifecycle methods - Functional deploy test (Test_CLI_Up_Down_GoFuncApp) with Flex Consumption infrastructure (runtime: go 1.0, managed identity, blob deploy) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b59b004 to
c0fb25a
Compare
- Fail fast when host.json is missing instead of silently skipping - Fix duplicated 'the' typo in Bicep parameter description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump min Go version to 1.22 (align with azure-functions-golang-worker) - Add ToolInPath check before running go version - Pass azd env vars to ModDownload for GOPROXY/GOPRIVATE support - Handle os.IsNotExist vs other stat errors for host.json - Use binaryPath metadata from build artifacts in Package step - Enforce remoteBuild=false for Go (error on true) - Add unit tests for Go remote build behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore min Go version to 1.24 (aligns with Go Functions worker needs) - Clean up temp build directory on build failure to avoid leaking dirs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use ErrorWithSuggestion for Go remoteBuild=true error - Honor full binaryPath from build metadata (not just basename) - Add unit test for rzip preserving Unix file permissions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Guard against nil deployResult before reading Stdout/Stderr - Use http.NewRequestWithContext for context-bound health probe requests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add #nosec G107 suppression for health probe client.Do call - Document Windows limitation for binary chmod in Package step - Add Go CLI tool unit tests (CheckInstalled, Build, ModDownload) - Add parseServiceLanguage test cases for 'go' and 'golang' alias - Fix IsDotNet test to use ServiceLanguageGo instead of raw string Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix gosec G306: use os.WriteFile with 0600 then os.Chmod for execute bits - Validate relative binaryPath doesn't escape build dir with filepath.Clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| if string(kind) == "py" { | ||
| return ServiceLanguagePython, nil | ||
| } | ||
| if string(kind) == "golang" { |
There was a problem hiding this comment.
It seems odd to me that we are saying "here's the list of constants that are valid" above (ServiceLanguageKind), but then comparing against arbitrary strings here. I get that it's aliases, but it had to be explained to me - it's not clear why we end up with these variations, or why we only have to handle py and golang (for instance, why not 'js', and 'javascript' (ie, where do these aliases come from?)
There was a problem hiding this comment.
Good point — I've expanded the comment to explain that aliases exist for common shorthand users may write in azure.yaml (e.g. py for python, golang for go). The canonical constant names like javascript/typescript already match typical usage so they don't need separate aliases. Fixed in 1fe9bd5.
There was a problem hiding this comment.
But where do you decide that something is a 'common' alias? Is it telemetry-based, or intuition?
There was a problem hiding this comment.
Do we accept golang anywhere else?
There was a problem hiding this comment.
No, go/golang is new as language for all azd.
We don't have a way to decide about alias. I'm fine removing the alias if you only want go. The alias is coming automatically after looking what we do for python/py and dotnet/net --
|
@vhvb1989, I do think the actual SDK is pretty neat though, and I like that they followed the net/http signature of just having a body reader/writer like the stdlib http servers. Pretty cool stuff! |
- Clarify language alias comment in parseServiceLanguage - Print HTTP method in test sample function response Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JeffreyCA
left a comment
There was a problem hiding this comment.
I'm having trouble running the function after deploying from a Windows machine (Function host is not running error when accessing the endpoint):
I think it might be due to the generated .zip not having the executable permission set on the binary:
Binary must have execute permissions in the zip — fixed a pre-existing issue in rzip.go where Unix file modes were not preserved
If I instead take a packaged .zip generated from a Linux machine, then on Windows run azd deploy gofunc --from-package .\gofuncapp-gofunc-azddeploy-1781737383.zip it works:
On Windows, os.Chmod doesn't set Unix execute bits and info.Mode() returns 0666 for all files. This causes deployed binaries (e.g., Go Functions app) to lack execute permission when the zip is extracted on Linux. Fix rzip.addFile to default to 0755 on Windows, ensuring cross-platform deployments work correctly regardless of the build host OS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@JeffreyCA Thanks for catching the Windows issue! 🙏 The problem was in our The fix (fc2ffc5): I'll be running the ADO pipelines in live mode (no recordings) which exercises Windows, Linux, and macOS to validate the functional test works across all platforms. |
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
JeffreyCA
left a comment
There was a problem hiding this comment.
Thank you, works great!
hemarina
left a comment
There was a problem hiding this comment.
TL;DR: Clean POC that follows existing framework service patterns precisely. Cross-compilation, packaging, remote build rejection, and functional testing all look solid. One minor observation on the rzip change.
1. rzip.go: Blanket 0755 on all files when zipping on Windows
cli/azd/pkg/rzip/rzip.go:113-117
The fix sets header.SetMode(0755) for every file when runtime.GOOS == "windows". This is needed for Go binaries (they must be executable on Linux), but it applies to all zip-based deployments from Windows — Python, Node, .NET, Java included. Config files, JSON, etc. would also get execute permission in the zip.
This is practically harmless since Azure Functions doesn't enforce file permissions for interpreted languages. Just wanted to flag it in case precision matters down the road — one option could be letting callers pass a per-file mode callback to CreateFromDirectory, or defaulting to 0644 and using 0755 only for files that need it.
What looks good:
- Framework service follows existing patterns precisely (Python, Java, .NET)
- Cross-compilation env vars (
GOOS=linux,GOARCH=amd64,CGO_ENABLED=0) are correct binaryPathvalidation inPackage()guards against path traversal- Remote build rejection with helpful error message and suggestion
golangalias forgolanguage kind is a nice UX touch- Comprehensive unit tests covering restore, build, package, missing host.json, and no build output
- Functional test with retry logic for cold-start timing
| } else { | ||
| // Validate relative path doesn't escape the build directory | ||
| cleaned := filepath.Clean(bp) | ||
| if strings.HasPrefix(cleaned, "..") { |
There was a problem hiding this comment.
Do we have a shared helper for logic like this? Feels like someone skipping the .Clean could have things go poorly.
| name: "go is not dotnet", | ||
| kind: ServiceLanguageGo, | ||
| expected: false, |
There was a problem hiding this comment.
Should we add a different service lang like Forth so we can keep validating the unknown behavior?
|
|
||
| const ( | ||
| // goBinaryName is the compiled binary name for Azure Functions Go worker on Linux. | ||
| goBinaryName = "app" |
There was a problem hiding this comment.
goBinaryName = "app" (and the hardcoded GOOS=linux/GOARCH=amd64 in Build) encode the Flex Consumption Go worker contract. That's fine for the current target, but the dependency on the platform-expected binary name is implicit. Consider a short comment linking to the platform/runtime doc that defines this contract, so a future change (e.g. a different worker name, or arm64 support) has a clear source of truth. Non-blocking.
| if string(kind) == "py" { | ||
| return ServiceLanguagePython, nil | ||
| } | ||
| if string(kind) == "golang" { |
There was a problem hiding this comment.
The "golang" alias is accepted here, but the azure.yaml schemas (schemas/v1.0/azure.yaml.json and schemas/alpha/azure.yaml.json) only list "go". A user who writes language: golang will get a schema validation warning in their editor even though azd accepts it.
The existing py/python precedent keeps both forms in the schema enum. Suggest either adding "golang" to both schema enums to match, or dropping the alias — to keep schema and behavior in sync.
| // that need to be executable on Linux (e.g., compiled binaries) would lose their | ||
| // execute bit in the zip. Default to 0755 on Windows to ensure cross-platform | ||
| // deployments work correctly. | ||
| if runtime.GOOS == "windows" { |
There was a problem hiding this comment.
rzip is a shared utility used by every service target that zips (App Service, Function Apps, SWA, etc.), so this mode change affects all of them, not just Go function deploys.
The Unix branch (SetMode(info.Mode())) preserving real modes is a reasonable general improvement. My concern is the Windows branch: a blanket 0755 marks every file in every Windows-produced zip as world-executable, which is a blunt heuristic driven by a Go-binary-specific need. rzip can't know which entry is the binary.
Could we instead extend the existing OnZipFn/options mechanism to let the caller flag executable entries (so only the Go binary gets +x)? That keeps this generic utility free of a target-specific assumption. At minimum, can you confirm making all Windows zip entries executable is acceptable for the existing callers?
POC: Go Azure Functions Support for azd core
Closes #8307
What this adds
A new
goservice language for Azure Functions, enablingazd upfor Go-based Function Apps on Flex Consumption. This follows the pattern of existing language framework services (Python, Node, Java, .NET).Components
pkg/tools/golang/golang.goExternalToolinterface —CheckInstalled()with min version Go 1.24,Build(),ModDownload()pkg/project/framework_service_go.goFrameworkService— Restore (go mod download), Build (cross-compileGOOS=linux GOARCH=amd64 CGO_ENABLED=0), Package (binary + host.json)cmd/container.goschemas/v1.0/azure.yaml.json,schemas/alpha/azure.yaml.jsongoto language enumpkg/project/service_target_functionapp.gofalsefor remote buildpkg/rzip/rzip.gopkg/project/framework_service_go_test.gotest/functional/up_test.goTest_CLI_Up_Down_GoFuncApp— provision → deploy → health probe → cleanuptest/functional/testdata/samples/gofuncapp/Deployment model
functionAppConfig.runtime: { name: 'go', version: '1.0' }worker.config.json— deployment package is justapp+host.jsonAzureWebJobsStorage__accountName)SystemAssignedIdentityKey learnings from prototyping
azure-functions-golang-workerSDKgo(notcustom) on Flex ConsumptionFUNCTIONS_WORKER_RUNTIMEapp setting is not needed (and is rejected by Flex Consumption)rzip.gowhere Unix file modes were not preservedTODO before merging