99 "github.com/spf13/cobra"
1010 "netapp/neoctl/internal/config"
1111 "netapp/neoctl/internal/runner"
12+ "netapp/neoctl/internal/runtime"
1213 "netapp/neoctl/internal/utils"
14+ "os/exec"
1315 "path/filepath"
1416 "text/tabwriter"
1517 "time"
@@ -207,9 +209,18 @@ func runInstanceAction(name string, action string) {
207209 fmt .Printf ("Creating instance \" %s\" ...\n " , name )
208210
209211 // Verify prerequisites first
210- runStep ("Verifying prerequisites" , func () error {
212+ if err := runStep ("Verifying prerequisites" , func () error {
211213 return runVerifyChecks (false )
212- })
214+ }); err != nil {
215+ resetPrivileges ()
216+ os .Exit (1 )
217+ }
218+
219+ // Ensure privileges if needed (Podman)
220+ if err := ensurePrivileges (); err != nil {
221+ fmt .Printf ("Error ensuring privileges: %v\n " , err )
222+ os .Exit (1 )
223+ }
213224
214225 // Load Bundle Config for images
215226 bundleCfg , err := config .LoadConfig ()
@@ -227,9 +238,12 @@ func runInstanceAction(name string, action string) {
227238 // Configuration missing - print X and fetch
228239 fmt .Printf ("\r \u001b [31m✗\u001b [0m %s \n " , checkDesc )
229240
230- runStep ("Fetching bundle configuration" , func () error {
241+ if err := runStep ("Fetching bundle configuration" , func () error {
231242 return fetchLatestBundles (false )
232- })
243+ }); err != nil {
244+ resetPrivileges ()
245+ os .Exit (1 )
246+ }
233247
234248 // Reload config after fetching
235249 bundleCfg , err = config .LoadConfig ()
@@ -244,15 +258,21 @@ func runInstanceAction(name string, action string) {
244258
245259
246260
247- runStep ("Writing configuration" , func () error {
261+ if err := runStep ("Writing configuration" , func () error {
248262 return runner .GenerateComposeFile (inst , bundleCfg , configDir )
249- })
263+ }); err != nil {
264+ resetPrivileges ()
265+ os .Exit (1 )
266+ }
250267
251- runStep (fmt .Sprintf ("Ensuring images (%s, %s)" , inst .Name + "-neo" , inst .Name + "-neoui" ), func () error {
268+ if err := runStep (fmt .Sprintf ("Ensuring images (%s, %s)" , inst .Name + "-neo" , inst .Name + "-neoui" ), func () error {
252269 return runner .RunComposePull (configDir , name )
253- })
270+ }); err != nil {
271+ resetPrivileges ()
272+ os .Exit (1 )
273+ }
254274
255- runStep ("Starting instance" , func () error {
275+ if err := runStep ("Starting instance" , func () error {
256276 if err := runner .RunComposeUp (configDir , name ); err != nil {
257277 return err
258278 }
@@ -271,7 +291,12 @@ func runInstanceAction(name string, action string) {
271291 fmt .Printf ("\n \u001b [33mWarning: Instance is running but degraded (%d/%d containers up)\u001b [0m\n " , runningCount , totalCount )
272292 }
273293 return nil
274- })
294+ }); err != nil {
295+ resetPrivileges ()
296+ os .Exit (1 )
297+ }
298+
299+ resetPrivileges ()
275300
276301 fmt .Printf ("Instance \" %s\" started! 🚀\n " , name )
277302
@@ -293,15 +318,33 @@ func runInstanceAction(name string, action string) {
293318 case "stop" :
294319 fmt .Printf ("Stopping instance \" %s\" ...\n " , name )
295320
296- runStep ("Stopping all containers" , func () error {
321+ // Ensure privileges if needed (Podman)
322+ if err := ensurePrivileges (); err != nil {
323+ fmt .Printf ("Error ensuring privileges: %v\n " , err )
324+ os .Exit (1 )
325+ }
326+
327+ if err := runStep ("Stopping all containers" , func () error {
297328 return runner .RunComposeDown (configDir , name )
298- })
329+ }); err != nil {
330+ resetPrivileges ()
331+ os .Exit (1 )
332+ }
333+
334+ resetPrivileges ()
299335
300336 fmt .Printf ("Instance \" %s\" stopped! 🛑\n " , name )
301337
302338 case "delete" :
303339 // Stop first
304340 fmt .Printf ("Stopping instance '%s' before deletion...\n " , name )
341+
342+ // Ensure privileges if needed (Podman)
343+ if err := ensurePrivileges (); err != nil {
344+ fmt .Printf ("Error ensuring privileges: %v\n " , err )
345+ os .Exit (1 )
346+ }
347+
305348 if err := runner .RunComposeDown (configDir , name ); err != nil {
306349 fmt .Printf ("Warning: Failed to stop instance (might be already stopped or files missing): %v\n " , err )
307350 }
@@ -463,12 +506,46 @@ func init() {
463506 instanceLogsCmd .Flags ().BoolVar (& instAll , "all" , false , "Fetch logs for all instances" )
464507}
465508
466- func runStep (description string , action func () error ) {
509+ func runStep (description string , action func () error ) error {
467510 fmt .Printf (" \u001b [36m•\u001b [0m %s ..." , description )
468511 if err := action (); err != nil {
469512 fmt .Printf ("\r \u001b [31m✗\u001b [0m %s \n " , description )
470513 fmt .Printf ("Error: %v\n " , err )
471- os . Exit ( 1 )
514+ return err
472515 }
473516 fmt .Printf ("\r \u001b [32m✓\u001b [0m %s \n " , description )
517+ return nil
518+ }
519+
520+ func ensurePrivileges () error {
521+ rt , err := runtime .CheckRuntime ()
522+ if err != nil {
523+ return err
524+ }
525+
526+ if rt == runtime .Podman {
527+ fmt .Printf (" ⚠️ Podman runtime requires privilege escalation ...\n " )
528+ // sudo -v refreshes the timestamp. -p prompts if needed.
529+ // We use the same prompt as in the runner to be consistent, though theoretically
530+ // if this succeeds, the runner one won't prompt.
531+ cmd := exec .Command ("sudo" , "-v" , "-p" , " [sudo] password for %u: " )
532+ cmd .Stdin = os .Stdin
533+ cmd .Stdout = os .Stdout
534+ cmd .Stderr = os .Stderr
535+
536+ if err := cmd .Run (); err != nil {
537+ return fmt .Errorf ("sudo authentication failed: %w" , err )
538+ }
539+ }
540+ return nil
541+ }
542+
543+ func resetPrivileges () {
544+ rt , err := runtime .CheckRuntime ()
545+ if err == nil && rt == runtime .Podman {
546+ cmd := exec .Command ("sudo" , "-k" )
547+ if err := cmd .Run (); err == nil {
548+ fmt .Println (" \u001b [32m✓\u001b [0m Reset privilege escalation session for security purposes" )
549+ }
550+ }
474551}
0 commit comments