Skip to content

Commit

Permalink
Fix cyclic symlink on rebuild when filesystem caching is enabled (pak…
Browse files Browse the repository at this point in the history
…eto-buildpacks#408)

* Checks for existing node_modules symlink

* Fix unit tests; use Lstat instead of Readlink

* Fix integration tests; create symlink when only launch_modules required
  • Loading branch information
thitch97 authored Oct 19, 2022
1 parent bf3edd2 commit f3e145c
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 11 deletions.
17 changes: 11 additions & 6 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func Build(pathParser PathParser,
return packit.BuildResult{}, err
}

currentModLayer, err = installProcess.SetupModules(context.WorkingDir, currentModLayer, layer.Path)
currentModLayer, err = installProcess.SetupModules(projectPath, currentModLayer, layer.Path)
if err != nil {
return packit.BuildResult{}, err
}
Expand Down Expand Up @@ -206,7 +206,7 @@ func Build(pathParser PathParser,
return packit.BuildResult{}, err
}

_, err = installProcess.SetupModules(context.WorkingDir, currentModLayer, layer.Path)
_, err = installProcess.SetupModules(projectPath, currentModLayer, layer.Path)
if err != nil {
return packit.BuildResult{}, err
}
Expand All @@ -221,6 +221,13 @@ func Build(pathParser PathParser,
logger.Action("Completed in %s", duration.Round(time.Millisecond))
logger.Break()

if !build {
err = ensureNodeModulesSymlink(projectPath, layer.Path, tmpDir)
if err != nil {
return packit.BuildResult{}, err
}
}

layer.Metadata = map[string]interface{}{
"cache_sha": sha,
}
Expand Down Expand Up @@ -256,10 +263,7 @@ func Build(pathParser PathParser,
}

layer.ExecD = []string{filepath.Join(context.CNBPath, "bin", "setup-symlinks")}
err = ensureNodeModulesSymlink(projectPath, layer.Path, tmpDir)
if err != nil {
return packit.BuildResult{}, err
}

} else {
logger.Process("Reusing cached layer %s", layer.Path)
if !build {
Expand All @@ -273,6 +277,7 @@ func Build(pathParser PathParser,
layer.Launch = true

layers = append(layers, layer)

}

err = symlinker.Unlink(filepath.Join(homeDir, ".npmrc"))
Expand Down
22 changes: 20 additions & 2 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {

Expect(installProcess.ShouldRunCall.Receives.WorkingDir).To(Equal(filepath.Join(workingDir, "some-project-dir")))

Expect(installProcess.SetupModulesCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(installProcess.SetupModulesCall.Receives.WorkingDir).To(Equal(filepath.Join(workingDir, "some-project-dir")))
Expect(installProcess.SetupModulesCall.Receives.CurrentModulesLayerPath).To(Equal(""))
Expect(installProcess.SetupModulesCall.Receives.NextModulesLayerPath).To(Equal(layer.Path))

Expand Down Expand Up @@ -265,6 +265,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
"PATH.delim": ":",
}))
Expect(layer.Launch).To(BeTrue())
Expect(layer.Build).To(BeFalse())
Expect(layer.Metadata).To(Equal(
map[string]interface{}{
"cache_sha": "some-awesome-shasum",
Expand Down Expand Up @@ -300,7 +301,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {

Expect(installProcess.ShouldRunCall.Receives.WorkingDir).To(Equal(filepath.Join(workingDir, "some-project-dir")))

Expect(installProcess.SetupModulesCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(installProcess.SetupModulesCall.Receives.WorkingDir).To(Equal(filepath.Join(workingDir, "some-project-dir")))
Expect(installProcess.SetupModulesCall.Receives.CurrentModulesLayerPath).To(Equal(""))
Expect(installProcess.SetupModulesCall.Receives.NextModulesLayerPath).To(Equal(layer.Path))

Expand All @@ -309,6 +310,14 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(installProcess.ExecuteCall.Receives.Launch).To(BeTrue())

Expect(sbomGenerator.GenerateCall.Receives.Dir).To(Equal(workingDir))

workspaceLink, err := os.Readlink(filepath.Join(workingDir, "some-project-dir", "node_modules"))
Expect(err).NotTo(HaveOccurred())
Expect(workspaceLink).To(Equal(filepath.Join(tmpDir, "node_modules")))

tmpLink, err := os.Readlink(filepath.Join(tmpDir, "node_modules"))
Expect(err).NotTo(HaveOccurred())
Expect(tmpLink).To(Equal(filepath.Join(layersDir, "launch-modules", "node_modules")))
})
})

Expand Down Expand Up @@ -356,6 +365,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
it.Before(func() {
entryResolver.MergeLayerTypesCall.Returns.Launch = true
entryResolver.MergeLayerTypesCall.Returns.Build = true
pathParser.GetCall.Returns.ProjectPath = workingDir

installProcess.SetupModulesCall.Stub = func(w string, c string, n string) (string, error) {
setupModulesCalls = append(setupModulesCalls, setupModulesParams{
Expand Down Expand Up @@ -406,6 +416,14 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(setupModulesCalls[1].WorkingDir).To(Equal(workingDir))
Expect(setupModulesCalls[1].CurrentModulesLayerPath).To(Equal(result.Layers[0].Path))
Expect(setupModulesCalls[1].NextModulesLayerPath).To(Equal(result.Layers[1].Path))

workspaceLink, err := os.Readlink(filepath.Join(workingDir, "node_modules"))
Expect(err).NotTo(HaveOccurred())
Expect(workspaceLink).To(Equal(filepath.Join(tmpDir, "node_modules")))

tmpLink, err := os.Readlink(filepath.Join(tmpDir, "node_modules"))
Expect(err).NotTo(HaveOccurred())
Expect(tmpLink).To(Equal(filepath.Join(layersDir, "build-modules", "node_modules")))
})
})

Expand Down
20 changes: 19 additions & 1 deletion install_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,26 @@ func (ip YarnInstallProcess) SetupModules(workingDir, currentModulesLayerPath, n
}

} else {
err := os.MkdirAll(filepath.Join(workingDir, "node_modules"), os.ModePerm)

file, err := os.Lstat(filepath.Join(workingDir, "node_modules"))
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("failed to stat node_modules directory: %w", err)
}

}

if file != nil && file.Mode()&os.ModeSymlink == os.ModeSymlink {
err = os.RemoveAll(filepath.Join(workingDir, "node_modules"))
if err != nil {
//not tested
return "", fmt.Errorf("failed to remove node_modules symlink: %w", err)
}
}

err = os.MkdirAll(filepath.Join(workingDir, "node_modules"), os.ModePerm)
if err != nil {
//not directly tested
return "", fmt.Errorf("failed to create node_modules directory: %w", err)
}

Expand Down
4 changes: 2 additions & 2 deletions install_process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func testInstallProcess(t *testing.T, context spec.G, it spec.S) {
})
})

context("node_modules directory cannot be created in working directory", func() {
context("Lstat() cannot be run on node_modules in working directory", func() {
it.Before(func() {
Expect(os.Chmod(workingDir, 0000)).To(Succeed())
})
Expand All @@ -267,7 +267,7 @@ func testInstallProcess(t *testing.T, context spec.G, it spec.S) {

it("returns an error", func() {
_, err := installProcess.SetupModules(workingDir, "", nextModulesLayerPath)
Expect(err).To(MatchError(ContainSubstring("failed to create node_modules directory:")))
Expect(err).To(MatchError(ContainSubstring("failed to stat node_modules directory:")))
Expect(err).To(MatchError(ContainSubstring("permission denied")))
})
})
Expand Down

0 comments on commit f3e145c

Please sign in to comment.