kubepile 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/package.json +2 -2
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +10 -16
- package/dist/src/kubepile.js +50 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,13 @@ This reads `~/.config/kubepile/*.yaml`, then writes `~/.kube/config`. If
|
|
|
46
46
|
`~/.kube/config` already exists, `kubepile compile` prompts before copying it to
|
|
47
47
|
`~/.kube/config.bak`.
|
|
48
48
|
|
|
49
|
+
Kubepile skips that backup prompt for safe rebuilds. A rebuild is safe when the
|
|
50
|
+
existing generated kubeconfig can be reproduced from a subset of the current
|
|
51
|
+
`*.yaml` files. In practice, this means no-op rebuilds and adding a new
|
|
52
|
+
provider file do not ask for a backup. If the existing generated kubeconfig was
|
|
53
|
+
manually edited, was not generated by kubepile, or can no longer be reproduced
|
|
54
|
+
from the current inputs, kubepile asks before replacing it.
|
|
55
|
+
|
|
49
56
|
Explicit command and options:
|
|
50
57
|
|
|
51
58
|
```sh
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubepile",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Compile and split kubeconfig files from ~/.config/kubepile.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/kubepile.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
|
-
"build": "npm run clean && tsc",
|
|
25
|
+
"build": "npm run clean && tsc && chmod +x dist/src/cli.js",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "vitest run --dir test",
|
|
28
28
|
"prepublishOnly": "npm run typecheck && npm run build && npm test"
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export {};
|
package/dist/src/cli.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
4
|
import { Command } from "@commander-js/extra-typings";
|
|
6
5
|
import packageJson from "../package.json" with { type: "json" };
|
|
7
6
|
import { compileToKubeConfig, defaultKubeConfigPath, defaultKubepileDir, splitKubeConfigFile, } from "./kubepile.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
7
|
+
const program = createProgram();
|
|
8
|
+
if (process.argv.slice(2).length === 0) {
|
|
9
|
+
program.outputHelp();
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
program.parseAsync(process.argv.slice(2), { from: "user" }).catch((error) => {
|
|
13
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14
|
+
process.stderr.write(`kubepile: ${message}\n`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
});
|
|
15
17
|
}
|
|
16
18
|
async function askBackup(existingPath, backupPath) {
|
|
17
19
|
const rl = createInterface({
|
|
@@ -73,11 +75,3 @@ Defaults:
|
|
|
73
75
|
});
|
|
74
76
|
return program;
|
|
75
77
|
}
|
|
76
|
-
const entryPoint = process.argv[1] ? pathToFileURL(process.argv[1]).href : undefined;
|
|
77
|
-
if (import.meta.url === entryPoint) {
|
|
78
|
-
runCli(process.argv.slice(2)).catch((error) => {
|
|
79
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
-
process.stderr.write(`kubepile: ${message}\n`);
|
|
81
|
-
process.exitCode = 1;
|
|
82
|
-
});
|
|
83
|
-
}
|
package/dist/src/kubepile.js
CHANGED
|
@@ -11,6 +11,9 @@ export function defaultKubeConfigPath() {
|
|
|
11
11
|
export async function buildMergedConfig(options = {}) {
|
|
12
12
|
const inputDir = options.inputDir ?? defaultKubepileDir();
|
|
13
13
|
const inputFiles = await listKubeConfigFiles(inputDir);
|
|
14
|
+
return buildMergedConfigFromFiles(inputFiles);
|
|
15
|
+
}
|
|
16
|
+
async function buildMergedConfigFromFiles(inputFiles) {
|
|
14
17
|
const configs = await Promise.all(inputFiles.map(async (filePath) => ({
|
|
15
18
|
filePath,
|
|
16
19
|
config: await readKubeConfigFile(filePath),
|
|
@@ -41,7 +44,7 @@ export async function compileToKubeConfig(options = {}) {
|
|
|
41
44
|
const { config, inputFiles } = await buildMergedConfig({ ...options, inputDir });
|
|
42
45
|
let backedUpTo;
|
|
43
46
|
if (await pathExists(outputPath)) {
|
|
44
|
-
const shouldBackup = await
|
|
47
|
+
const shouldBackup = await shouldBackUpExistingKubeConfig(outputPath, inputDir, inputFiles, options);
|
|
45
48
|
if (shouldBackup) {
|
|
46
49
|
await mkdir(path.dirname(backupPath), { recursive: true });
|
|
47
50
|
await copyFile(outputPath, backupPath);
|
|
@@ -52,6 +55,52 @@ export async function compileToKubeConfig(options = {}) {
|
|
|
52
55
|
await writeFile(outputPath, serializeGeneratedKubeConfig(config, inputDir), "utf8");
|
|
53
56
|
return { config, inputFiles, outputPath, backedUpTo };
|
|
54
57
|
}
|
|
58
|
+
async function shouldBackUpExistingKubeConfig(outputPath, inputDir, currentInputFiles, options) {
|
|
59
|
+
if (await canSafelyRegenerate(outputPath, inputDir, currentInputFiles)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return Boolean(await options.shouldBackup?.(outputPath, `${outputPath}.bak`));
|
|
63
|
+
}
|
|
64
|
+
async function canSafelyRegenerate(outputPath, inputDir, currentInputFiles) {
|
|
65
|
+
const existingSource = await readFile(outputPath, "utf8");
|
|
66
|
+
if (!existingSource.startsWith(generatedKubeConfigHeader(inputDir))) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const existingConfig = parseKubeConfig(existingSource, outputPath);
|
|
71
|
+
const previousInputFiles = await findInputFilesRepresentedInConfig(currentInputFiles, existingConfig);
|
|
72
|
+
if (previousInputFiles.length === 0) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const { config } = await buildMergedConfigFromFiles(previousInputFiles);
|
|
76
|
+
return existingSource === serializeGeneratedKubeConfig(config, inputDir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function findInputFilesRepresentedInConfig(inputFiles, config) {
|
|
83
|
+
const representedInputFiles = [];
|
|
84
|
+
for (const inputFile of inputFiles) {
|
|
85
|
+
const inputConfig = await readKubeConfigFile(inputFile);
|
|
86
|
+
if (configContainsAllNamedEntries(config, inputConfig, inputFile)) {
|
|
87
|
+
representedInputFiles.push(inputFile);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return representedInputFiles;
|
|
91
|
+
}
|
|
92
|
+
function configContainsAllNamedEntries(haystack, needle, sourceLabel) {
|
|
93
|
+
return hasAllNamedEntries(getNamedClusters(haystack, sourceLabel), getNamedClusters(needle, sourceLabel))
|
|
94
|
+
&& hasAllNamedEntries(getNamedUsers(haystack, sourceLabel), getNamedUsers(needle, sourceLabel))
|
|
95
|
+
&& hasAllNamedEntries(getNamedContexts(haystack, sourceLabel), getNamedContexts(needle, sourceLabel));
|
|
96
|
+
}
|
|
97
|
+
function hasAllNamedEntries(haystack, needles) {
|
|
98
|
+
const entriesByName = new Map(haystack.map((entry) => [entry.name, entry]));
|
|
99
|
+
return needles.every((needle) => deepEqual(entriesByName.get(needle.name), needle));
|
|
100
|
+
}
|
|
101
|
+
function deepEqual(left, right) {
|
|
102
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
103
|
+
}
|
|
55
104
|
async function pathExists(filePath) {
|
|
56
105
|
try {
|
|
57
106
|
await stat(filePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubepile",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Compile and split kubeconfig files from ~/.config/kubepile.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/kubepile.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
|
-
"build": "npm run clean && tsc",
|
|
25
|
+
"build": "npm run clean && tsc && chmod +x dist/src/cli.js",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "vitest run --dir test",
|
|
28
28
|
"prepublishOnly": "npm run typecheck && npm run build && npm test"
|