mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: locate Terraform entrypoint file (#16753)
Fixes: https://github.com/coder/coder/issues/16360
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user