@@ -9,7 +9,8 @@ update_settings(k8s_upsert_timeout_secs = 60) # on first tilt up, often can tak
99#Add tools to path
1010os .putenv ("PATH" , os .getenv ("PATH" ) + ":" + tools_bin )
1111
12- keys = ["OCI_TENANCY_ID" , "OCI_USER_ID" , "OCI_CREDENTIALS_FINGERPRINT" , "OCI_REGION" , "OCI_CREDENTIALS_KEY_PATH" ]
12+ legacy_auth_keys = ["OCI_TENANCY_ID" , "OCI_USER_ID" , "OCI_CREDENTIALS_FINGERPRINT" , "OCI_REGION" , "OCI_CREDENTIALS_KEY" ]
13+ session_auth_keys = ["OCI_TENANCY_ID" , "OCI_CREDENTIALS_FINGERPRINT" , "OCI_REGION" , "OCI_SESSION_TOKEN" , "OCI_SESSION_PRIVATE_KEY" ]
1314
1415# set defaults
1516settings = {
@@ -19,7 +20,7 @@ settings = {
1920 "deploy_cert_manager" : True ,
2021 "preload_images_for_kind" : True ,
2122 "kind_cluster_name" : "capoci" ,
22- "capi_version" : "v1.10.7 " ,
23+ "capi_version" : "v1.11.0 " ,
2324 "cert_manager_version" : "v1.16.2" ,
2425 "kubernetes_version" : "v1.30.0" ,
2526}
@@ -66,10 +67,104 @@ def validate_auth():
6667 for sub in substitutions :
6768 if sub [- 4 :] == "_B64" :
6869 os .environ [sub [:- 4 ]] = base64_decode (os .environ [sub ])
69- print ("{} was not specified in tilt-settings.json, attempting to load {}" .format (base64_decode (os .environ [sub ]), sub ))
70- missing = [k for k in keys if not os .environ .get (k )]
70+ print ("loaded {} from {}" .format (sub [:- 4 ], sub ))
71+
72+ if not os .environ .get ("OCI_CREDENTIALS_KEY" ) and os .environ .get ("OCI_CREDENTIALS_KEY_PATH" ):
73+ os .environ ["OCI_CREDENTIALS_KEY" ] = read_file_from_path (os .environ .get ("OCI_CREDENTIALS_KEY_PATH" ))
74+
75+ use_instance_principal = str (os .environ .get ("USE_INSTANCE_PRINCIPAL" , "false" )).strip ().lower () == "true"
76+ use_session_token = str (os .environ .get ("USE_SESSION_TOKEN" , "false" )).strip ().lower () == "true"
77+ if not use_instance_principal and not use_session_token and settings .get ("oci_session_profile" ):
78+ # If a session profile is configured, default to session-token mode to avoid silent fallback to API-key auth.
79+ use_session_token = True
80+ os .environ ["USE_SESSION_TOKEN" ] = "true"
81+ print ("oci_session_profile is set; defaulting auth mode to session-token" )
82+
83+ if use_instance_principal :
84+ return "instance-principal"
85+
86+ if use_session_token :
87+ apply_session_profile_defaults ()
88+ missing = [k for k in session_auth_keys if not os .environ .get (k )]
89+ if missing :
90+ fail ("session-token auth selected but missing kustomize_substitutions values for {}" .format (missing ))
91+ return "session-token"
92+
93+ missing = [k for k in legacy_auth_keys if not os .environ .get (k )]
7194 if missing :
72- fail ("missing kustomize_substitutions keys {} in tilt-setting.json" .format (missing ))
95+ fail ("legacy API-key auth selected but missing kustomize_substitutions values for {}. Set USE_SESSION_TOKEN_B64 or USE_INSTANCE_PRINCIPAL_B64 to switch modes." .format (missing ))
96+ return "legacy-api-key"
97+
98+ def add_session_token_refresh_resource (auth_mode ):
99+ if auth_mode != "session-token" :
100+ return
101+
102+ session_profile = settings .get ("oci_session_profile" , "DEFAULT" )
103+ token_path = expand_path (settings .get ("oci_session_token_path" ))
104+ private_key_path = expand_path (settings .get ("oci_session_private_key_path" ))
105+ if not token_path or not private_key_path :
106+ local_resource (
107+ "oci-session-refresh" ,
108+ cmd = "echo 'Set oci_session_token_path and oci_session_private_key_path in tilt-settings.json for session refresh.' >&2; exit 1" ,
109+ trigger_mode = TRIGGER_MODE_MANUAL ,
110+ auto_init = False ,
111+ labels = ["cluster-api" ],
112+ )
113+ return
114+
115+ refresh_cmd = """set -euo pipefail
116+ if command -v oci >/dev/null 2>&1; then
117+ if ! oci session refresh --profile '{profile}'; then
118+ echo "warning: oci session refresh failed for profile '{profile}', continuing with local session files" >&2
119+ fi
120+ fi
121+ CAPOCI_NAMESPACE="$({kubectl_cmd} get deploy -A -o jsonpath='{{range .items[?(@.metadata.name==\" capoci-controller-manager\" ) ]}}{{.metadata.namespace}}{{end}}' 2>/dev/null || true)"
122+ if [ -z "$CAPOCI_NAMESPACE" ]; then
123+ CAPOCI_NAMESPACE="cluster-api-provider-oci-system"
124+ fi
125+ CAPOCI_AUTH_SECRET_NAME="$({kubectl_cmd} get deploy capoci-controller-manager -n "$CAPOCI_NAMESPACE" -o jsonpath='{{.spec.template.spec.volumes[?(@.name==\" auth-config-dir\" )].secret.secretName}}' 2>/dev/null || true)"
126+ if [ -z "$CAPOCI_AUTH_SECRET_NAME" ]; then
127+ CAPOCI_AUTH_SECRET_NAME="capoci-auth-config"
128+ fi
129+ export CAPOCI_NAMESPACE
130+ export CAPOCI_AUTH_SECRET_NAME
131+ export USE_INSTANCE_PRINCIPAL_B64="ZmFsc2U="
132+ export USE_SESSION_TOKEN_B64="dHJ1ZQ=="
133+ export OCI_SESSION_TOKEN_B64="$(base64 < '{token_path}' | tr -d '\\ n')"
134+ export OCI_SESSION_PRIVATE_KEY_B64="$(awk 'BEGIN{{in_key=0}}
135+ !in_key {{
136+ if (match($0, /-----BEGIN [A-Z ]*PRIVATE KEY-----/)) {{
137+ in_key=1
138+ }} else {{
139+ next
140+ }}
141+ }}
142+ {{
143+ line=$0
144+ if (match(line, /-----END [A-Z ]*PRIVATE KEY-----/)) {{
145+ print substr(line, 1, RSTART + RLENGTH - 1)
146+ exit
147+ }}
148+ print line
149+ }}' '{private_key_path}' | base64 | tr -d '\\ n')"
150+ {envsubst_cmd} < config/default/credentials.yaml \
151+ | sed "s/^ name: auth-config$/ name: $CAPOCI_AUTH_SECRET_NAME/" \
152+ | sed "s/^ namespace: system$/ namespace: $CAPOCI_NAMESPACE/" \
153+ | {kubectl_cmd} apply -f -
154+ """ .format (
155+ profile = session_profile ,
156+ token_path = token_path ,
157+ private_key_path = private_key_path ,
158+ envsubst_cmd = envsubst_cmd ,
159+ kubectl_cmd = kubectl_cmd ,
160+ )
161+ local_resource (
162+ "oci-session-refresh" ,
163+ cmd = refresh_cmd ,
164+ trigger_mode = TRIGGER_MODE_MANUAL ,
165+ auto_init = True ,
166+ labels = ["cluster-api" ],
167+ )
73168
74169# Users may define their own Tilt customizations in tilt.d. This directory is excluded from git and these files will
75170# not be checked in to version control.
@@ -184,13 +279,118 @@ def base64_encode(to_encode):
184279 return str (encode_blob )
185280
186281def base64_encode_file (path_to_encode ):
187- encode_blob = local ("cat {} | tr -d '\n ' | base64 - | tr -d ' \n ' " .format (path_to_encode ), quiet = True )
282+ encode_blob = local ("base64 < {} | tr -d '\n '" .format (shell_single_quote ( path_to_encode ) ), quiet = True )
188283 return str (encode_blob )
189284
190285def read_file_from_path (path_to_read ):
191- str_blob = local ("cat {} | tr -d ' \n ' " .format (path_to_read ), quiet = True )
286+ str_blob = local ("cat {}" .format (shell_single_quote ( path_to_read ) ), quiet = True )
192287 return str (str_blob )
193288
289+ def read_private_key_pem_from_path (path_to_read ):
290+ awk_cmd = """awk 'BEGIN{{in_key=0}}
291+ !in_key {{
292+ if (match($0, /-----BEGIN [A-Z ]*PRIVATE KEY-----/)) {{
293+ in_key=1
294+ }} else {{
295+ next
296+ }}
297+ }}
298+ {{
299+ line=$0
300+ if (match(line, /-----END [A-Z ]*PRIVATE KEY-----/)) {{
301+ print substr(line, 1, RSTART + RLENGTH - 1)
302+ exit
303+ }}
304+ print line
305+ }}' {path}""" .format (path = shell_single_quote (path_to_read ))
306+ pem = str (local (awk_cmd , quiet = True , echo_off = True )).strip ()
307+ if not pem :
308+ fail ("no PEM private key block found in {}" .format (path_to_read ))
309+ return pem + "\n "
310+
311+ def shell_single_quote (s ):
312+ return "'" + str (s ).replace ("'" , "'\" '\" '" ) + "'"
313+
314+ def expand_path (path ):
315+ if path == None :
316+ return ""
317+ path_str = str (path ).strip ()
318+ if path_str == "" :
319+ return ""
320+ home = str (os .getenv ("HOME" , "" ))
321+ if path_str [0 :2 ] == "~/" :
322+ return home + path_str [1 :]
323+ return path_str .replace ("$HOME" , home ).replace ("${HOME}" , home )
324+
325+ def read_oci_profile_value (profile , key ):
326+ config_file = expand_path ("~/.oci/config" )
327+ if not config_file :
328+ return ""
329+ profile_name = str (profile ).strip ()
330+ key_name = str (key ).strip ()
331+ if not profile_name or not key_name :
332+ return ""
333+ awk_cmd = """awk '
334+ $0 == "[{profile}]" {{ in_profile=1; next }}
335+ /^\\ [/ {{ in_profile=0 }}
336+ in_profile && $0 ~ /^[[:space:]]*{key}[[:space:]]*=/ {{
337+ line=$0
338+ sub(/^[[:space:]]*{key}[[:space:]]*=[[:space:]]*/, "", line)
339+ print line
340+ exit
341+ }}
342+ ' {config_file}""" .format (
343+ profile = profile_name ,
344+ key = key_name ,
345+ config_file = shell_single_quote (config_file ),
346+ )
347+ return str (local (awk_cmd , quiet = True , echo_off = True )).strip ()
348+
349+ def set_env_if_missing (name , value ):
350+ if value and not str (os .environ .get (name , "" )).strip ():
351+ os .environ [name ] = str (value ).strip ()
352+
353+ def ensure_env_matches_profile (name , profile_value , profile_name ):
354+ env_val = str (os .environ .get (name , "" )).strip ()
355+ if not profile_value or not env_val :
356+ return
357+ if env_val != profile_value :
358+ fail ("{} in kustomize_substitutions ({}) does not match {} profile {} value ({})." .format (name , env_val , profile_name , name .lower (), profile_value ))
359+
360+ def apply_session_profile_defaults ():
361+ profile = str (settings .get ("oci_session_profile" , "" )).strip ()
362+ if not profile :
363+ return
364+
365+ profile_tenancy = read_oci_profile_value (profile , "tenancy" )
366+ profile_region = read_oci_profile_value (profile , "region" )
367+ profile_fingerprint = read_oci_profile_value (profile , "fingerprint" )
368+ profile_token_path = read_oci_profile_value (profile , "security_token_file" )
369+ profile_key_path = read_oci_profile_value (profile , "key_file" )
370+
371+ set_env_if_missing ("OCI_TENANCY_ID" , profile_tenancy )
372+ set_env_if_missing ("OCI_REGION" , profile_region )
373+ set_env_if_missing ("OCI_CREDENTIALS_FINGERPRINT" , profile_fingerprint )
374+
375+ ensure_env_matches_profile ("OCI_TENANCY_ID" , profile_tenancy , profile )
376+ ensure_env_matches_profile ("OCI_REGION" , profile_region , profile )
377+ ensure_env_matches_profile ("OCI_CREDENTIALS_FINGERPRINT" , profile_fingerprint , profile )
378+
379+ token_path = expand_path (settings .get ("oci_session_token_path" ))
380+ private_key_path = expand_path (settings .get ("oci_session_private_key_path" ))
381+ if not token_path :
382+ token_path = expand_path (profile_token_path )
383+ if not private_key_path :
384+ private_key_path = expand_path (profile_key_path )
385+
386+ if token_path and not os .environ .get ("OCI_SESSION_TOKEN" ):
387+ os .environ ["OCI_SESSION_TOKEN" ] = read_file_from_path (token_path )
388+ if os .environ .get ("OCI_SESSION_TOKEN" ):
389+ os .environ ["OCI_SESSION_TOKEN_B64" ] = base64_encode (os .environ ["OCI_SESSION_TOKEN" ])
390+ if private_key_path :
391+ os .environ ["OCI_SESSION_PRIVATE_KEY" ] = read_private_key_pem_from_path (private_key_path )
392+ os .environ ["OCI_SESSION_PRIVATE_KEY_B64" ] = base64_encode (os .environ ["OCI_SESSION_PRIVATE_KEY" ])
393+
194394def base64_decode (to_decode ):
195395 # Use -D for macOS (BSD), --decode - for Linux (GNU)
196396 # Detect OS using uname command
@@ -211,7 +411,8 @@ def kustomizesub(folder):
211411# Actual work happens here
212412##############################
213413
214- validate_auth ()
414+ auth_mode = validate_auth ()
415+ add_session_token_refresh_resource (auth_mode )
215416
216417include_user_tilt_files ()
217418
0 commit comments