-
Notifications
You must be signed in to change notification settings - Fork 194
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
Add .webappignore
and .funcignore
Support for Service Packaging
#4258
base: main
Are you sure you want to change the base?
Changes from all commits
1fbb599
df008d0
e127df4
8713ace
bd95da3
f584724
c09bb29
de2fae6
37bd983
4579de5
a5303a9
ab875e2
27f042f
faca53f
03ad9a1
7fd5c2d
64a1c91
baf3e34
6d8333c
a3aa556
e64531c
cb82f33
caf5c69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,4 +55,4 @@ | |
"default": "auth login --use-device-code" | ||
} | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package dotignore | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/denormal/go-gitignore" | ||
) | ||
|
||
// ReadDotIgnoreFile reads the ignore file located at the root of the project directory. | ||
// If the ignoreFileName is blank or the file is not found, it returns nil, nil. | ||
func ReadDotIgnoreFile(projectDir string, ignoreFileName string) (gitignore.GitIgnore, error) { | ||
// Return nil if the ignoreFileName is empty | ||
if ignoreFileName == "" { | ||
return nil, nil | ||
} | ||
|
||
ignoreFilePath := filepath.Join(projectDir, ignoreFileName) | ||
if _, err := os.Stat(ignoreFilePath); os.IsNotExist(err) { | ||
// Return nil if the ignore file does not exist | ||
return nil, nil | ||
} | ||
|
||
ignoreMatcher, err := gitignore.NewFromFile(ignoreFilePath) | ||
if err != nil { | ||
return nil, fmt.Errorf("error reading %s file at %s: %w", ignoreFileName, ignoreFilePath, err) | ||
} | ||
|
||
return ignoreMatcher, nil | ||
} | ||
|
||
// ShouldIgnore determines whether a file or directory should be ignored based on the provided ignore matcher. | ||
func ShouldIgnore(relativePath string, isDir bool, ignoreMatcher gitignore.GitIgnore) bool { | ||
if ignoreMatcher != nil { | ||
if match := ignoreMatcher.Relative(relativePath, isDir); match != nil && match.Ignore() { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,34 +9,36 @@ import ( | |
"path/filepath" | ||
"time" | ||
|
||
"github.com/azure/azure-dev/cli/azd/pkg/dotignore" | ||
"github.com/azure/azure-dev/cli/azd/pkg/rzip" | ||
"github.com/otiai10/copy" | ||
) | ||
|
||
// CreateDeployableZip creates a zip file of a folder, recursively. | ||
// createDeployableZip creates a zip file of a folder, recursively. | ||
// Returns the path to the created zip file or an error if it fails. | ||
func createDeployableZip(projectName string, appName string, path string) (string, error) { | ||
// TODO: should probably avoid picking up files that weren't meant to be deployed (ie, local .env files, etc..) | ||
// Create the output zip file path | ||
filePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s-azddeploy-%d.zip", projectName, appName, time.Now().Unix())) | ||
zipFile, err := os.Create(filePath) | ||
if err != nil { | ||
return "", fmt.Errorf("failed when creating zip package to deploy %s: %w", appName, err) | ||
} | ||
|
||
if err := rzip.CreateFromDirectory(path, zipFile); err != nil { | ||
// if we fail here just do our best to close things out and cleanup | ||
// Zip the directory without any exclusions (they've already been handled in buildForZip) | ||
err = rzip.CreateFromDirectory(path, zipFile) | ||
if err != nil { | ||
zipFile.Close() | ||
os.Remove(zipFile.Name()) | ||
return "", err | ||
} | ||
|
||
// Close the zip file and return the path | ||
if err := zipFile.Close(); err != nil { | ||
// may fail but, again, we'll do our best to cleanup here. | ||
os.Remove(zipFile.Name()) | ||
return "", err | ||
} | ||
|
||
return zipFile.Name(), nil | ||
return filePath, nil | ||
} | ||
|
||
// excludeDirEntryCondition resolves when a file or directory should be considered or not as part of build, when build is a | ||
|
@@ -48,17 +50,43 @@ type buildForZipOptions struct { | |
excludeConditions []excludeDirEntryCondition | ||
} | ||
|
||
// buildForZip is use by projects which build strategy is to only copy the source code into a folder which is later | ||
// zipped for packaging. For example Python and Node framework languages. buildForZipOptions provides the specific | ||
// details for each language which should not be ever copied. | ||
func buildForZip(src, dst string, options buildForZipOptions) error { | ||
// buildForZip is used by projects to prepare a directory for | ||
// zipping, excluding files based on the ignore file and other conditions. | ||
func buildForZip(src, dst string, options buildForZipOptions, serviceConfig *ServiceConfig) error { | ||
// Lookup the appropriate ignore file name based on the service kind (Host) | ||
ignoreFileName := GetIgnoreFileNameByKind(serviceConfig.Host) | ||
|
||
// Read and honor the specified ignore file if it exists | ||
ignoreMatcher, err := dotignore.ReadDotIgnoreFile(src, ignoreFileName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we also thinking about supporting
In my mind, I think azd would honor both ignore files. If we're doing this, I think we should consider implementing the correct recursion logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ellismg had implemented There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .funcignore only honors files in the service root and in discussion with them and the app service team we decided to not honor recursive .ignore files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That is quite an interesting decision. From a dev perspective, one main reason why I took a closer look. It also turns out that
If we're already engaged in conversations with both app service and function teams, can we consider:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarifying that I meant to say..."service root", not the root of the repo. This implementation honors .ignore files in the root of the service. I think we are on the same page. |
||
if err != nil && !os.IsNotExist(err) { | ||
return fmt.Errorf("reading %s file: %w", ignoreFileName, err) | ||
} | ||
|
||
// Temporary array to build exclude conditions dynamically | ||
tempExcludeConditions := []excludeDirEntryCondition{} | ||
|
||
// If there's no .ignore file, add the provided excludeConditions | ||
if ignoreMatcher == nil { | ||
tempExcludeConditions = append(tempExcludeConditions, options.excludeConditions...) | ||
} else { | ||
// If there's a .ignore file, apply ignoreMatcher only | ||
tempExcludeConditions = append(tempExcludeConditions, func(path string, file os.FileInfo) bool { | ||
relativePath, err := filepath.Rel(src, path) | ||
if err == nil && dotignore.ShouldIgnore(relativePath, file.IsDir(), ignoreMatcher) { | ||
return true | ||
} | ||
return false | ||
}) | ||
} | ||
|
||
// these exclude conditions applies to all projects | ||
options.excludeConditions = append(options.excludeConditions, globalExcludeAzdFolder) | ||
// Always append the global exclusions (e.g., .azure folder) | ||
tempExcludeConditions = append(tempExcludeConditions, globalExcludeAzdFolder) | ||
|
||
// Copy the source directory to the destination, applying the final exclude conditions | ||
return copy.Copy(src, dst, copy.Options{ | ||
Skip: func(srcInfo os.FileInfo, src, dest string) (bool, error) { | ||
for _, checkExclude := range options.excludeConditions { | ||
// Apply exclude conditions (either the default or the ignoreMatcher) | ||
for _, checkExclude := range tempExcludeConditions { | ||
if checkExclude(src, srcInfo) { | ||
return true, nil | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,19 @@ type ServiceTarget interface { | |
) ([]string, error) | ||
} | ||
|
||
// GetIgnoreFileNameByKind returns the appropriate ignore file name (e.g., .funcignore, .webappignore) | ||
// based on the service target kind. | ||
func GetIgnoreFileNameByKind(kind ServiceTargetKind) string { | ||
switch kind { | ||
case AzureFunctionTarget: | ||
return ".funcignore" | ||
case AppServiceTarget: | ||
return ".webappignore" | ||
default: | ||
return "" | ||
} | ||
} | ||
Comment on lines
+99
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I debated on whether or not we should have a helper method like this, or add this to the interface, but since only a few of the service targets will have this capability I figured we should add it as a helper method to not mess with the interface of the service target. But I could be convinced the other direction, depending on how you want to design this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like flexibility -- simple functions are great for that purpose. I'm okay here. |
||
|
||
// NewServiceDeployResult is a helper function to create a new ServiceDeployResult | ||
func NewServiceDeployResult( | ||
relatedResourceId string, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming
filePath
gives us the full absolute path whilezipFile.Name()
was only giving us the filename? I would just double check that this is honored downstream of the caller.