@@ -2,6 +2,7 @@ package cli
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "io"
78 "os"
@@ -30,6 +31,7 @@ const welcome = `
3031
3132func ShellCmd (ctx context.Context , appTitle string ) * cobra.Command {
3233 var rc runtimeconfig.RuntimeConfig
34+ var command string
3335
3436 cmd := & cobra.Command {
3537 Use : "shell" ,
@@ -55,72 +57,121 @@ func ShellCmd(ctx context.Context, appTitle string) *cobra.Command {
5557 shpath = "/bin/bash"
5658 }
5759
58- fmt .Printf (welcome , runtimeconfig .AppSlug ())
59- shell := exec .Command (shpath )
60- shell .Env = os .Environ ()
61-
62- // get the current working directory
63- var err error
64- shell .Dir , err = os .Getwd ()
65- if err != nil {
66- return fmt .Errorf ("unable to get current working directory: %w" , err )
60+ // Command execution mode
61+ if command != "" {
62+ return executeCommand (shpath , command , rc )
6763 }
6864
69- shellpty , err := pty . Start ( shell )
70- if err != nil {
71- return fmt . Errorf ( "unable to start shell: %w" , err )
72- }
65+ // Interactive shell mode
66+ return openInteractiveShell ( shpath , rc )
67+ },
68+ }
7369
74- sigch := make (chan os.Signal , 1 )
75- signal .Notify (sigch , syscall .SIGWINCH )
76- go handleResize (sigch , shellpty )
77- sigch <- syscall .SIGWINCH
78- state , err := term .MakeRaw (int (os .Stdin .Fd ()))
79- if err != nil {
80- return fmt .Errorf ("unable to make raw terminal: %w" , err )
81- }
70+ cmd .Flags ().StringVarP (& command , "command" , "c" , "" , "Command to execute in the shell environment instead of opening an interactive shell" )
8271
83- defer func () {
84- signal .Stop (sigch )
85- close (sigch )
86- fd := int (os .Stdin .Fd ())
87- _ = term .Restore (fd , state )
88- }()
89-
90- kcpath := rc .PathToKubeConfig ()
91- config := fmt .Sprintf ("export KUBECONFIG=%q\n " , kcpath )
92- _ , _ = shellpty .WriteString (config )
93- _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
94-
95- bindir := rc .EmbeddedClusterBinsSubDir ()
96- config = fmt .Sprintf ("export PATH=\" $PATH:%s\" \n " , bindir )
97- _ , _ = shellpty .WriteString (config )
98- _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
99-
100- // if /etc/bash_completion is present enable kubectl auto completion.
101- if _ , err := os .Stat ("/etc/bash_completion" ); err == nil {
102- config = fmt .Sprintf ("source <(k0s completion %s)\n " , filepath .Base (shpath ))
103- _ , _ = shellpty .WriteString (config )
104- _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
105-
106- comppath := rc .PathToEmbeddedClusterBinary ("kubectl_completion_bash.sh" )
107- config = fmt .Sprintf ("source <(cat %s)\n " , comppath )
108- _ , _ = shellpty .WriteString (config )
109- _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
110-
111- config = "source /etc/bash_completion\n "
112- _ , _ = shellpty .WriteString (config )
113- _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
114- }
72+ return cmd
73+ }
11574
116- go func () { _ , _ = io .Copy (shellpty , os .Stdin ) }()
117- go func () { _ , _ = io .Copy (os .Stdout , shellpty ) }()
118- _ = shell .Wait ()
119- return nil
120- },
75+ // executeCommand executes a command in the shell with the embedded cluster environment configured.
76+ func executeCommand (shpath string , command string , rc runtimeconfig.RuntimeConfig ) error {
77+ // Build the command with environment setup
78+ shell := exec .Command (shpath , "-c" , command )
79+
80+ // Set environment variables
81+ shell .Env = os .Environ ()
82+ kcpath := rc .PathToKubeConfig ()
83+ shell .Env = append (shell .Env , fmt .Sprintf ("KUBECONFIG=%s" , kcpath ))
84+ bindir := rc .EmbeddedClusterBinsSubDir ()
85+ shell .Env = append (shell .Env , fmt .Sprintf ("PATH=%s:%s" , os .Getenv ("PATH" ), bindir ))
86+
87+ // Set working directory
88+ var err error
89+ shell .Dir , err = os .Getwd ()
90+ if err != nil {
91+ return fmt .Errorf ("unable to get current working directory: %w" , err )
12192 }
12293
123- return cmd
94+ // Connect stdio
95+ shell .Stdin = os .Stdin
96+ shell .Stdout = os .Stdout
97+ shell .Stderr = os .Stderr
98+
99+ // Execute and return exit code
100+ if err := shell .Run (); err != nil {
101+ // Preserve exit code from the command
102+ var exitErr * exec.ExitError
103+ if errors .As (err , & exitErr ) {
104+ os .Exit (exitErr .ExitCode ())
105+ }
106+ return err
107+ }
108+
109+ return nil
110+ }
111+
112+ // openInteractiveShell opens an interactive shell with the embedded cluster environment configured.
113+ func openInteractiveShell (shpath string , rc runtimeconfig.RuntimeConfig ) error {
114+ fmt .Printf (welcome , runtimeconfig .AppSlug ())
115+ shell := exec .Command (shpath )
116+ shell .Env = os .Environ ()
117+
118+ var err error
119+ shell .Dir , err = os .Getwd ()
120+ if err != nil {
121+ return fmt .Errorf ("unable to get current working directory: %w" , err )
122+ }
123+
124+ shellpty , err := pty .Start (shell )
125+ if err != nil {
126+ return fmt .Errorf ("unable to start shell: %w" , err )
127+ }
128+
129+ sigch := make (chan os.Signal , 1 )
130+ signal .Notify (sigch , syscall .SIGWINCH )
131+ go handleResize (sigch , shellpty )
132+ sigch <- syscall .SIGWINCH
133+ state , err := term .MakeRaw (int (os .Stdin .Fd ()))
134+ if err != nil {
135+ return fmt .Errorf ("unable to make raw terminal: %w" , err )
136+ }
137+
138+ defer func () {
139+ signal .Stop (sigch )
140+ close (sigch )
141+ fd := int (os .Stdin .Fd ())
142+ _ = term .Restore (fd , state )
143+ }()
144+
145+ kcpath := rc .PathToKubeConfig ()
146+ config := fmt .Sprintf ("export KUBECONFIG=%q\n " , kcpath )
147+ _ , _ = shellpty .WriteString (config )
148+ _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
149+
150+ bindir := rc .EmbeddedClusterBinsSubDir ()
151+ config = fmt .Sprintf ("export PATH=\" $PATH:%s\" \n " , bindir )
152+ _ , _ = shellpty .WriteString (config )
153+ _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
154+
155+ // if /etc/bash_completion is present enable kubectl auto completion.
156+ if _ , err := os .Stat ("/etc/bash_completion" ); err == nil {
157+ config = fmt .Sprintf ("source <(k0s completion %s)\n " , filepath .Base (shpath ))
158+ _ , _ = shellpty .WriteString (config )
159+ _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
160+
161+ comppath := rc .PathToEmbeddedClusterBinary ("kubectl_completion_bash.sh" )
162+ config = fmt .Sprintf ("source <(cat %s)\n " , comppath )
163+ _ , _ = shellpty .WriteString (config )
164+ _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
165+
166+ config = "source /etc/bash_completion\n "
167+ _ , _ = shellpty .WriteString (config )
168+ _ , _ = io .CopyN (io .Discard , shellpty , int64 (len (config )+ 1 ))
169+ }
170+
171+ go func () { _ , _ = io .Copy (shellpty , os .Stdin ) }()
172+ go func () { _ , _ = io .Copy (os .Stdout , shellpty ) }()
173+ _ = shell .Wait ()
174+ return nil
124175}
125176
126177// handleResize is a helper function to handle pty resizes.
0 commit comments