kubepile 0.0.4 → 0.0.6

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 CHANGED
@@ -13,6 +13,31 @@ Kubepile lets you maintain individual, per-provider kubeconfigs in a
13
13
  `~/.config/kubepile` directory, and compile them into a single, merged
14
14
  kubeconfig.
15
15
 
16
+ Kubepile exposes a tiny set of commands to manage your configs:
17
+
18
+ * [`compile`](#compile): compile your Kubepile configs into a merged kubeconfig
19
+ * [`source`](#source): set a specific context as your default for the current
20
+ shell
21
+ * [`split`](#split): decompile an existing, messy kubeconfig into nice, clean
22
+ Kubepile configs
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install -g kubepile
28
+ ```
29
+
30
+ Kubepile also includes a small shell helper that you need to install once:
31
+
32
+ ```sh
33
+ kubepile install
34
+ ```
35
+
36
+ Once you've installed the shell helper, either start a new shell or re-source
37
+ your `.zshrc`/`.bashrc`/`.profile`/etc. Kubepile supports Zsh, Bash, and Fish.
38
+
39
+ ## Kubepile configs
40
+
16
41
  Each `*.yaml` file is a normal kubeconfig. You can paste in kubeconfigs from
17
42
  providers without converting them to a kubepile-specific schema. During
18
43
  `compile`, kubepile reads every file and merges its `clusters`, `users`, and
@@ -28,13 +53,9 @@ intentionally fail compilation with a helpful message explaining which file
28
53
  broke the kubepile rules.
29
54
 
30
55
  Kubepile will never set a `current-context`, out of the design belief that
31
- `current-context` is a dangerous footgun in multi-cluster setups.
32
-
33
- ## Install
34
-
35
- ```sh
36
- npm install -g kubepile
37
- ```
56
+ setting a global, cross-shell-session `current-context` is a dangerous footgun
57
+ in multi-cluster setups. Instead, use `kubepile source <context>` to
58
+ temporarily set a default context for your current shell session.
38
59
 
39
60
  ## Compile
40
61
 
@@ -65,28 +86,41 @@ Running `kubepile` with no command prints help.
65
86
 
66
87
  ## Source
67
88
 
68
- `kubepile source <context>` switches your current shell to one context by
69
- creating a temporary kubeconfig containing only that context and exporting
70
- `KUBECONFIG` to point at it. It also prefixes your shell prompt with the context
71
- name.
89
+ `kubepile source <context>` switches your current shell to use a specific
90
+ Kubernetes context by default by creating a temporary kubeconfig with that
91
+ context set as the current-context, and exporting `KUBECONFIG` to point at it.
92
+ It also prefixes your shell prompt with the context name.
93
+
94
+ ```sh
95
+ kubepile source prod
96
+ # Your shell prompt is now:
97
+ # (prod) WHATEVER_YOUR_OLD_PROMPT_WAS
98
+ # All kubectl commands will use the prod context
99
+ ```
100
+
101
+ To list available contexts:
102
+
103
+ ```sh
104
+ kubepile source --list
105
+ ```
72
106
 
73
- Shells do not let child processes modify the parent shell environment, so this
74
- requires installing a small shell function first:
107
+ To switch to a different context, just run the `source` command with a new
108
+ context:
75
109
 
76
110
  ```sh
77
- kubepile install
111
+ kubepile source dev
78
112
  ```
79
113
 
80
- Then start a new shell and run:
114
+ Note that this requires installing the shell helpers listed in the
115
+ installation instructions. If you haven't installed them yet, install them
116
+ with:
81
117
 
82
118
  ```sh
83
- kubepile source prod
119
+ kubepile install
84
120
  ```
85
121
 
86
- `kubepile install` installs only for your current shell. It supports bash, zsh,
87
- and fish. The installed function proxies every normal command to the real
88
- `kubepile` binary with `command kubepile`, so it follows whatever `kubepile` is
89
- currently on your `PATH` after tools like `nvm` update it.
122
+ And re-source your main shell config (such as e.g. a `.zshrc`) or start a new
123
+ shell.
90
124
 
91
125
  ## Split
92
126
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubepile",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Compile and split kubeconfig files from ~/.config/kubepile.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/kubepile.js",
package/dist/src/cli.js CHANGED
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline/promises";
4
4
  import { Command } from "@commander-js/extra-typings";
5
5
  import packageJson from "../package.json" with { type: "json" };
6
6
  import { compileToKubeConfig, defaultKubeConfigPath, defaultKubepileDir, splitKubeConfigFile, } from "./kubepile.js";
7
- import { generateShellCommand, installShellIntegration, } from "./shell.js";
7
+ import { generateShellCommand, installShellIntegration, listSourceContextNames, } from "./shell.js";
8
8
  const program = createProgram();
9
9
  if (process.argv.slice(2).length === 0) {
10
10
  program.outputHelp();
@@ -100,8 +100,19 @@ Defaults:
100
100
  program
101
101
  .command("source")
102
102
  .description("Switch to one kube context in the current shell.")
103
- .argument("<context>", "context name to source")
104
- .action(() => {
103
+ .argument("[context]", "context name to source")
104
+ .option("--list", "list available contexts")
105
+ .action(async (context, options) => {
106
+ if (options.list) {
107
+ const contextNames = await listSourceContextNames();
108
+ if (contextNames.length > 0) {
109
+ process.stdout.write(`${contextNames.join("\n")}\n`);
110
+ }
111
+ return;
112
+ }
113
+ if (!context) {
114
+ throw new Error("kubepile source requires a context name or --list.");
115
+ }
105
116
  throw new Error("kubepile source requires shell integration. Run `kubepile install`, start a new shell, then run `kubepile source <context>`.");
106
117
  });
107
118
  return program;
@@ -9,6 +9,9 @@ export interface GenerateShellCommandResult {
9
9
  kubeConfigPath: string;
10
10
  shellCommand: string;
11
11
  }
12
+ export interface ListSourceContextNamesOptions {
13
+ sourcePath?: string;
14
+ }
12
15
  export interface InstallShellIntegrationOptions {
13
16
  shell?: ShellKind;
14
17
  rcFile?: string;
@@ -20,6 +23,7 @@ export interface InstallShellIntegrationResult {
20
23
  updated: boolean;
21
24
  }
22
25
  export declare function generateShellCommand(contextName: string, options?: GenerateShellCommandOptions): Promise<GenerateShellCommandResult>;
26
+ export declare function listSourceContextNames(options?: ListSourceContextNamesOptions): Promise<string[]>;
23
27
  export declare function installShellIntegration(options?: InstallShellIntegrationOptions): Promise<InstallShellIntegrationResult>;
24
28
  export declare function detectCurrentShell(shellPath?: string | undefined): ShellKind;
25
29
  export declare function shellRcFile(shell: ShellKind, homeDir?: string): Promise<string>;
package/dist/src/shell.js CHANGED
@@ -1,16 +1,22 @@
1
1
  import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { defaultKubeConfigPath, extractContextConfig, readKubeConfigFile, serializeKubeConfig, } from "./kubepile.js";
4
+ import { defaultKubeConfigPath, readKubeConfigFile, serializeKubeConfig, } from "./kubepile.js";
5
5
  const SHELL_BLOCK_START = "# >>> kubepile shell integration >>>";
6
6
  const SHELL_BLOCK_END = "# <<< kubepile shell integration <<<";
7
7
  export async function generateShellCommand(contextName, options = {}) {
8
8
  const sourcePath = options.sourcePath ?? defaultKubeConfigPath();
9
9
  const shell = options.shell ?? "posix";
10
10
  const sourceConfig = await readKubeConfigFile(sourcePath);
11
- const contextConfig = extractContextConfig(sourceConfig, contextName, sourcePath);
11
+ const contextConfig = {
12
+ ...sourceConfig,
13
+ "current-context": contextName,
14
+ };
12
15
  const tempRoot = await mkdtemp(path.join(options.tempDir ?? os.tmpdir(), "kubepile-source-"));
13
16
  const kubeConfigPath = path.join(tempRoot, "config");
17
+ if (!contextNamesFromConfig(sourceConfig, sourcePath).includes(contextName)) {
18
+ throw new Error(`${sourcePath} does not contain context "${contextName}"`);
19
+ }
14
20
  await writeFile(kubeConfigPath, serializeKubeConfig(contextConfig), {
15
21
  encoding: "utf8",
16
22
  mode: 0o600,
@@ -22,6 +28,10 @@ export async function generateShellCommand(contextName, options = {}) {
22
28
  : posixSourceCommand(contextName, kubeConfigPath),
23
29
  };
24
30
  }
31
+ export async function listSourceContextNames(options = {}) {
32
+ const sourcePath = options.sourcePath ?? defaultKubeConfigPath();
33
+ return contextNamesFromConfig(await readKubeConfigFile(sourcePath), sourcePath);
34
+ }
25
35
  export async function installShellIntegration(options = {}) {
26
36
  const shell = options.shell ?? detectCurrentShell();
27
37
  const rcFile = options.rcFile ?? await shellRcFile(shell, options.homeDir ?? os.homedir());
@@ -79,6 +89,12 @@ function posixIntegrationFunction(shell) {
79
89
  return `kubepile() {
80
90
  if [ "$1" = "source" ]; then
81
91
  shift
92
+ for arg in "$@"; do
93
+ if [ "$arg" = "--list" ]; then
94
+ command \\kubepile source "$@"
95
+ return
96
+ fi
97
+ done
82
98
  eval "$(command \\kubepile generate-shell-command --shell ${shell} "$@")"
83
99
  else
84
100
  command \\kubepile "$@"
@@ -89,7 +105,11 @@ function fishIntegrationFunction() {
89
105
  return `function kubepile
90
106
  if test (count $argv) -gt 0; and test "$argv[1]" = "source"
91
107
  set -e argv[1]
92
- command kubepile generate-shell-command --shell fish $argv | source
108
+ if contains -- --list $argv
109
+ command kubepile source $argv
110
+ else
111
+ command kubepile generate-shell-command --shell fish $argv | source
112
+ end
93
113
  else
94
114
  command kubepile $argv
95
115
  end
@@ -150,6 +170,20 @@ async function firstExistingPath(filePaths) {
150
170
  }
151
171
  return undefined;
152
172
  }
173
+ function contextNamesFromConfig(config, sourcePath) {
174
+ if (config.contexts === undefined) {
175
+ return [];
176
+ }
177
+ if (!Array.isArray(config.contexts)) {
178
+ throw new Error(`${sourcePath} contexts must be an array`);
179
+ }
180
+ return config.contexts.map((context) => {
181
+ if (typeof context.name !== "string" || context.name.length === 0) {
182
+ throw new Error(`${sourcePath} context name must be a non-empty string`);
183
+ }
184
+ return context.name;
185
+ });
186
+ }
153
187
  function escapeRegExp(value) {
154
188
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
155
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubepile",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Compile and split kubeconfig files from ~/.config/kubepile.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/kubepile.js",