1919from .sync import sync_notebook
2020
2121
22+ def check_secret_exists (secret_name : str , namespace : str | None ) -> bool :
23+ """Check if a Kubernetes secret exists.
24+
25+ Args:
26+ secret_name: Name of the secret
27+ namespace: Kubernetes namespace (None = use kubectl context)
28+
29+ Returns:
30+ True if secret exists, False otherwise
31+ """
32+ cmd = ["kubectl" , "get" , "secret" , secret_name ]
33+ if namespace is not None :
34+ cmd .extend (["-n" , namespace ])
35+ result = subprocess .run (cmd , capture_output = True )
36+ return result .returncode == 0
37+
38+
39+ def parse_s3_credentials (
40+ s3cfg_path : str , namespace : str | None = None
41+ ) -> tuple [str | None , str | None , str | None ]:
42+ """Parse S3 credentials from ~/.s3cfg.
43+
44+ Tries sections in order: [namespace] -> [marimo] -> [default].
45+ This allows namespace-specific credentials for multi-tenant setups.
46+
47+ Args:
48+ s3cfg_path: Path to the s3cfg file
49+ namespace: Optional namespace to try as a section name first
50+
51+ Returns:
52+ (access_key, secret_key, section_name) or (None, None, None) if not found
53+ """
54+ config = configparser .ConfigParser ()
55+ config .read (s3cfg_path )
56+
57+ # Build section priority: namespace (if provided) -> marimo -> default
58+ sections = ["marimo" , "default" ]
59+ if namespace :
60+ sections .insert (0 , namespace )
61+
62+ for section in sections :
63+ try :
64+ access_key = config .get (section , "access_key" )
65+ secret_key = config .get (section , "secret_key" )
66+ return access_key , secret_key , section
67+ except (configparser .NoSectionError , configparser .NoOptionError ):
68+ continue
69+
70+ return None , None , None
71+
72+
2273def ensure_cw_credentials (namespace : str | None ) -> bool :
23- """Create cw-credentials secret from ~/.s3cfg if needed.
74+ """Ensure cw-credentials secret exists for cw:// mounts.
75+
76+ If the secret already exists, returns True immediately.
77+ Otherwise, attempts to create it from ~/.s3cfg credentials.
78+
79+ In interactive terminals (TTY), prompts for confirmation before creating.
80+ In non-interactive environments (CI/CD), creates automatically.
2481
2582 Args:
2683 namespace: Kubernetes namespace (None = use kubectl context)
2784
2885 Returns:
2986 True if secret exists or was created, False if no credentials available
3087 """
88+ # TODO: Support custom secret names via frontmatter `s3_secret` field
89+ # and pass through to operator via CRD spec.s3SecretName
90+ secret_name = "cw-credentials" # Hardcoded for now
91+ ns_display = namespace or "(current context)"
92+
93+ # Step 1: Check if secret already exists FIRST (most common case)
94+ if check_secret_exists (secret_name , namespace ):
95+ click .echo (f"Using existing secret '{ secret_name } ' in { ns_display } " )
96+ return True
97+
98+ # Step 2: Secret doesn't exist - check for local credentials
3199 s3cfg_path = os .path .expanduser ("~/.s3cfg" )
32100 if not os .path .exists (s3cfg_path ):
33101 click .echo (
34- "Warning: ~/.s3cfg not found. Run 's3cmd --configure' to set up credentials." ,
102+ f"Secret '{ secret_name } ' not found and ~/.s3cfg does not exist.\n "
103+ "Options:\n "
104+ f" 1. Create secret manually:\n "
105+ f" kubectl create secret generic { secret_name } \\ \n "
106+ f" --from-literal=AWS_ACCESS_KEY_ID=... \\ \n "
107+ f" --from-literal=AWS_SECRET_ACCESS_KEY=...\n "
108+ " 2. Configure s3cmd: s3cmd --configure" ,
35109 err = True ,
36110 )
37111 return False
38112
39- # Check if secret already exists
40- cmd = [ "kubectl" , "get" , "secret" , "cw-credentials" ]
41- if namespace is not None :
42- cmd . extend ([ "-n" , namespace ])
43- result = subprocess . run (
44- cmd ,
45- capture_output = True ,
46- )
47- if result . returncode == 0 :
48- return True # Already exists
113+ # Step 3: Parse credentials (try [namespace] -> [marimo] -> [default])
114+ access_key , secret_key , section = parse_s3_credentials ( s3cfg_path , namespace )
115+ if not access_key or not secret_key :
116+ sections_tried = f"[ { namespace } ], " if namespace else ""
117+ click . echo (
118+ f"Could not read credentials from ~/.s3cfg. \n "
119+ f"Tried sections: { sections_tried } [marimo], [default]" ,
120+ err = True ,
121+ )
122+ return False
49123
50- # Parse ~/.s3cfg
51- config = configparser .ConfigParser ()
52- config .read (s3cfg_path )
124+ # Step 4: Show what we're about to do
125+ click .echo (f"\n S3 Credentials:" )
126+ click .echo (f" Namespace: { ns_display } " )
127+ click .echo (f" Secret: { secret_name } (will create)" )
128+ click .echo (f" Source: ~/.s3cfg [{ section } ]" )
129+ click .echo (f" Access Key: ***" )
53130
54- try :
55- access_key = config .get ("default" , "access_key" )
56- secret_key = config .get ("default" , "secret_key" )
57- except (configparser .NoSectionError , configparser .NoOptionError ) as e :
58- click .echo (f"Warning: Could not read credentials from ~/.s3cfg: { e } " , err = True )
59- return False
131+ # Step 5: Confirm with user if in interactive terminal
132+ if sys .stdin .isatty ():
133+ if not click .confirm (f"\n Create secret '{ secret_name } '?" ):
134+ click .echo ("Secret creation skipped." )
135+ return False
60136
61- # Create secret
137+ # Step 6: Create the secret
62138 cmd = [
63139 "kubectl" ,
64140 "create" ,
65141 "secret" ,
66142 "generic" ,
67- "cw-credentials" ,
143+ secret_name ,
68144 f"--from-literal=AWS_ACCESS_KEY_ID={ access_key } " ,
69145 f"--from-literal=AWS_SECRET_ACCESS_KEY={ secret_key } " ,
70146 ]
@@ -78,12 +154,12 @@ def ensure_cw_credentials(namespace: str | None) -> bool:
78154 )
79155 if result .returncode != 0 :
80156 click .echo (
81- f"Warning: Failed to create cw-credentials secret: { result .stderr } " ,
157+ f"Failed to create secret: { result .stderr } " ,
82158 err = True ,
83159 )
84160 return False
85161
86- click .echo (f"Created cw-credentials secret in namespace { namespace } " )
162+ click .echo (f"Created secret ' { secret_name } ' " )
87163 return True
88164
89165
0 commit comments