From f7449e17fad6aeba7441367a861c61ddb3169679 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Thu, 12 Dec 2019 18:12:12 +0100 Subject: [PATCH] improvement(core): allow relative symlinks within module root for builds This addresses a limitation where we silently ignored all symlinks. Docker supports relative symlinks within the build context, and we should support those as well. We now also log (on verbose level) when we filter out symlinks. Fixes #1421 --- garden-service/src/build-dir.ts | 20 ++++- .../src/plugins/kubernetes/container/build.ts | 17 +++- garden-service/src/util/fs.ts | 2 +- garden-service/src/vcs/git.ts | 88 +++++++++++++------ garden-service/src/vcs/vcs.ts | 2 + .../build-dir}/garden.yml | 0 .../build-dir}/module-a/garden.yml | 0 .../build-dir}/module-a/some-dir/some-file | 0 .../build-dir}/module-b/garden.yml | 0 .../build-dir}/module-c/garden.yml | 0 .../build-dir}/module-d/garden.yml | 0 .../build-dir}/module-e/e1.txt | 0 .../build-dir}/module-e/garden.yml | 0 .../build-dir}/module-e/some-dir/e2.txt | 0 .../build-dir}/module-f/garden.yml | 0 .../build-dir/symlink-absolute/garden.yml | 3 + .../build-dir/symlink-absolute/symlink.txt | 1 + .../symlink-outside-module/garden.yml | 3 + .../symlink-outside-module/symlink.txt | 1 + .../build-dir/symlink-within-module/foo.txt | 1 + .../symlink-within-module/garden.yml | 3 + .../symlink-within-module/nested/symlink.txt | 1 + .../symlink-within-module/symlink.txt | 1 + garden-service/test/unit/src/build-dir.ts | 35 +++++++- garden-service/test/unit/src/vcs/git.ts | 76 +++++++++++++--- garden-service/test/unit/src/vcs/vcs.ts | 2 + 26 files changed, 213 insertions(+), 43 deletions(-) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-a/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-a/some-dir/some-file (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-b/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-c/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-d/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-e/e1.txt (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-e/garden.yml (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-e/some-dir/e2.txt (100%) rename garden-service/test/data/{test-project-build-products => test-projects/build-dir}/module-f/garden.yml (100%) create mode 100644 garden-service/test/data/test-projects/build-dir/symlink-absolute/garden.yml create mode 120000 garden-service/test/data/test-projects/build-dir/symlink-absolute/symlink.txt create mode 100644 garden-service/test/data/test-projects/build-dir/symlink-outside-module/garden.yml create mode 120000 garden-service/test/data/test-projects/build-dir/symlink-outside-module/symlink.txt create mode 100644 garden-service/test/data/test-projects/build-dir/symlink-within-module/foo.txt create mode 100644 garden-service/test/data/test-projects/build-dir/symlink-within-module/garden.yml create mode 120000 garden-service/test/data/test-projects/build-dir/symlink-within-module/nested/symlink.txt create mode 120000 garden-service/test/data/test-projects/build-dir/symlink-within-module/symlink.txt diff --git a/garden-service/src/build-dir.ts b/garden-service/src/build-dir.ts index 2b254f4c9b..1c8820c535 100644 --- a/garden-service/src/build-dir.ts +++ b/garden-service/src/build-dir.ts @@ -161,7 +161,25 @@ export class BuildDir { sourcePath = stripWildcard(sourcePath) destinationPath = stripWildcard(destinationPath) - const syncOpts = ["-rptgo", "--ignore-missing-args", "--temp-dir", normalizeLocalRsyncPath(tmpDir)] + const syncOpts = [ + "--recursive", + // Preserve modification times + "--times", + // Preserve owner + group + "--owner", + "--group", + // Copy permissions + "--perms", + // Copy symlinks + "--links", + // Only allow links that point within the copied tree + "--safe-links", + // Ignore missing files in file list + "--ignore-missing-args", + // Set a temp directory outside of the target directory to avoid potential conflicts + "--temp-dir", + normalizeLocalRsyncPath(tmpDir), + ] let logMsg = `Syncing ${module.version.files.length} files from ` + diff --git a/garden-service/src/plugins/kubernetes/container/build.ts b/garden-service/src/plugins/kubernetes/container/build.ts index 9ab6263718..6f679ca09c 100644 --- a/garden-service/src/plugins/kubernetes/container/build.ts +++ b/garden-service/src/plugins/kubernetes/container/build.ts @@ -140,7 +140,22 @@ const remoteBuild: BuildHandler = async (params) => { // https://stackoverflow.com/questions/1636889/rsync-how-can-i-configure-it-to-create-target-directory-on-server let src = normalizeLocalRsyncPath(`${buildRoot}`) + `/./${module.name}/` const destination = `rsync://localhost:${syncFwd.localPort}/volume/${ctx.workingCopyId}/` - const syncArgs = ["-vrpztgo", "--relative", "--delete", "--temp-dir", "/tmp", src, destination] + const syncArgs = [ + "--recursive", + "--relative", + // Copy symlinks (Note: These are sanitized while syncing to the build staging dir) + "--links", + // Preserve permissions + "--perms", + // Preserve modification times + "--times", + "--compress", + "--delete", + "--temp-dir", + "/tmp", + src, + destination, + ] log.debug(`Syncing from ${src} to ${destination}`) diff --git a/garden-service/src/util/fs.ts b/garden-service/src/util/fs.ts index 921270511d..4fe55e4990 100644 --- a/garden-service/src/util/fs.ts +++ b/garden-service/src/util/fs.ts @@ -155,7 +155,7 @@ export async function findConfigPathsInPath({ log: LogEntry }) { // TODO: we could make this lighter/faster using streaming - const files = await vcs.getFiles({ path: dir, include, exclude: exclude || [], log }) + const files = await vcs.getFiles({ path: dir, pathDescription: "project root", include, exclude: exclude || [], log }) return files.map((f) => f.path).filter((f) => isConfigFilename(basename(f))) } diff --git a/garden-service/src/vcs/git.ts b/garden-service/src/vcs/git.ts index 2d1444162e..a2e0ccaf9d 100644 --- a/garden-service/src/vcs/git.ts +++ b/garden-service/src/vcs/git.ts @@ -6,9 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { join, resolve, relative } from "path" +import { join, resolve, relative, isAbsolute } from "path" import { flatten } from "lodash" -import { ensureDir, pathExists, stat, createReadStream } from "fs-extra" +import { ensureDir, pathExists, createReadStream, Stats, realpath, readlink, lstat } from "fs-extra" import { PassThrough } from "stream" import hasha from "hasha" import split2 = require("split2") @@ -104,7 +104,7 @@ export class GitHandler extends VcsHandler { * Returns a list of files, along with file hashes, under the given path, taking into account the configured * .ignore files, and the specified include/exclude filters. */ - async getFiles({ log, path, include, exclude }: GetFilesParams): Promise { + async getFiles({ log, path, pathDescription, include, exclude }: GetFilesParams): Promise { const git = this.gitCli(log, path) const gitRoot = await this.getRepoRoot(log, path) @@ -212,9 +212,9 @@ export class GitHandler extends VcsHandler { if (submodulePaths.includes(f.path)) { // This path is a submodule, so we recursively call getFiles for that path again. // Note: We apply include/exclude filters after listing files from submodule - return (await this.getFiles({ log, path: f.path, exclude: [] })).filter((submoduleFile) => - matchPath(relative(path, submoduleFile.path), include, exclude) - ) + return ( + await this.getFiles({ log, path: f.path, pathDescription: "submodule", exclude: [] }) + ).filter((submoduleFile) => matchPath(relative(path, submoduleFile.path), include, exclude)) } else { return [f] } @@ -224,26 +224,52 @@ export class GitHandler extends VcsHandler { // Make sure we have a fresh hash for each file return Bluebird.map(withSubmodules, async (f) => { const resolvedPath = resolve(path, f.path) - if (!f.hash || modified.has(resolvedPath)) { - // If we can't compute the hash, i.e. the file is gone, we filter it out below - let hash = "" - try { - // "git ls-files" returns a symlink even if it points to a directory. - // We filter symlinked directories out, since hashObject() will fail to - // process them. - if (!(await stat(resolvedPath)).isDirectory()) { - hash = (await this.hashObject(resolvedPath)) || "" - } - } catch (err) { - // 128 = File no longer exists - if (err.exitCode !== 128 && err.code !== "ENOENT") { - throw err + let output = { path: resolvedPath, hash: f.hash || "" } + let stats: Stats + + try { + stats = await lstat(resolvedPath) + } catch (err) { + // 128 = File no longer exists + if (err.exitCode === 128 || err.code === "ENOENT") { + // If the file is gone, we filter it out below + return { path: resolvedPath, hash: "" } + } else { + throw err + } + } + + // We need to special-case handling of symlinks. We disallow any "unsafe" symlinks, i.e. any ones that may + // link outside of `path` (which is usually a project or module root). The `hashObject` method also special-cases + // symlinks, to match git's hashing behavior (which is to hash the link itself, and not what the link points to). + if (stats.isSymbolicLink()) { + const target = await readlink(resolvedPath) + + // Make sure symlink is relative and points within `path` + if (isAbsolute(target)) { + log.verbose(`Ignoring symlink with absolute target at ${resolvedPath}`) + output.hash = "" + return output + } else if (target.startsWith("..")) { + const realTarget = await realpath(resolvedPath) + const relPath = relative(path, realTarget) + + if (relPath.startsWith("..")) { + log.verbose(`Ignoring symlink pointing outside of ${pathDescription} at ${resolvedPath}`) + output.hash = "" + return output } } - return { path: resolvedPath, hash } - } else { - return { path: resolvedPath, hash: f.hash } } + + if (output.hash === "" || modified.has(resolvedPath)) { + // Don't attempt to hash directories. Directories will by extension be filtered out of the list. + if (!stats.isDirectory()) { + output.hash = (await this.hashObject(stats, resolvedPath)) || "" + } + } + + return output }).filter((f) => f.hash !== "") } @@ -325,12 +351,20 @@ export class GitHandler extends VcsHandler { /** * Replicates the `git hash-object` behavior. See https://stackoverflow.com/a/5290484/3290965 */ - async hashObject(path: string) { - const info = await stat(path) + async hashObject(stats: Stats, path: string) { const stream = new PassThrough() const output = hasha.fromStream(stream, { algorithm: "sha1" }) - stream.push(`blob ${info.size}\0`) - createReadStream(path).pipe(stream) + stream.push(`blob ${stats.size}\0`) + + if (stats.isSymbolicLink()) { + // For symlinks, we follow git's behavior, which is to hash the link itself (i.e. the path it contains) as + // opposed to the file/directory that it points to. + stream.push(await readlink(path)) + stream.end() + } else { + createReadStream(path).pipe(stream) + } + return output } diff --git a/garden-service/src/vcs/vcs.ts b/garden-service/src/vcs/vcs.ts index bbfcc55ef1..af46c40e7d 100644 --- a/garden-service/src/vcs/vcs.ts +++ b/garden-service/src/vcs/vcs.ts @@ -70,6 +70,7 @@ export const moduleVersionSchema = joi.object().keys({ export interface GetFilesParams { log: LogEntry path: string + pathDescription?: string include?: string[] exclude?: string[] } @@ -101,6 +102,7 @@ export abstract class VcsHandler { let files = await this.getFiles({ log, path: moduleConfig.path, + pathDescription: "module root", include: moduleConfig.include, exclude: moduleConfig.exclude, }) diff --git a/garden-service/test/data/test-project-build-products/garden.yml b/garden-service/test/data/test-projects/build-dir/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/garden.yml rename to garden-service/test/data/test-projects/build-dir/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-a/garden.yml b/garden-service/test/data/test-projects/build-dir/module-a/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-a/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-a/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-a/some-dir/some-file b/garden-service/test/data/test-projects/build-dir/module-a/some-dir/some-file similarity index 100% rename from garden-service/test/data/test-project-build-products/module-a/some-dir/some-file rename to garden-service/test/data/test-projects/build-dir/module-a/some-dir/some-file diff --git a/garden-service/test/data/test-project-build-products/module-b/garden.yml b/garden-service/test/data/test-projects/build-dir/module-b/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-b/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-b/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-c/garden.yml b/garden-service/test/data/test-projects/build-dir/module-c/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-c/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-c/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-d/garden.yml b/garden-service/test/data/test-projects/build-dir/module-d/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-d/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-d/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-e/e1.txt b/garden-service/test/data/test-projects/build-dir/module-e/e1.txt similarity index 100% rename from garden-service/test/data/test-project-build-products/module-e/e1.txt rename to garden-service/test/data/test-projects/build-dir/module-e/e1.txt diff --git a/garden-service/test/data/test-project-build-products/module-e/garden.yml b/garden-service/test/data/test-projects/build-dir/module-e/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-e/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-e/garden.yml diff --git a/garden-service/test/data/test-project-build-products/module-e/some-dir/e2.txt b/garden-service/test/data/test-projects/build-dir/module-e/some-dir/e2.txt similarity index 100% rename from garden-service/test/data/test-project-build-products/module-e/some-dir/e2.txt rename to garden-service/test/data/test-projects/build-dir/module-e/some-dir/e2.txt diff --git a/garden-service/test/data/test-project-build-products/module-f/garden.yml b/garden-service/test/data/test-projects/build-dir/module-f/garden.yml similarity index 100% rename from garden-service/test/data/test-project-build-products/module-f/garden.yml rename to garden-service/test/data/test-projects/build-dir/module-f/garden.yml diff --git a/garden-service/test/data/test-projects/build-dir/symlink-absolute/garden.yml b/garden-service/test/data/test-projects/build-dir/symlink-absolute/garden.yml new file mode 100644 index 0000000000..9fe7bc89a2 --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-absolute/garden.yml @@ -0,0 +1,3 @@ +kind: Module +type: exec +name: symlink-absolute diff --git a/garden-service/test/data/test-projects/build-dir/symlink-absolute/symlink.txt b/garden-service/test/data/test-projects/build-dir/symlink-absolute/symlink.txt new file mode 120000 index 0000000000..cad2309100 --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-absolute/symlink.txt @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/garden-service/test/data/test-projects/build-dir/symlink-outside-module/garden.yml b/garden-service/test/data/test-projects/build-dir/symlink-outside-module/garden.yml new file mode 100644 index 0000000000..7852057a7c --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-outside-module/garden.yml @@ -0,0 +1,3 @@ +kind: Module +type: exec +name: symlink-outside-module diff --git a/garden-service/test/data/test-projects/build-dir/symlink-outside-module/symlink.txt b/garden-service/test/data/test-projects/build-dir/symlink-outside-module/symlink.txt new file mode 120000 index 0000000000..fb4d2f9880 --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-outside-module/symlink.txt @@ -0,0 +1 @@ +../garden.yml \ No newline at end of file diff --git a/garden-service/test/data/test-projects/build-dir/symlink-within-module/foo.txt b/garden-service/test/data/test-projects/build-dir/symlink-within-module/foo.txt new file mode 100644 index 0000000000..deba01fc8d --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-within-module/foo.txt @@ -0,0 +1 @@ +something diff --git a/garden-service/test/data/test-projects/build-dir/symlink-within-module/garden.yml b/garden-service/test/data/test-projects/build-dir/symlink-within-module/garden.yml new file mode 100644 index 0000000000..600db70783 --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-within-module/garden.yml @@ -0,0 +1,3 @@ +kind: Module +type: exec +name: symlink-within-module diff --git a/garden-service/test/data/test-projects/build-dir/symlink-within-module/nested/symlink.txt b/garden-service/test/data/test-projects/build-dir/symlink-within-module/nested/symlink.txt new file mode 120000 index 0000000000..416e3bd9e6 --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-within-module/nested/symlink.txt @@ -0,0 +1 @@ +../foo.txt \ No newline at end of file diff --git a/garden-service/test/data/test-projects/build-dir/symlink-within-module/symlink.txt b/garden-service/test/data/test-projects/build-dir/symlink-within-module/symlink.txt new file mode 120000 index 0000000000..996f1789ff --- /dev/null +++ b/garden-service/test/data/test-projects/build-dir/symlink-within-module/symlink.txt @@ -0,0 +1 @@ +foo.txt \ No newline at end of file diff --git a/garden-service/test/unit/src/build-dir.ts b/garden-service/test/unit/src/build-dir.ts index 563de1afcb..180d7bc3e2 100644 --- a/garden-service/test/unit/src/build-dir.ts +++ b/garden-service/test/unit/src/build-dir.ts @@ -8,7 +8,7 @@ import { getConfigFilePath } from "../../../src/util/fs" import { Garden } from "../../../src/garden" /* - Module dependency diagram for test-project-build-products + Module dependency diagram for build-dir test project a b \ / @@ -17,7 +17,7 @@ import { Garden } from "../../../src/garden" f */ -const projectRoot = join(dataDir, "test-project-build-products") +const projectRoot = join(dataDir, "test-projects", "build-dir") const makeGarden = async () => { return await makeTestGarden(projectRoot) @@ -102,6 +102,37 @@ describe("BuildDir", () => { expect(await pathExists(deleteMe)).to.be.false }) + + it("should sync symlinks that point within the module root", async () => { + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("symlink-within-module") + + await garden.buildDir.syncFromSrc(module, garden.log) + + const buildDir = await garden.buildDir.buildPath(module) + expect(await pathExists(join(buildDir, "symlink.txt"))).to.be.true + expect(await pathExists(join(buildDir, "nested", "symlink.txt"))).to.be.true + }) + + it("should not sync symlinks that point outside the module root", async () => { + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("symlink-outside-module") + + await garden.buildDir.syncFromSrc(module, garden.log) + + const buildDir = await garden.buildDir.buildPath(module) + expect(await pathExists(join(buildDir, "symlink.txt"))).to.be.false + }) + + it("should not sync absolute symlinks", async () => { + const graph = await garden.getConfigGraph(garden.log) + const module = await graph.getModule("symlink-absolute") + + await garden.buildDir.syncFromSrc(module, garden.log) + + const buildDir = await garden.buildDir.buildPath(module) + expect(await pathExists(join(buildDir, "symlink.txt"))).to.be.false + }) }) it("should sync dependency products to their specified destinations", async () => { diff --git a/garden-service/test/unit/src/vcs/git.ts b/garden-service/test/unit/src/vcs/git.ts index ab9f2afb42..8f1440af8d 100644 --- a/garden-service/test/unit/src/vcs/git.ts +++ b/garden-service/test/unit/src/vcs/git.ts @@ -10,7 +10,7 @@ import execa = require("execa") import { expect } from "chai" import tmp from "tmp-promise" import uuid from "uuid" -import { createFile, writeFile, realpath, mkdir, remove, symlink } from "fs-extra" +import { createFile, writeFile, realpath, mkdir, remove, symlink, ensureSymlink, lstat } from "fs-extra" import { join, resolve, basename, relative } from "path" import { expectError, makeTestGardenA } from "../../../helpers" @@ -326,22 +326,44 @@ describe("GitHandler", () => { expect(files).to.eql([]) }) - it("should exclude an untracked symlink to a directory", async () => { - const tmpDir2 = await tmp.dir({ unsafeCleanup: true }) - const tmpPathB = await realpath(tmpDir2.path) + it("should include a relative symlink within the path", async () => { + const fileName = "foo" + const filePath = resolve(tmpPath, fileName) + const symlinkPath = resolve(tmpPath, "symlink") - const name = "a-symlink-to-a-directory" - const path = resolve(tmpPath, name) + await createFile(filePath) + await symlink(fileName, symlinkPath) - await symlink(tmpPathB, path) + const files = (await handler.getFiles({ path: tmpPath, exclude: [], log })).map((f) => f.path) + expect(files).to.eql([filePath, symlinkPath]) + }) - const files = (await handler.getFiles({ path: tmpPath, exclude: [], log })).filter( - (f) => !f.path.includes(defaultIgnoreFilename) - ) + it("should exclude a relative symlink that points outside the path", async () => { + const subPath = resolve(tmpPath, "subdir") + + const fileName = "foo" + const filePath = resolve(tmpPath, fileName) + const symlinkPath = resolve(subPath, "symlink") + await createFile(filePath) + await ensureSymlink(join("..", fileName), symlinkPath) + + const files = (await handler.getFiles({ path: subPath, exclude: [], log })).map((f) => f.path) expect(files).to.eql([]) }) + it("should exclude an absolute symlink that points inside the path", async () => { + const fileName = "foo" + const filePath = resolve(tmpPath, fileName) + const symlinkPath = resolve(tmpPath, "symlink") + + await createFile(filePath) + await symlink(filePath, symlinkPath) + + const files = (await handler.getFiles({ path: tmpPath, exclude: [], log })).map((f) => f.path) + expect(files).to.eql([filePath]) + }) + context("path contains a submodule", () => { let submodule: tmp.DirectoryResult let submodulePath: string @@ -451,10 +473,42 @@ describe("GitHandler", () => { it("should return the same result as `git hash-object` for a file", async () => { const path = resolve(tmpPath, "foo.txt") await createFile(path) + await writeFile(path, "iogjeiojgeowigjewoijoeiw") + const stats = await lstat(path) const expected = (await git("hash-object", path))[0] - expect(await handler.hashObject(path)).to.equal(expected) + expect(await handler.hashObject(stats, path)).to.equal(expected) + }) + + it("should return the same result as `git ls-files` for a file", async () => { + const path = resolve(tmpPath, "foo.txt") + await createFile(path) + await writeFile(path, "iogjeiojgeowigjewoijoeiw") + const stats = await lstat(path) + await git("add", path) + + const files = (await git("ls-files", "-s", path))[0] + const expected = files.split(" ")[1] + + expect(await handler.hashObject(stats, path)).to.equal(expected) + }) + + it("should return the same result as `git ls-files` for a symlink", async () => { + const filePath = resolve(tmpPath, "foo") + const symlinkPath = resolve(tmpPath, "bar") + await createFile(filePath) + await writeFile(filePath, "kfgjdslgjaslj") + + await symlink("foo", symlinkPath) + await git("add", symlinkPath) + + const stats = await lstat(symlinkPath) + + const files = (await git("ls-files", "-s", symlinkPath))[0] + const expected = files.split(" ")[1] + + expect(await handler.hashObject(stats, symlinkPath)).to.equal(expected) }) }) diff --git a/garden-service/test/unit/src/vcs/vcs.ts b/garden-service/test/unit/src/vcs/vcs.ts index bf594a0b70..a9afc11923 100644 --- a/garden-service/test/unit/src/vcs/vcs.ts +++ b/garden-service/test/unit/src/vcs/vcs.ts @@ -74,6 +74,7 @@ describe("VcsHandler", () => { path: moduleConfig.path, include: undefined, exclude: undefined, + pathDescription: "module root", }) ).thenResolve([ { path: "c", hash: "c" }, @@ -93,6 +94,7 @@ describe("VcsHandler", () => { path: moduleConfig.path, include: undefined, exclude: undefined, + pathDescription: "module root", }) ).thenResolve([ { path: moduleConfig.configPath, hash: "c" },