Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cli/azd/pkg/project/project_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ func createDeployableZip(svc *ServiceConfig, root string) (string, error) {
}
} else if svc.Language == ServiceLanguageJavaScript || svc.Language == ServiceLanguageTypeScript {
if name == "node_modules" && isDir {
if svc.RemoteBuild != nil && !*svc.RemoteBuild {
// if remote build is false, we do not exclude node_modules by default
return true, nil
}

Comment thread
weikanglim marked this conversation as resolved.
return false, nil
}
}
Expand Down
75 changes: 67 additions & 8 deletions cli/azd/pkg/project/service_target_functionapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package project

import (
"bytes"
"context"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
Expand All @@ -17,8 +20,67 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/tools"
"github.com/denormal/go-gitignore"
)

// resolveFunctionAppRemoteBuild returns the appropriate remote build setting for function apps.
func resolveFunctionAppRemoteBuild(serviceConfig *ServiceConfig) (remoteBuild bool, err error) {
switch serviceConfig.Language {
case ServiceLanguageJavaScript, ServiceLanguageTypeScript:
Comment thread
weikanglim marked this conversation as resolved.
ignoreFile := serviceConfig.Host.IgnoreFile()
ignoreFilePath := filepath.Join(serviceConfig.Path(), ignoreFile)
ignoreFileContents, err := os.ReadFile(ignoreFilePath)
Comment thread
weikanglim marked this conversation as resolved.
if errors.Is(err, fs.ErrNotExist) {
if serviceConfig.RemoteBuild != nil {
// no ignore file, nothing to validate -- return true
return *serviceConfig.RemoteBuild, nil
}

// no ignore file, default to true
return true, nil
Comment thread
weikanglim marked this conversation as resolved.
}

if err != nil {
return false, fmt.Errorf("reading ignore file: %w", err)
}

// Parse from in-memory contents so we don't hold an open file handle (important on Windows temp dirs).
ignore := gitignore.New(bytes.NewReader(ignoreFileContents), serviceConfig.Path(), nil)

nodeModulesExcluded := false
if match := ignore.Relative("node_modules", true); match != nil && match.Ignore() {
nodeModulesExcluded = true
}

if serviceConfig.RemoteBuild == nil { // remoteBuild option unset
// enable remote build only if 'node_modules' is excluded
return nodeModulesExcluded, nil
}

if *serviceConfig.RemoteBuild && !nodeModulesExcluded {
Comment thread
weikanglim marked this conversation as resolved.
return false, &internal.ErrorWithSuggestion{
Comment thread
weikanglim marked this conversation as resolved.
Err: fmt.Errorf("'remoteBuild: true' requires '%s' to exclude node_modules", ignoreFile),
Suggestion: fmt.Sprintf("Update '%s' to exclude node_modules, or set 'remoteBuild: false'.", ignoreFile),
}
}

if !*serviceConfig.RemoteBuild && nodeModulesExcluded {
return false, &internal.ErrorWithSuggestion{
Err: fmt.Errorf("'remoteBuild: false' cannot be used when '%s' excludes node_modules", ignoreFile),
Suggestion: fmt.Sprintf("Set 'remoteBuild: true', or remove node_modules from '%s'.", ignoreFile),
}
}

return *serviceConfig.RemoteBuild, nil
default:
if serviceConfig.RemoteBuild != nil {
return *serviceConfig.RemoteBuild, nil
}

return serviceConfig.Language == ServiceLanguagePython, nil
}
}

// functionAppTarget specifies an Azure Function to deploy to.
// Implements `project.ServiceTarget`
type functionAppTarget struct {
Expand Down Expand Up @@ -156,17 +218,14 @@ func (f *functionAppTarget) Deploy(
}

progress.SetProgress(NewServiceProgress("Uploading deployment package"))
var remoteBuild bool
if serviceConfig.RemoteBuild != nil {
remoteBuild = *serviceConfig.RemoteBuild
} else {
remoteBuild = serviceConfig.Language == ServiceLanguageJavaScript ||
serviceConfig.Language == ServiceLanguageTypeScript ||
serviceConfig.Language == ServiceLanguagePython
}

// Deploy to appropriate plan type
if isFlexConsumption {
remoteBuild, buildErr := resolveFunctionAppRemoteBuild(serviceConfig)
if buildErr != nil {
return nil, buildErr
}

_, err = f.cli.DeployFunctionAppUsingZipFileFlexConsumption(
ctx,
targetResource.SubscriptionId(),
Expand Down
114 changes: 114 additions & 0 deletions cli/azd/pkg/project/service_target_functionapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
package project

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -45,3 +48,114 @@ func TestNewFunctionAppTargetTypeValidation(t *testing.T) {
})
}
}

