diff --git a/build.go b/build.go index 34ac3c60..200cef4f 100644 --- a/build.go +++ b/build.go @@ -27,7 +27,7 @@ type SourceRemover interface { //go:generate faux --interface PublishProcess --output fakes/publish_process.go type PublishProcess interface { - Execute(workingDir, rootDir, nugetCachePath, projectPath, outputPath string, flags []string) error + Execute(workingDir, rootDir, nugetCachePath, intermediateBuildCachePath, projectPath, outputPath string, flags []string) error } //go:generate faux --interface BindingResolver --output fakes/binding_resolver.go @@ -99,8 +99,15 @@ func Build( nugetCache.Cache = true + intermediateBuildCache, err := context.Layers.Get("intermediate-build-cache") + if err != nil { + return packit.BuildResult{}, err + } + + intermediateBuildCache.Cache = true + logger.Process("Executing build process") - err = publishProcess.Execute(context.WorkingDir, os.Getenv("DOTNET_ROOT"), nugetCache.Path, projectPath, tempDir, flags) + err = publishProcess.Execute(context.WorkingDir, os.Getenv("DOTNET_ROOT"), nugetCache.Path, intermediateBuildCache.Path, projectPath, tempDir, flags) if err != nil { return packit.BuildResult{}, err } @@ -135,6 +142,16 @@ func Build( return packit.BuildResult{}, err } + exists, err = fs.Exists(intermediateBuildCache.Path) + if exists { + if !fs.IsEmptyDir(intermediateBuildCache.Path) { + layers = append(layers, intermediateBuildCache) + } + } + if err != nil { + return packit.BuildResult{}, err + } + return packit.BuildResult{ Layers: layers, }, nil diff --git a/build_test.go b/build_test.go index 26792e27..ba956bfa 100644 --- a/build_test.go +++ b/build_test.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" "testing" - "time" dotnetpublish "github.com/paketo-buildpacks/dotnet-publish" "github.com/paketo-buildpacks/dotnet-publish/fakes" @@ -23,7 +22,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { var ( Expect = NewWithT(t).Expect - timestamp time.Time buffer *bytes.Buffer workingDir string homeDir string @@ -66,17 +64,15 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(os.MkdirAll(filepath.Join(layersDir, "nuget-cache"), os.ModePerm)).To(Succeed()) Expect(os.WriteFile(filepath.Join(layersDir, "nuget-cache", "some-cache"), []byte{}, 0600)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(layersDir, "intermediate-build-cache"), os.ModePerm)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(layersDir, "intermediate-build-cache", "some-cache"), []byte{}, 0600)).To(Succeed()) + os.Setenv("DOTNET_ROOT", "some-existing-root-dir") buffer = bytes.NewBuffer(nil) logger := scribe.NewLogger(buffer) - timestamp = time.Now() - clock := chronos.NewClock(func() time.Time { - return timestamp - }) - - build = dotnetpublish.Build(sourceRemover, bindingResolver, homeDir, symlinker, publishProcess, buildpackYMLParser, commandConfigParser, clock, logger) + build = dotnetpublish.Build(sourceRemover, bindingResolver, homeDir, symlinker, publishProcess, buildpackYMLParser, commandConfigParser, chronos.DefaultClock, logger) }) it.After(func() { @@ -100,12 +96,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(1)) - layer := result.Layers[0] + Expect(result.Layers).To(HaveLen(2)) - Expect(layer.Name).To(Equal("nuget-cache")) - Expect(layer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache"))) - Expect(layer.Cache).To(BeTrue()) + nugetCacheLayer := result.Layers[0] + Expect(nugetCacheLayer.Name).To(Equal("nuget-cache")) + Expect(nugetCacheLayer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache"))) + Expect(nugetCacheLayer.Cache).To(BeTrue()) + + intermediateBuildCacheLayer := result.Layers[1] + Expect(intermediateBuildCacheLayer.Name).To(Equal("intermediate-build-cache")) + Expect(intermediateBuildCacheLayer.Path).To(Equal(filepath.Join(layersDir, "intermediate-build-cache"))) + Expect(intermediateBuildCacheLayer.Cache).To(BeTrue()) Expect(sourceRemover.RemoveCall.Receives.WorkingDir).To(Equal(workingDir)) Expect(sourceRemover.RemoveCall.Receives.PublishOutputDir).To(MatchRegexp(`dotnet-publish-output\d+`)) @@ -119,6 +120,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(publishProcess.ExecuteCall.Receives.WorkingDir).To(Equal(workingDir)) Expect(publishProcess.ExecuteCall.Receives.RootDir).To(Equal("some-existing-root-dir")) Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path")) + Expect(publishProcess.ExecuteCall.Receives.NugetCachePath).To(Equal(nugetCacheLayer.Path)) + Expect(publishProcess.ExecuteCall.Receives.IntermediateBuildCachePath).To(Equal(intermediateBuildCacheLayer.Path)) + Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path")) Expect(publishProcess.ExecuteCall.Receives.OutputPath).To(MatchRegexp(`dotnet-publish-output\d+`)) Expect(publishProcess.ExecuteCall.Receives.Flags).To(Equal([]string{"--publishflag", "value"})) @@ -128,9 +132,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(buffer.String()).To(ContainSubstring("Please specify the project path through the $BP_DOTNET_PROJECT_PATH environment variable instead. See README.md or the documentation on paketo.io for more information.")) }) - context("the cache layer is empty", func() { + context("the cache layers are empty", func() { it.Before(func() { Expect(os.Remove(filepath.Join(layersDir, "nuget-cache", "some-cache"))).To(Succeed()) + Expect(os.Remove(filepath.Join(layersDir, "intermediate-build-cache", "some-cache"))).To(Succeed()) }) it("does not return a layer", func() { @@ -171,12 +176,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) Expect(err).NotTo(HaveOccurred()) - Expect(result.Layers).To(HaveLen(1)) - layer := result.Layers[0] + Expect(result.Layers).To(HaveLen(2)) + + nugetCacheLayer := result.Layers[0] + Expect(nugetCacheLayer.Name).To(Equal("nuget-cache")) + Expect(nugetCacheLayer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache"))) + Expect(nugetCacheLayer.Cache).To(BeTrue()) - Expect(layer.Name).To(Equal("nuget-cache")) - Expect(layer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache"))) - Expect(layer.Cache).To(BeTrue()) + intermediateBuildCacheLayer := result.Layers[1] + Expect(intermediateBuildCacheLayer.Name).To(Equal("intermediate-build-cache")) + Expect(intermediateBuildCacheLayer.Path).To(Equal(filepath.Join(layersDir, "intermediate-build-cache"))) + Expect(intermediateBuildCacheLayer.Cache).To(BeTrue()) Expect(sourceRemover.RemoveCall.Receives.WorkingDir).To(Equal(workingDir)) Expect(sourceRemover.RemoveCall.Receives.PublishOutputDir).To(MatchRegexp(`dotnet-publish-output\d+`)) @@ -185,6 +195,8 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(publishProcess.ExecuteCall.Receives.WorkingDir).To(Equal(workingDir)) Expect(publishProcess.ExecuteCall.Receives.RootDir).To(Equal("some-existing-root-dir")) Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path")) + Expect(publishProcess.ExecuteCall.Receives.NugetCachePath).To(Equal(nugetCacheLayer.Path)) + Expect(publishProcess.ExecuteCall.Receives.IntermediateBuildCachePath).To(Equal(intermediateBuildCacheLayer.Path)) Expect(publishProcess.ExecuteCall.Receives.OutputPath).To(MatchRegexp(`dotnet-publish-output\d+`)) Expect(publishProcess.ExecuteCall.Receives.Flags).To(Equal([]string{"--publishflag", "value"})) diff --git a/dotnet_publish_process.go b/dotnet_publish_process.go index 79753ba4..a228ab6e 100644 --- a/dotnet_publish_process.go +++ b/dotnet_publish_process.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/paketo-buildpacks/packit/v2/chronos" + "github.com/paketo-buildpacks/packit/v2/fs" "github.com/paketo-buildpacks/packit/v2/pexec" "github.com/paketo-buildpacks/packit/v2/scribe" ) @@ -30,9 +31,15 @@ func NewDotnetPublishProcess(executable Executable, logger scribe.Logger, clock } } -func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, projectPath, outputPath string, flags []string) error { +func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, intermediateBuildCachePath, projectPath, outputPath string, flags []string) error { + err := loadBuildCache(workingDir, projectPath, intermediateBuildCachePath) + if err != nil { + return fmt.Errorf("failed to load build cache: %w", err) + } + args := []string{ - "publish", filepath.Join(workingDir, projectPath), // change to workingDir plus project path + "publish", + filepath.Join(workingDir, projectPath), } if !containsFlag(flags, "--configuration") && !containsFlag(flags, "-c") { @@ -73,5 +80,56 @@ func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, projectP p.logger.Action("Completed in %s", duration) p.logger.Break() + err = recreateBuildCache(workingDir, projectPath, intermediateBuildCachePath) + if err != nil { + return err + } + + return nil +} + +func loadBuildCache(workingDir, projectPath, cachePath string) error { + obj, err := fs.Exists(filepath.Join(cachePath, "obj")) + if err != nil { + return err + } + + if obj { + // RemoveAll to clear the contents of the directory, which fs.Copy won't do + err = os.RemoveAll(filepath.Join(workingDir, projectPath, "obj")) + if err != nil { + return err + } + err = fs.Copy(filepath.Join(cachePath, "obj"), filepath.Join(workingDir, projectPath, "obj")) + if err != nil { + return err + } + } + return nil +} + +func recreateBuildCache(workingDir, projectPath, cachePath string) error { + obj, err := fs.Exists(filepath.Join(workingDir, projectPath, "obj")) + if err != nil { + return fmt.Errorf("failed to locate build cache: %w", err) + } + + if obj { + // RemoveAll to clear the contents of the directory, which fs.Copy won't do + err = os.RemoveAll(filepath.Join(cachePath, "obj")) + if err != nil { + // not tested + return fmt.Errorf("failed to reset build cache: %w", err) + } + err = os.MkdirAll(filepath.Join(cachePath, "obj"), os.ModePerm) + if err != nil { + // not tested + return fmt.Errorf("failed to reset build cache: %w", err) + } + err = fs.Copy(filepath.Join(workingDir, projectPath, "obj"), filepath.Join(cachePath, "obj")) + if err != nil { + return fmt.Errorf("failed to store build cache: %w", err) + } + } return nil } diff --git a/dotnet_publish_process_test.go b/dotnet_publish_process_test.go index de951803..cc665bcf 100644 --- a/dotnet_publish_process_test.go +++ b/dotnet_publish_process_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "testing" "time" @@ -27,11 +28,21 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { path string executable *fakes.Executable process dotnetpublish.DotnetPublishProcess + cacheDir string + + workingDir string buffer *bytes.Buffer ) it.Before(func() { + var err error + workingDir, err = os.MkdirTemp("", "workingDir") + Expect(err).NotTo(HaveOccurred()) + + cacheDir, err = os.MkdirTemp("", cacheDir) + Expect(err).NotTo(HaveOccurred()) + path = os.Getenv("PATH") Expect(os.Setenv("PATH", "some-path")).To(Succeed()) @@ -65,14 +76,17 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { it.After(func() { Expect(os.Setenv("PATH", path)).To(Succeed()) + Expect(os.RemoveAll(workingDir)).To(Succeed()) + Expect(os.RemoveAll(cacheDir)).To(Succeed()) }) it("executes the dotnet publish process", func() { - err := process.Execute("some-working-dir", "some-dotnet-root-dir", "some/nuget/cache/path", "some/project/path", "some-publish-output-dir", []string{"--flag", "value"}) + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-publish-output-dir", []string{"--flag", "value"}) Expect(err).NotTo(HaveOccurred()) args := []string{ - "publish", "some-working-dir/some/project/path", + "publish", + workingDir, "--configuration", "Release", "--runtime", "ubuntu.18.04-x64", "--self-contained", "false", @@ -82,7 +96,7 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { Expect(executable.ExecuteCall.Receives.Execution.Args).To(Equal(args)) - Expect(executable.ExecuteCall.Receives.Execution.Dir).To(Equal("some-working-dir")) + Expect(executable.ExecuteCall.Receives.Execution.Dir).To(Equal(workingDir)) Expect(executable.ExecuteCall.Receives.Execution.Env).To(ContainElement("PATH=some-dotnet-root-dir:some-path")) Expect(executable.ExecuteCall.Receives.Execution.Env).To(ContainElement("NUGET_PACKAGES=some/nuget/cache/path")) @@ -94,9 +108,47 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { )) }) + context("when there is a project specific path", func() { + var projectPath string + + it.Before(func() { + projectPath = filepath.Join("src", "project") + + Expect(os.MkdirAll(filepath.Join(workingDir, projectPath), os.ModePerm)).To(Succeed()) + }) + + it("executes the dotnet publish process", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, projectPath, "some-publish-output-dir", []string{"--flag", "value"}) + Expect(err).NotTo(HaveOccurred()) + + args := []string{ + "publish", + filepath.Join(workingDir, projectPath), + "--configuration", "Release", + "--runtime", "ubuntu.18.04-x64", + "--self-contained", "false", + "--output", "some-publish-output-dir", + "--flag", "value", + } + + Expect(executable.ExecuteCall.Receives.Execution.Args).To(Equal(args)) + + Expect(executable.ExecuteCall.Receives.Execution.Dir).To(Equal(workingDir)) + Expect(executable.ExecuteCall.Receives.Execution.Env).To(ContainElement("PATH=some-dotnet-root-dir:some-path")) + Expect(executable.ExecuteCall.Receives.Execution.Env).To(ContainElement("NUGET_PACKAGES=some/nuget/cache/path")) + + Expect(buffer.String()).To(ContainLines( + fmt.Sprintf(" Running 'dotnet %s'", strings.Join(args, " ")), + " stdout-output", + " stderr-output", + " Completed in 1s", + )) + }) + }) + context("when the user passes flags that the buildpack sets by default", func() { it("overrides the default value with the user-provided one", func() { - err := process.Execute("some-working-dir", "some-dotnet-root-dir", "some/nuget/cache/path", "some/project/path", "some-publish-output-dir", + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-publish-output-dir", []string{"--runtime", "user-value", "--self-contained=true", "--configuration", "UserConfiguration", @@ -105,7 +157,8 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) args := []string{ - "publish", "some-working-dir/some/project/path", + "publish", + workingDir, "--runtime", "user-value", "--self-contained=true", "--configuration", "UserConfiguration", @@ -118,11 +171,12 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { context("when the user passes --no-self-contained, equivalent to --self-contained=false", func() { it("overrides the buildpack's value for self-contained with the user-provided one", func() { - err := process.Execute("some-working-dir", "some-dotnet-root-dir", "some/nuget/cache/path", "some/project/path", "some-publish-output-dir", []string{"--no-self-contained"}) + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-publish-output-dir", []string{"--no-self-contained"}) Expect(err).NotTo(HaveOccurred()) args := []string{ - "publish", "some-working-dir/some/project/path", + "publish", + workingDir, "--configuration", "Release", "--runtime", "ubuntu.18.04-x64", "--output", "some-publish-output-dir", @@ -132,8 +186,132 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { Expect(executable.ExecuteCall.Receives.Execution.Args).To(Equal(args)) }) }) + context("when the build generates an obj directory", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(workingDir, "project-path"), os.ModePerm)).To(Succeed()) + executable.ExecuteCall.Stub = func(e pexec.Execution) error { + return os.MkdirAll(filepath.Join(workingDir, "project-path", "obj"), os.ModePerm) + } + Expect(os.MkdirAll(filepath.Join(cacheDir, "obj"), os.ModePerm)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(cacheDir, "obj", "some-contents"), nil, os.ModePerm)).To(Succeed()) + }) + it("copies the obj directory into the cache layer", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "project-path", "some-publish-output-dir", []string{"--no-self-contained"}) + Expect(err).NotTo(HaveOccurred()) + + Expect(filepath.Join(cacheDir, "obj")).To(BeADirectory()) + }) + context("and the cache layer directory doesn't exist yet", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(workingDir, "project-path"), os.ModePerm)).To(Succeed()) + executable.ExecuteCall.Stub = func(e pexec.Execution) error { + return os.MkdirAll(filepath.Join(workingDir, "project-path", "obj"), os.ModePerm) + } + Expect(os.RemoveAll(cacheDir)).To(Succeed()) + }) + it("creates the layer dir and moves the cache contents into it", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "project-path", "some-publish-output-dir", []string{"--no-self-contained"}) + Expect(err).NotTo(HaveOccurred()) + + Expect(filepath.Join(cacheDir, "obj")).To(BeADirectory()) + }) + }) + }) + + context("when the cache has an obj directory from a previous build", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(cacheDir, "obj"), os.ModePerm)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(workingDir, "project-path"), os.ModePerm)).To(Succeed()) + }) + it("copies the obj directory into the working directory", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "project-path", "some-publish-output-dir", []string{"--no-self-contained"}) + Expect(err).NotTo(HaveOccurred()) + + Expect(filepath.Join(workingDir, "project-path", "obj")).To(BeADirectory()) + }) + context("and there's already an obj directory in the /workspace", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(cacheDir, "obj"), os.ModePerm)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(workingDir, "project-path", "obj"), os.ModePerm)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "project-path", "obj", "some-contents"), nil, os.ModePerm)).To(Succeed()) + }) + it("overwrites the /workspace obj with the layer obj dir", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "project-path", "some-publish-output-dir", []string{"--no-self-contained"}) + Expect(err).NotTo(HaveOccurred()) + + Expect(filepath.Join(workingDir, "project-path", "obj")).To(BeADirectory()) + }) + }) + }) context("failure cases", func() { + context("when check for existence of obj file in cache layer fails", func() { + it.Before(func() { + Expect(os.Chmod(cacheDir, 0000)).To(Succeed()) + }) + it.After(func() { + Expect(os.Chmod(cacheDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + context("when removing obj directory in working dir fails", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(cacheDir, "obj"), os.ModePerm)) + Expect(os.MkdirAll(filepath.Join(workingDir, "obj"), 0500)).To(Succeed()) + Expect(os.Chmod(workingDir, 0500)).To(Succeed()) + }) + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + context("when copying build cache into working directory fails", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(cacheDir, "obj"), 0200)) + }) + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + + context("when checking for obj dir generated during build fails", func() { + it.Before(func() { + Expect(os.Chmod(workingDir, 0200)).To(Succeed()) + }) + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + + context("when copying obj dir into cache layer fails", func() { + it.Before(func() { + Expect(os.MkdirAll(filepath.Join(workingDir, "obj"), 0000)) + }) + + it("returns an error", func() { + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + context("when the dotnet publish executable errors", func() { it.Before(func() { executable.ExecuteCall.Stub = func(execution pexec.Execution) error { @@ -145,12 +323,12 @@ func testDotnetPublishProcess(t *testing.T, context spec.G, it spec.S) { }) it("returns an error", func() { - err := process.Execute("some-working-dir", "some-dotnet-root-dir", "some/nuget/cache/path", "", "some-output-dir", []string{}) + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) Expect(err).To(MatchError("failed to execute 'dotnet publish': execution error")) }) it("logs the command output", func() { - err := process.Execute("some-working-dir", "some-dotnet-root-dir", "some/nuget/cache/path", "", "some-output-dir", []string{}) + err := process.Execute(workingDir, "some-dotnet-root-dir", "some/nuget/cache/path", cacheDir, "", "some-output-dir", []string{}) Expect(err).To(HaveOccurred()) Expect(buffer.String()).To(ContainLines( diff --git a/fakes/publish_process.go b/fakes/publish_process.go index 323f60e2..8ccecf43 100644 --- a/fakes/publish_process.go +++ b/fakes/publish_process.go @@ -7,32 +7,34 @@ type PublishProcess struct { mutex sync.Mutex CallCount int Receives struct { - WorkingDir string - RootDir string - NugetCachePath string - ProjectPath string - OutputPath string - Flags []string + WorkingDir string + RootDir string + NugetCachePath string + IntermediateBuildCachePath string + ProjectPath string + OutputPath string + Flags []string } Returns struct { Error error } - Stub func(string, string, string, string, string, []string) error + Stub func(string, string, string, string, string, string, []string) error } } -func (f *PublishProcess) Execute(param1 string, param2 string, param3 string, param4 string, param5 string, param6 []string) error { +func (f *PublishProcess) Execute(param1 string, param2 string, param3 string, param4 string, param5 string, param6 string, param7 []string) error { f.ExecuteCall.mutex.Lock() defer f.ExecuteCall.mutex.Unlock() f.ExecuteCall.CallCount++ f.ExecuteCall.Receives.WorkingDir = param1 f.ExecuteCall.Receives.RootDir = param2 f.ExecuteCall.Receives.NugetCachePath = param3 - f.ExecuteCall.Receives.ProjectPath = param4 - f.ExecuteCall.Receives.OutputPath = param5 - f.ExecuteCall.Receives.Flags = param6 + f.ExecuteCall.Receives.IntermediateBuildCachePath = param4 + f.ExecuteCall.Receives.ProjectPath = param5 + f.ExecuteCall.Receives.OutputPath = param6 + f.ExecuteCall.Receives.Flags = param7 if f.ExecuteCall.Stub != nil { - return f.ExecuteCall.Stub(param1, param2, param3, param4, param5, param6) + return f.ExecuteCall.Stub(param1, param2, param3, param4, param5, param6, param7) } return f.ExecuteCall.Returns.Error } diff --git a/integration/default_apps_test.go b/integration/default_apps_test.go index 1f50fefa..a1274802 100644 --- a/integration/default_apps_test.go +++ b/integration/default_apps_test.go @@ -2,8 +2,6 @@ package integration_test import ( "fmt" - "io" - "net/http" "os" "path/filepath" "testing" @@ -30,10 +28,12 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { context("when building a .NET Core app", func() { var ( - image occam.Image - container occam.Container - name string - source string + image occam.Image + images map[string]string + container occam.Container + containers map[string]string + name string + source string ) it.Before(func() { @@ -41,59 +41,60 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { name, err = occam.RandomName() Expect(err).NotTo(HaveOccurred()) + containers = make(map[string]string) + images = make(map[string]string) }) it.After(func() { - Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed()) - Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed()) + for id := range containers { + Expect(docker.Container.Remove.Execute(id)).To(Succeed()) + } + for id := range images { + Expect(docker.Image.Remove.Execute(id)).To(Succeed()) + } Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed()) Expect(os.RemoveAll(source)).To(Succeed()) }) context("given a source application with .NET Core 6", func() { - it("should build a working OCI image", func() { + it("should build (and rebuild) a working OCI image", func() { var err error source, err := occam.Source(filepath.Join("testdata", "source_6_app")) Expect(err).NotTo(HaveOccurred()) - var logs fmt.Stringer - image, logs, err = pack.WithNoColor().Build. - WithBuildpacks( - icuBuildpack, - dotnetCoreRuntimeBuildpack, - dotnetCoreAspNetBuildpack, - dotnetCoreSDKBuildpack, - buildpack, - dotnetExecuteBuildpack, - ). - WithEnv(map[string]string{ - "BP_DOTNET_PUBLISH_FLAGS": "--verbosity=normal", - }). - Execute(name, source) - Expect(err).NotTo(HaveOccurred(), logs.String()) - - Expect(logs).To(ContainLines( - MatchRegexp(` Running 'dotnet publish .* --verbosity=normal'`), - )) - - container, err = docker.Container.Run. - WithEnv(map[string]string{"PORT": "8080"}). - WithPublish("8080"). - WithPublishAll(). - Execute(image.ID) - Expect(err).NotTo(HaveOccurred()) - - Eventually(container).Should(BeAvailable()) - - response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort("8080"))) - Expect(err).NotTo(HaveOccurred()) - defer response.Body.Close() - - Expect(response.StatusCode).To(Equal(http.StatusOK)) - - content, err := io.ReadAll(response.Body) - Expect(err).NotTo(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("source_6_app")) + for i := 0; i < 2; i++ { + var logs fmt.Stringer + image, logs, err = pack.WithNoColor().Build. + WithBuildpacks( + icuBuildpack, + dotnetCoreRuntimeBuildpack, + dotnetCoreAspNetBuildpack, + dotnetCoreSDKBuildpack, + buildpack, + dotnetExecuteBuildpack, + ). + WithEnv(map[string]string{ + "BP_DOTNET_PUBLISH_FLAGS": "--verbosity=normal", + }). + Execute(name, source) + Expect(err).NotTo(HaveOccurred(), logs.String()) + images[image.ID] = "" + + Expect(logs).To(ContainLines( + MatchRegexp(` Running 'dotnet publish .* --verbosity=normal'`), + )) + + container, err = docker.Container.Run. + WithEnv(map[string]string{"PORT": "8080"}). + WithPublish("8080"). + WithPublishAll(). + Execute(image.ID) + Expect(err).NotTo(HaveOccurred()) + containers[container.ID] = "" + + Eventually(container).Should(BeAvailable()) + Eventually(container).Should(Serve(ContainSubstring("source_6_app")).OnPort(8080)) + } }) }) @@ -118,6 +119,7 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { }). Execute(name, source) Expect(err).NotTo(HaveOccurred(), logs.String()) + images[image.ID] = "" Expect(logs).To(ContainLines( MatchRegexp(` Running 'dotnet publish .* --verbosity=normal'`), @@ -129,18 +131,10 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { WithPublishAll(). Execute(image.ID) Expect(err).NotTo(HaveOccurred()) + containers[container.ID] = "" Eventually(container).Should(BeAvailable()) - - response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort("8080"))) - Expect(err).NotTo(HaveOccurred()) - defer response.Body.Close() - - Expect(response.StatusCode).To(Equal(http.StatusOK)) - - content, err := io.ReadAll(response.Body) - Expect(err).NotTo(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("source_5_app")) + Eventually(container).Should(Serve(ContainSubstring("source_5_app")).OnPort(8080)) }) }) @@ -162,6 +156,7 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { ). Execute(name, source) Expect(err).NotTo(HaveOccurred(), logs.String()) + images[image.ID] = "" container, err = docker.Container.Run. WithEnv(map[string]string{"PORT": "8080"}). @@ -169,18 +164,10 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { WithPublishAll(). Execute(image.ID) Expect(err).NotTo(HaveOccurred()) + containers[container.ID] = "" Eventually(container).Should(BeAvailable()) - - response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort("8080"))) - Expect(err).NotTo(HaveOccurred()) - defer response.Body.Close() - - Expect(response.StatusCode).To(Equal(http.StatusOK)) - - content, err := io.ReadAll(response.Body) - Expect(err).NotTo(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("simple_3_0_app")) + Eventually(container).Should(Serve(ContainSubstring("simple_3_0_app")).OnPort(8080)) }) }) @@ -202,6 +189,7 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { ). Execute(name, source) Expect(err).NotTo(HaveOccurred(), logs.String()) + images[image.ID] = "" container, err = docker.Container.Run. WithEnv(map[string]string{"PORT": "8080"}). @@ -209,18 +197,10 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { WithPublishAll(). Execute(image.ID) Expect(err).NotTo(HaveOccurred()) + containers[container.ID] = "" Eventually(container).Should(BeAvailable()) - - response, err := http.Get(fmt.Sprintf("http://localhost:%s/api/values/6", container.HostPort("8080"))) - Expect(err).NotTo(HaveOccurred()) - defer response.Body.Close() - - Expect(response.StatusCode).To(Equal(http.StatusOK)) - - content, err := io.ReadAll(response.Body) - Expect(err).NotTo(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("value")) + Eventually(container).Should(Serve(ContainSubstring("value")).WithEndpoint("/api/values/6").OnPort(8080)) }) }) @@ -242,6 +222,7 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { ). Execute(name, source) Expect(err).NotTo(HaveOccurred(), logs.String()) + images[image.ID] = "" container, err = docker.Container.Run. WithEnv(map[string]string{"PORT": "8080"}). @@ -249,23 +230,9 @@ func testDefaultApps(t *testing.T, context spec.G, it spec.S) { WithPublishAll(). Execute(image.ID) Expect(err).NotTo(HaveOccurred()) + containers[container.ID] = "" - Eventually(func() int { - response, _ := http.Get(fmt.Sprintf("http://localhost:%s/swagger/index.html", container.HostPort("8080"))) - if response != nil { - defer response.Body.Close() - return response.StatusCode - } - return -1 - }).Should(Equal(http.StatusOK)) - - response, err := http.Get(fmt.Sprintf("http://localhost:%s/swagger/index.html", container.HostPort("8080"))) - Expect(err).NotTo(HaveOccurred()) - defer response.Body.Close() - - content, err := io.ReadAll(response.Body) - Expect(err).NotTo(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("