Skip to content

Commit ffb1c47

Browse files
mplsgrantjosibake
authored andcommitted
add create-kubeconfigs to warnet admin
Replace the setup_contexts.sh script into a proper warnet command. Co-authored-by: mplsgrant <[email protected]>
1 parent eeba1b1 commit ffb1c47

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

src/warnet/admin.py

+92
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
from rich import print as richprint
66

77
from .constants import NETWORK_DIR
8+
from .k8s import get_kubeconfig_value, get_namespaces_by_prefix, get_service_accounts_in_namespace
89
from .namespaces import copy_namespaces_defaults, namespaces
910
from .network import copy_network_defaults
11+
from .process import run_command
1012

1113

1214
@click.group(name="admin", hidden=True)
@@ -33,3 +35,93 @@ def init():
3335
f"[green]Copied network and namespace example files to {Path(current_dir) / NETWORK_DIR.name}[/green]"
3436
)
3537
richprint(f"[green]Created warnet project structure in {current_dir}[/green]")
38+
39+
40+
@admin.command()
41+
@click.argument("prefix", type=str, required=True)
42+
@click.option(
43+
"--kubeconfig-dir",
44+
default="kubeconfigs",
45+
help="Directory to store kubeconfig files (default: kubeconfigs)",
46+
)
47+
@click.option(
48+
"--token-duration",
49+
default=172800,
50+
type=int,
51+
help="Duration of the token in seconds (default: 48 hours)",
52+
)
53+
def create_kubeconfigs(prefix: str, kubeconfig_dir, token_duration):
54+
"""Create kubeconfig files for all ServiceAccounts in warnet team namespaces starting with <prefix>."""
55+
kubeconfig_dir = os.path.expanduser(kubeconfig_dir)
56+
57+
cluster_name = get_kubeconfig_value("{.clusters[0].name}")
58+
cluster_server = get_kubeconfig_value("{.clusters[0].cluster.server}")
59+
cluster_ca = get_kubeconfig_value("{.clusters[0].cluster.certificate-authority-data}")
60+
61+
os.makedirs(kubeconfig_dir, exist_ok=True)
62+
63+
# Get all namespaces that start with prefix
64+
# This assumes when deploying multiple namespacs for the purpose of team games, all namespaces start with a prefix,
65+
# e.g., tabconf-wargames-*. Currently, this is a bit brittle, but we can improve on this in the future
66+
# by automatically applying a TEAM_PREFIX when creating the get_warnet_namespaces
67+
# TODO: choose a prefix convention and have it managed by the helm charts instead of requiring the
68+
# admin user to pipe through the correct string in multiple places. Another would be to use
69+
# labels instead of namespace naming conventions
70+
warnet_namespaces = get_namespaces_by_prefix(prefix)
71+
72+
for namespace in warnet_namespaces:
73+
click.echo(f"Processing namespace: {namespace}")
74+
service_accounts = get_service_accounts_in_namespace(namespace)
75+
76+
for sa in service_accounts:
77+
# Create a token for the ServiceAccount with specified duration
78+
command = f"kubectl create token {sa} -n {namespace} --duration={token_duration}s"
79+
try:
80+
token = run_command(command)
81+
except Exception as e:
82+
click.echo(
83+
f"Failed to create token for ServiceAccount {sa} in namespace {namespace}. Error: {str(e)}. Skipping..."
84+
)
85+
continue
86+
87+
# Create a kubeconfig file for the user
88+
kubeconfig_file = os.path.join(kubeconfig_dir, f"{sa}-{namespace}-kubeconfig")
89+
90+
# TODO: move yaml out of python code to resources/manifests/
91+
#
92+
# might not be worth it since we are just reading the yaml to then create a bunch of values and its not
93+
# actually used to deploy anything into the cluster
94+
# Then benefit would be making this code a bit cleaner and easy to follow, fwiw
95+
kubeconfig_content = f"""apiVersion: v1
96+
kind: Config
97+
clusters:
98+
- name: {cluster_name}
99+
cluster:
100+
server: {cluster_server}
101+
certificate-authority-data: {cluster_ca}
102+
users:
103+
- name: {sa}
104+
user:
105+
token: {token}
106+
contexts:
107+
- name: {sa}-{namespace}
108+
context:
109+
cluster: {cluster_name}
110+
namespace: {namespace}
111+
user: {sa}
112+
current-context: {sa}-{namespace}
113+
"""
114+
with open(kubeconfig_file, "w") as f:
115+
f.write(kubeconfig_content)
116+
117+
click.echo(f" Created kubeconfig file for {sa}: {kubeconfig_file}")
118+
119+
click.echo("---")
120+
click.echo(
121+
f"All kubeconfig files have been created in the '{kubeconfig_dir}' directory with a duration of {token_duration} seconds."
122+
)
123+
click.echo("Distribute these files to the respective users.")
124+
click.echo(
125+
"Users can then use by running `warnet auth <file>` or with kubectl by specifying the --kubeconfig flag or by setting the KUBECONFIG environment variable."
126+
)
127+
click.echo(f"Note: The tokens will expire after {token_duration} seconds.")

src/warnet/k8s.py

+24
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,27 @@ def wait_for_caddy_ready(name, namespace, timeout=300):
245245
return True
246246
print(f"Timeout waiting for Caddy pod {name} to be ready.")
247247
return False
248+
249+
250+
def get_kubeconfig_value(jsonpath):
251+
command = f"kubectl config view --minify -o jsonpath={jsonpath}"
252+
return run_command(command)
253+
254+
255+
def get_namespaces_by_prefix(prefix: str):
256+
"""
257+
Get all namespaces beginning with `prefix`. Returns empty list of no namespaces with the specified prefix are found.
258+
"""
259+
command = "kubectl get namespaces -o jsonpath={.items[*].metadata.name}"
260+
namespaces = run_command(command).split()
261+
return [ns for ns in namespaces if ns.startswith(prefix)]
262+
263+
264+
def get_service_accounts_in_namespace(namespace):
265+
"""
266+
Get all service accounts in a namespace. Returns an empty list if no service accounts are found in the specified namespace.
267+
"""
268+
command = f"kubectl get serviceaccounts -n {namespace} -o jsonpath={{.items[*].metadata.name}}"
269+
# skip the default service account created by k8s
270+
service_accounts = run_command(command).split()
271+
return [sa for sa in service_accounts if sa != "default"]

0 commit comments

Comments
 (0)