fix: locate Terraform entrypoint file (#16753)

Fixes: https://github.com/coder/coder/issues/16360
This commit is contained in:
Marcin Tojek
2025-02-28 15:22:36 +01:00
committed by GitHub
parent e27953d2bc
commit 930816fd0e
4 changed files with 158 additions and 6 deletions
@@ -22,9 +22,12 @@ import {
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import { server } from "testHelpers/server";
import type { FileTree } from "utils/filetree";
import type { MonacoEditorProps } from "./MonacoEditor";
import { Language } from "./PublishTemplateVersionDialog";
import TemplateVersionEditorPage from "./TemplateVersionEditorPage";
import TemplateVersionEditorPage, {
findEntrypointFile,
} from "./TemplateVersionEditorPage";
const { API } = apiModule;
@@ -409,3 +412,127 @@ function renderEditorPage(queryClient: QueryClient) {
</AppProviders>,
);
}
describe("Find entrypoint", () => {
it("empty tree", () => {
const ft: FileTree = {};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBeUndefined();
});
it("flat structure, main.tf in root", () => {
const ft: FileTree = {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
"nnn.tf": "foobaz",
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("main.tf");
});
it("flat structure, no main.tf", () => {
const ft: FileTree = {
"aaa.tf": "hello",
"bbb.tf": "world",
"ccc.tf": "foobaz",
"nnn.tf": "foobaz",
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("nnn.tf");
});
it("with dirs, single main.tf", () => {
const ft: FileTree = {
"aaa-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
},
"bbb-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
},
"main.tf": "foobar",
"nnn.tf": "foobaz",
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("main.tf");
});
it("with dirs, multiple main.tf's", () => {
const ft: FileTree = {
"aaa-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"bbb-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"ccc-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
},
"main.tf": "foobar",
"nnn.tf": "foobaz",
"zzz-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("main.tf");
});
it("with dirs, multiple main.tf, no main.tf in root", () => {
const ft: FileTree = {
"aaa-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"bbb-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"ccc-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
},
"nnn.tf": "foobaz",
"zzz-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("aaa-dir/main.tf");
});
it("with dirs, multiple main.tf, unordered file tree", () => {
const ft: FileTree = {
"ccc-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"aaa-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
"zzz-dir": {
"aaa.tf": "hello",
"bbb.tf": "world",
"main.tf": "foobar",
},
};
const mainFile = findEntrypointFile(ft);
expect(mainFile).toBe("aaa-dir/main.tf");
});
});
@@ -90,7 +90,7 @@ export const TemplateVersionEditorPage: FC = () => {
// File navigation
// It can be undefined when a selected file is deleted
const activePath: string | undefined =
searchParams.get("path") ?? findInitialFile(fileTree ?? {});
searchParams.get("path") ?? findEntrypointFile(fileTree ?? {});
const onActivePathChange = (path: string | undefined) => {
if (path) {
searchParams.set("path", path);
@@ -357,10 +357,33 @@ const publishVersion = async (options: {
return Promise.all(publishActions);
};
const findInitialFile = (fileTree: FileTree): string | undefined => {
const defaultMainTerraformFile = "main.tf";
// findEntrypointFile function locates the entrypoint file to open in the Editor.
// It browses the filetree following these steps:
// 1. If "main.tf" exists in root, return it.
// 2. Traverse through sub-directories.
// 3. If "main.tf" exists in a sub-directory, skip further browsing, and return the path.
// 4. If "main.tf" was not found, return the last reviewed "".tf" file.
export const findEntrypointFile = (fileTree: FileTree): string | undefined => {
let initialFile: string | undefined;
traverse(fileTree, (content, filename, path) => {
if (Object.keys(fileTree).find((key) => key === defaultMainTerraformFile)) {
return defaultMainTerraformFile;
}
let skip = false;
traverse(fileTree, (_, filename, path) => {
if (skip) {
return;
}
if (filename === defaultMainTerraformFile) {
initialFile = path;
skip = true;
return;
}
if (filename.endsWith(".tf")) {
initialFile = path;
}
+1 -1
View File
@@ -122,6 +122,6 @@ test("traverse() go trough all the file tree files", () => {
traverse(fileTree, (_content, _filename, fullPath) => {
filePaths.push(fullPath);
});
const expectedFilePaths = ["main.tf", "images", "images/java.Dockerfile"];
const expectedFilePaths = ["images", "images/java.Dockerfile", "main.tf"];
expect(filePaths).toEqual(expectedFilePaths);
});
+3 -1
View File
@@ -96,7 +96,9 @@ export const traverse = (
) => void,
parent?: string,
) => {
for (const [filename, content] of Object.entries(fileTree)) {
for (const [filename, content] of Object.entries(fileTree).sort(([a], [b]) =>
a.localeCompare(b),
)) {
const fullPath = parent ? `${parent}/${filename}` : filename;
callback(content, filename, fullPath);
if (typeof content === "object") {