func TestResolveFunctionAppRemoteBuild_JavaScriptMatrix(t *testing.T) {
t.Parallel()

tests := []struct {
name string
remoteBuild *bool
funcIgnoreContent string
expectRemoteBuild bool
expectError string
}{
{
name: "NoRemoteBuildAndFuncIgnoreExcludesNodeModules_RemoteBuildEnabled",
remoteBuild: nil,
funcIgnoreContent: "node_modules\n",
expectRemoteBuild: true,
},
{
name: "NoRemoteBuildAndFuncIgnoreDoesNotExcludeNodeModules_RemoteBuildDisabled",
remoteBuild: nil,
funcIgnoreContent: "dist\n",
expectRemoteBuild: false,
},
{
name: "NoRemoteBuildAndMissingFuncIgnore_RemoteBuildEnabled",
remoteBuild: nil,
funcIgnoreContent: "",
expectRemoteBuild: true,
},
{
name: "RemoteBuildFalseAndMissingFuncIgnore_RemoteBuildDisabled",
remoteBuild: new(false),
funcIgnoreContent: "",
expectRemoteBuild: false,
},
{
name: "RemoteBuildFalseAndFuncIgnoreExcludesNodeModules_Errors",
remoteBuild: new(false),
Comment thread
weikanglim marked this conversation as resolved.
funcIgnoreContent: "node_modules\n",
expectError: "'remoteBuild: false' cannot be used when '.funcignore' excludes node_modules",
},
{
name: "RemoteBuildFalseAndFuncIgnoreDoesNotExcludeNodeModules_Succeeds",
remoteBuild: new(false),
funcIgnoreContent: "dist\n",
expectRemoteBuild: false,
},
{
name: "RemoteBuildTrueAndFuncIgnoreExcludesNodeModules_Succeeds",
remoteBuild: new(true),
Comment thread
weikanglim marked this conversation as resolved.
funcIgnoreContent: "node_modules\n",
expectRemoteBuild: true,
},
{
name: "RemoteBuildTrueAndFuncIgnoreDoesNotExcludeNodeModules_Errors",
remoteBuild: new(true),
Comment thread
weikanglim marked this conversation as resolved.
funcIgnoreContent: "dist\n",
expectError: "'remoteBuild: true' requires '.funcignore' to exclude node_modules",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

serviceConfig := createTestServiceConfig(t.TempDir(), AzureFunctionTarget, ServiceLanguageJavaScript)
serviceConfig.RemoteBuild = tt.remoteBuild

if tt.funcIgnoreContent != "" {
Comment thread
weikanglim marked this conversation as resolved.
err := os.WriteFile(
filepath.Join(serviceConfig.Path(), ".funcignore"),
Comment thread
weikanglim marked this conversation as resolved.
[]byte(tt.funcIgnoreContent),
0600,
)
require.NoError(t, err)
}

remoteBuild, err := resolveFunctionAppRemoteBuild(serviceConfig)
if tt.expectError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.expectError)

var suggestionErr *internal.ErrorWithSuggestion
require.ErrorAs(t, err, &suggestionErr)
return
}

require.NoError(t, err)
require.Equal(t, tt.expectRemoteBuild, remoteBuild)
})
}
}

func TestResolveFunctionAppRemoteBuild_NonJavaScriptDefaults(t *testing.T) {
t.Parallel()

pythonConfig := createTestServiceConfig(t.TempDir(), AzureFunctionTarget, ServiceLanguagePython)
remoteBuild, err := resolveFunctionAppRemoteBuild(pythonConfig)
require.NoError(t, err)
require.True(t, remoteBuild)

pythonConfig.RemoteBuild = new(false)
Comment thread
weikanglim marked this conversation as resolved.
remoteBuild, err = resolveFunctionAppRemoteBuild(pythonConfig)
require.NoError(t, err)
require.False(t, remoteBuild)

csharpConfig := createTestServiceConfig(t.TempDir(), AzureFunctionTarget, ServiceLanguageCsharp)
remoteBuild, err = resolveFunctionAppRemoteBuild(csharpConfig)
require.NoError(t, err)
require.False(t, remoteBuild)
}
Loading