diff --git a/builder/build.go b/builder/build.go index ca6af73b1..26b3c052f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -24,7 +24,7 @@ const AdditionalPackageBuildArg = "ADDITIONAL_PACKAGE" // BuildImage construct Docker image from function parameters // TODO: refactor signature to a struct to simplify the length of the method header -func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string) error { +func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, ignorePaths []string) error { if stack.IsValidTemplate(language) { pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language) @@ -48,7 +48,7 @@ func BuildImage(image string, handler string, functionName string, language stri return fmt.Errorf("building %s, %s is an invalid path", imageName, handler) } - tempPath, buildErr := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths) + tempPath, buildErr := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths, ignorePaths) fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language) if buildErr != nil { return buildErr @@ -184,7 +184,7 @@ func isRunningInCI() bool { } // createBuildContext creates temporary build folder to perform a Docker build with language template -func createBuildContext(functionName string, handler string, language string, useFunction bool, handlerFolder string, copyExtraPaths []string) (string, error) { +func createBuildContext(functionName string, handler string, language string, useFunction bool, handlerFolder string, copyExtraPaths []string, ignorePaths []string) (string, error) { tempPath := fmt.Sprintf("./build/%s/", functionName) fmt.Printf("Clearing temporary build folder: %s\n", tempPath) @@ -232,15 +232,21 @@ func createBuildContext(functionName string, handler string, language string, us return tempPath, readErr } + functionIgnorePaths := []string{} + for _, ignorePath := range ignorePaths { + functionIgnorePaths = append(functionIgnorePaths, filepath.Clean(path.Join(functionPath, ignorePath))) + } + for _, info := range infos { switch info.Name() { case "build", "template": fmt.Printf("Skipping \"%s\" folder\n", info.Name()) continue default: - copyErr := CopyFiles( + copyErr := CopyFilesWithIgnorePaths( filepath.Clean(path.Join(handler, info.Name())), filepath.Clean(path.Join(functionPath, info.Name())), + functionIgnorePaths, ) if copyErr != nil { @@ -257,9 +263,10 @@ func createBuildContext(functionName string, handler string, language string, us // Note that if useFunction is false, ie is a `dockerfile` template, then // functionPath == tempPath, the docker build context, not the `function` handler folder // inside the docker build context - copyErr := CopyFiles( + copyErr := CopyFilesWithIgnorePaths( extraPathAbs, filepath.Clean(path.Join(functionPath, extraPath)), + functionIgnorePaths, ) if copyErr != nil { diff --git a/builder/copy.go b/builder/copy.go index bac034a0a..ab025a83c 100644 --- a/builder/copy.go +++ b/builder/copy.go @@ -12,14 +12,26 @@ import ( // CopyFiles copies files from src to destination. func CopyFiles(src, dest string) error { + return CopyFilesWithIgnorePaths(src, dest, nil) +} + +// CopyFilesWithIgnorePaths copies files from src to destination, +// but ignores files or directories specified by ignorePaths +func CopyFilesWithIgnorePaths(src, dest string, ignorePaths []string) error { info, err := os.Stat(src) if err != nil { return err } + for _, ignorePath := range ignorePaths { + if dest == ignorePath { + return nil + } + } + if info.IsDir() { debugPrint(fmt.Sprintf("Creating directory: %s at %s", info.Name(), dest)) - return copyDir(src, dest) + return copyDir(src, dest, ignorePaths) } debugPrint(fmt.Sprintf("cp - %s %s", src, dest)) @@ -27,7 +39,7 @@ func CopyFiles(src, dest string) error { } // copyDir will recursively copy a directory to dest -func copyDir(src, dest string) error { +func copyDir(src, dest string, ignorePaths []string) error { info, err := os.Stat(src) if err != nil { return fmt.Errorf("error reading dest stats: %s", err.Error()) @@ -43,9 +55,10 @@ func copyDir(src, dest string) error { } for _, info := range infos { - if err := CopyFiles( + if err := CopyFilesWithIgnorePaths( filepath.Join(src, info.Name()), filepath.Join(dest, info.Name()), + ignorePaths, ); err != nil { return err } diff --git a/builder/copy_test.go b/builder/copy_test.go index 824f2b134..f84e15ea7 100644 --- a/builder/copy_test.go +++ b/builder/copy_test.go @@ -33,7 +33,7 @@ func Test_CopyFiles(t *testing.T) { t.Fatalf("Unexpected copy error\n%v", err) } - err = checkDestinationFiles(destDir, 2, mode) + err = checkDestinationFiles(destDir, 2, mode, nil) if err != nil { t.Fatalf("Destination file mode differs from source file mode\n%v", err) } @@ -71,12 +71,47 @@ func Test_CopyFiles_ToDestinationWithIntermediateFolder(t *testing.T) { t.Fatalf("Unexpected copy error\n%v", err) } - err = checkDestinationFiles(destDir+"/intermediate/", 1, mode) + err = checkDestinationFiles(destDir+"/intermediate/", 1, mode, nil) if err != nil { t.Fatalf("Destination file mode differs from source file mode\n%v", err) } } +func Test_CopyFilesWithIgnorePaths(t *testing.T) { + fileModes := []int{0600, 0640, 0644, 0700, 0755} + + dir := os.TempDir() + for _, mode := range fileModes { + // set up a source folder with 2 file + srcDir, srcDirErr := setupSourceFolder(2, mode) + if srcDirErr != nil { + log.Fatal("Error creating source folder") + } + defer os.RemoveAll(srcDir) + + // create a destination folder to copy the files to + destDir, destDirErr := ioutil.TempDir(dir, "openfaas-test-destination-") + if destDirErr != nil { + t.Fatalf("Error creating destination folder\n%v", destDirErr) + } + defer os.RemoveAll(destDir) + + ignorePaths := []string{ + fmt.Sprintf("%s/test-file-2", destDir), + } + + err := CopyFilesWithIgnorePaths(srcDir, destDir+"/", ignorePaths) + if err != nil { + t.Fatalf("Unexpected copy error\n%v", err) + } + + err = checkDestinationFiles(destDir, 2, mode, ignorePaths) + if err != nil { + t.Fatalf("Destination file mode differs from source file mode\n%v", err) + } + } +} + func setupSourceFolder(numberOfFiles, mode int) (string, error) { dir := os.TempDir() data := []byte("open faas") @@ -99,15 +134,26 @@ func setupSourceFolder(numberOfFiles, mode int) (string, error) { return srcDir, nil } -func checkDestinationFiles(dir string, numberOfFiles, mode int) error { +func checkDestinationFiles(dir string, numberOfFiles, mode int, ignorePaths []string) error { // Check each file inside the destination folder for i := 1; i <= numberOfFiles; i++ { - fileStat, err := os.Stat(fmt.Sprintf("%s/test-file-%d", dir, i)) - if os.IsNotExist(err) { - return err - } - if fileStat.Mode() != os.FileMode(mode) { - return errors.New("expected mode did not match") + filePath := fmt.Sprintf("%s/test-file-%d", dir, i) + fileStat, err := os.Stat(filePath) + + for _, ignorePath := range ignorePaths { + if filePath == ignorePath { + if !os.IsNotExist(err) { + return err + } + } else { + if os.IsNotExist(err) { + return err + } + + if fileStat.Mode() != os.FileMode(mode) { + return errors.New("expected mode did not match") + } + } } } diff --git a/builder/publish.go b/builder/publish.go index 24d4d6161..2b0559c02 100644 --- a/builder/publish.go +++ b/builder/publish.go @@ -16,7 +16,7 @@ import ( // PublishImage will publish images as multi-arch // TODO: refactor signature to a struct to simplify the length of the method header func PublishImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, - buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, platforms string, extraTags []string) error { + buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, ignorePaths []string, platforms string, extraTags []string) error { if stack.IsValidTemplate(language) { pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language) @@ -40,7 +40,7 @@ func PublishImage(image string, handler string, functionName string, language st return fmt.Errorf("building %s, %s is an invalid path", imageName, handler) } - tempPath, buildErr := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths) + tempPath, buildErr := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths, ignorePaths) fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language) if buildErr != nil { return buildErr diff --git a/commands/build.go b/commands/build.go index 7d22b8e84..9cc12dcf2 100644 --- a/commands/build.go +++ b/commands/build.go @@ -190,6 +190,7 @@ func runBuild(cmd *cobra.Command, args []string) error { buildLabelMap, quietBuild, copyExtra, + nil, ) if err != nil { return err @@ -255,6 +256,7 @@ func build(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool buildLabelMap, quietBuild, combinedExtraPaths, + function.IgnorePaths, ) if err != nil { diff --git a/commands/publish.go b/commands/publish.go index b7ce17d65..eabad650c 100644 --- a/commands/publish.go +++ b/commands/publish.go @@ -69,12 +69,12 @@ var publishCmd = &cobra.Command{ Short: "Builds and pushes multi-arch OpenFaaS container images", Long: `Builds and pushes multi-arch OpenFaaS container images using Docker buildx. Most users will want faas-cli build or faas-cli up for development and testing. -This command is designed to make releasing and publishing multi-arch container +This command is designed to make releasing and publishing multi-arch container images easier. -A stack.yaml file is required, and any images that are built will not be -available in the local Docker library. This is due to technical constraints in -Docker and buildx. You must use a multi-arch template to use this command with +A stack.yaml file is required, and any images that are built will not be +available in the local Docker library. This is due to technical constraints in +Docker and buildx. You must use a multi-arch template to use this command with correctly configured TARGETPLATFORM and BUILDPLATFORM arguments. See also: faas-cli build`, @@ -205,6 +205,7 @@ func publish(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bo buildLabelMap, quietBuild, combinedExtraPaths, + function.IgnorePaths, platforms, extraTags, ) diff --git a/commands/version.go b/commands/version.go index de859cf5a..0877f7902 100644 --- a/commands/version.go +++ b/commands/version.go @@ -39,7 +39,7 @@ func init() { // versionCmd displays version information var versionCmd = &cobra.Command{ Use: "version [--short-version] [--gateway GATEWAY_URL]", - Short: "Display the clients version information", + Short: "Display the clients version information. Foo", Long: fmt.Sprintf(`The version command returns the current clients version information. This currently consists of the GitSHA from which the client was built. @@ -115,7 +115,7 @@ func printServerVersions() error { Provider name: %s orchestration: %s - version: %s + version: %s sha: %s `, gatewayInfo.Provider.Name, gatewayInfo.Provider.Orchestration, gatewayInfo.Provider.Version.Release, gatewayInfo.Provider.Version.SHA) return nil diff --git a/sample/nodejs-echo/node_modules/foo b/sample/nodejs-echo/node_modules/foo new file mode 100644 index 000000000..e69de29bb diff --git a/stack.yml b/stack.yml index d458e077a..0aef5763c 100644 --- a/stack.yml +++ b/stack.yml @@ -32,6 +32,8 @@ functions: lang: node handler: ./sample/nodejs-echo image: alexellis/faas-nodejs-echo:0.1 + ignore_paths: + - node_modules # limits: # memory: 40m # requests: diff --git a/stack/schema.go b/stack/schema.go index 8f19ae234..44787280e 100644 --- a/stack/schema.go +++ b/stack/schema.go @@ -60,6 +60,10 @@ type Function struct { // BuildArgs for providing build-args BuildArgs map[string]string `yaml:"build_args,omitempty"` + // IgnorePaths are relative files or directories that will not be copied to the + // build folder e.g. node_modules + IgnorePaths []string `yaml:"ignore_paths,omitempty"` + // Platforms for use with buildx and faas-cli publish Platforms string `yaml:"platforms,omitempty"` }