-
Notifications
You must be signed in to change notification settings - Fork 338
Add Go Azure Functions framework service support #8599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c0fb25a
Add Go Azure Functions framework service support (#8307)
vhvb1989 a00b9b6
fix: address Copilot review feedback (iteration 1)
vhvb1989 ac1f8e3
fix: address Copilot review feedback (iteration 2)
vhvb1989 220c6c6
fix: address Copilot review feedback (iteration 3)
vhvb1989 99c5b4a
fix: address Copilot review feedback (iteration 4)
vhvb1989 383e32f
fix: address Copilot review feedback (iteration 5)
vhvb1989 1b25bc3
fix: address Copilot review feedback (iteration 6)
vhvb1989 80633dc
fix: address Copilot review feedback (iteration 7)
vhvb1989 e20cf6d
fix: address Copilot review feedback (iteration 8)
vhvb1989 eb02aa4
fix: address remaining Copilot review comments
vhvb1989 c11daaa
fix: add unit test for missing host.json error path
vhvb1989 17a596a
fix: resolve CI failures and remaining review comment
vhvb1989 1fe9bd5
fix: address human review feedback from @richardpark-msft
vhvb1989 fc2ffc5
fix: ensure zip entries have execute permission on Windows
vhvb1989 3d850d2
address review feedback: simplify path resolution, scope zip +x to Go…
vhvb1989 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ words: | |
| - azdignore | ||
| - gofmt | ||
| - golangci | ||
| - golangtools | ||
| - lightspeed | ||
| - runewidth | ||
| - toplevel | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
|
vhvb1989 marked this conversation as resolved.
|
||
| // Licensed under the MIT License. | ||
|
|
||
| package project | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "runtime" | ||
|
|
||
| "github.com/azure/azure-dev/cli/azd/pkg/async" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/environment" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/tools" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/tools/golang" | ||
| "github.com/otiai10/copy" | ||
| ) | ||
|
|
||
| const ( | ||
| // goBinaryName is the compiled binary name for the Azure Functions Go worker on Linux. | ||
| // | ||
| // The binary name ("app"), Linux/amd64 target, and CGO being disabled are dictated by | ||
| // the Flex Consumption Go worker contract: the platform ships a proxy that execs this | ||
| // binary by name. See the worker spec for the authoritative source of truth: | ||
| // https://github.com/Azure/azure-functions-golang-worker | ||
| // A future change (e.g. a different worker name, or arm64 support) should update both | ||
| // this constant and the build target env vars in Build below. | ||
| goBinaryName = "app" | ||
|
vhvb1989 marked this conversation as resolved.
|
||
| ) | ||
|
|
||
| type goProject struct { | ||
| env *environment.Environment | ||
| goCli *golang.Cli | ||
| } | ||
|
|
||
| // NewGoProject creates a new instance of a Go project framework service. | ||
| func NewGoProject( | ||
| env *environment.Environment, | ||
| goCli *golang.Cli, | ||
| ) FrameworkService { | ||
| return &goProject{ | ||
| env: env, | ||
| goCli: goCli, | ||
| } | ||
| } | ||
|
|
||
| func (gp *goProject) Requirements() FrameworkRequirements { | ||
| return FrameworkRequirements{ | ||
| Package: FrameworkPackageRequirements{ | ||
| RequireRestore: true, | ||
| RequireBuild: true, | ||
| }, | ||
|
vhvb1989 marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| // RequiredExternalTools returns the Go CLI as a required tool. | ||
| func (gp *goProject) RequiredExternalTools( | ||
| _ context.Context, | ||
| _ *ServiceConfig, | ||
| ) []tools.ExternalTool { | ||
| return []tools.ExternalTool{gp.goCli} | ||
| } | ||
|
|
||
| // Initialize is a no-op for Go projects. | ||
| func (gp *goProject) Initialize( | ||
| ctx context.Context, | ||
| serviceConfig *ServiceConfig, | ||
| ) error { | ||
| return nil | ||
|
vhvb1989 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // Restore downloads Go module dependencies. | ||
| func (gp *goProject) Restore( | ||
| ctx context.Context, | ||
| serviceConfig *ServiceConfig, | ||
| serviceContext *ServiceContext, | ||
| progress *async.Progress[ServiceProgress], | ||
| ) (*ServiceRestoreResult, error) { | ||
| progress.SetProgress(NewServiceProgress("Downloading Go modules")) | ||
| if err := gp.goCli.ModDownload(ctx, serviceConfig.Path(), gp.env.Environ()); err != nil { | ||
| return nil, fmt.Errorf("restoring Go dependencies: %w", err) | ||
| } | ||
|
|
||
| return &ServiceRestoreResult{ | ||
| Artifacts: ArtifactCollection{ | ||
| { | ||
| Kind: ArtifactKindDirectory, | ||
| Location: serviceConfig.Path(), | ||
| LocationKind: LocationKindLocal, | ||
| Metadata: map[string]string{ | ||
| "projectPath": serviceConfig.Path(), | ||
| "framework": "go", | ||
| }, | ||
| }, | ||
| }, | ||
| }, nil | ||
| } | ||
|
|
||
| // Build compiles the Go project, cross-compiling for linux/amd64. | ||
| func (gp *goProject) Build( | ||
| ctx context.Context, | ||
| serviceConfig *ServiceConfig, | ||
| serviceContext *ServiceContext, | ||
| progress *async.Progress[ServiceProgress], | ||
| ) (*ServiceBuildResult, error) { | ||
| progress.SetProgress(NewServiceProgress("Compiling Go project")) | ||
|
|
||
| buildDir, err := os.MkdirTemp("", "azd-go-build") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("creating build directory: %w", err) | ||
| } | ||
|
|
||
| outputPath := filepath.Join(buildDir, goBinaryName) | ||
|
|
||
| // Cross-compile for linux/amd64 with CGO disabled, as required by the Flex | ||
| // Consumption Go worker contract (see goBinaryName above for the spec link). | ||
| buildEnv := append( | ||
| gp.env.Environ(), | ||
| "GOOS=linux", | ||
| "GOARCH=amd64", | ||
| "CGO_ENABLED=0", | ||
| ) | ||
|
|
||
| if err := gp.goCli.Build( | ||
| ctx, serviceConfig.Path(), outputPath, buildEnv, | ||
| ); err != nil { | ||
| os.RemoveAll(buildDir) | ||
| return nil, fmt.Errorf("compiling Go project: %w", err) | ||
| } | ||
|
|
||
| return &ServiceBuildResult{ | ||
| Artifacts: ArtifactCollection{ | ||
| { | ||
| Kind: ArtifactKindDirectory, | ||
| Location: buildDir, | ||
| LocationKind: LocationKindLocal, | ||
| Metadata: map[string]string{ | ||
| "buildPath": buildDir, | ||
| "binaryPath": outputPath, | ||
| "framework": "go", | ||
| "targetOS": "linux", | ||
| "targetArch": "amd64", | ||
| "buildOS": runtime.GOOS, | ||
| "buildArch": runtime.GOARCH, | ||
| }, | ||
| }, | ||
| }, | ||
| }, nil | ||
| } | ||
|
|
||
| // Package stages the compiled binary and host.json into a deployment directory | ||
| // suitable for Azure Functions zip deploy. | ||
| // On Flex Consumption with runtime 'go', the platform provides the worker.config.json | ||
| // and proxy binary — the deployment package only needs the app binary and host.json. | ||
| func (gp *goProject) Package( | ||
|
vhvb1989 marked this conversation as resolved.
|
||
| ctx context.Context, | ||
| serviceConfig *ServiceConfig, | ||
| serviceContext *ServiceContext, | ||
| progress *async.Progress[ServiceProgress], | ||
| ) (*ServicePackageResult, error) { | ||
| progress.SetProgress(NewServiceProgress("Staging Go Functions deployment")) | ||
|
|
||
| // Resolve the compiled binary path from the build artifact. We own the Build | ||
| // step (see Build above), which always records an absolute binaryPath pointing | ||
| // at <buildDir>/app, so we can trust the metadata directly without sanitizing | ||
| // against path traversal. Fall back to <buildDir>/app if the metadata is absent. | ||
| artifact, found := serviceContext.Build.FindFirst(WithKind(ArtifactKindDirectory)) | ||
| if !found { | ||
| return nil, fmt.Errorf("no build output found in service context") | ||
| } | ||
| binaryPath := artifact.Metadata["binaryPath"] | ||
| if binaryPath == "" { | ||
| binaryPath = filepath.Join(artifact.Location, goBinaryName) | ||
| } | ||
|
|
||
| packageDir, err := os.MkdirTemp("", "azd-go-package") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("creating package directory: %w", err) | ||
| } | ||
|
|
||
| // Copy compiled binary and ensure execute permission is set. | ||
| // On Windows, os.Chmod is a no-op for Unix execute bits; the binary is instead | ||
| // flagged executable at zip time via rzip.WithExecutableMatcher (see createDeployableZip). | ||
| progress.SetProgress(NewServiceProgress("Copying compiled binary")) | ||
| destBinaryPath := filepath.Join(packageDir, filepath.Base(binaryPath)) | ||
| if err := copy.Copy(binaryPath, destBinaryPath); err != nil { | ||
| return nil, fmt.Errorf("copying Go binary: %w", err) | ||
| } | ||
| if err := os.Chmod(destBinaryPath, 0755); err != nil { | ||
| return nil, fmt.Errorf("setting binary permissions: %w", err) | ||
| } | ||
|
vhvb1989 marked this conversation as resolved.
|
||
|
|
||
| // Copy host.json from user project (required for Azure Functions deployment) | ||
| hostJSONSrc := filepath.Join(serviceConfig.Path(), "host.json") | ||
| if _, err := os.Stat(hostJSONSrc); err != nil { | ||
| if os.IsNotExist(err) { | ||
| return nil, fmt.Errorf( | ||
| "host.json not found at %q: Azure Functions requires a host.json file in the project directory", | ||
| hostJSONSrc, | ||
| ) | ||
| } | ||
| return nil, fmt.Errorf("checking host.json at %q: %w", hostJSONSrc, err) | ||
| } | ||
|
Copilot marked this conversation as resolved.
|
||
| if err := copy.Copy( | ||
| hostJSONSrc, filepath.Join(packageDir, "host.json"), | ||
| ); err != nil { | ||
| return nil, fmt.Errorf("copying host.json: %w", err) | ||
| } | ||
|
|
||
| return &ServicePackageResult{ | ||
| Artifacts: ArtifactCollection{ | ||
| { | ||
| Kind: ArtifactKindDirectory, | ||
| Location: packageDir, | ||
| LocationKind: LocationKindLocal, | ||
| Metadata: map[string]string{ | ||
| "packagePath": packageDir, | ||
| "framework": "go", | ||
| "host": "azure-function", | ||
| }, | ||
| }, | ||
| }, | ||
| }, nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.