@@ -20,6 +20,7 @@ import (
2020 "bufio"
2121 "fmt"
2222 "os"
23+ "path/filepath"
2324 "strings"
2425
2526 "github.com/NVIDIA/holodeck/api/holodeck/v1alpha1"
@@ -38,6 +39,7 @@ import (
3839type options struct {
3940 provision bool
4041 cachePath string
42+ cacheFile string
4143 envFile string
4244 kubeconfig string
4345
@@ -120,7 +122,7 @@ func (m command) run(c *cli.Context, opts *options) error {
120122 // Create instance manager and generate unique ID
121123 manager := instances .NewManager (m .log , opts .cachePath )
122124 instanceID := manager .GenerateInstanceID ()
123- opts .cachePath = manager .GetInstanceCacheFile (instanceID )
125+ opts .cacheFile = manager .GetInstanceCacheFile (instanceID )
124126
125127 // Add instance ID to environment metadata
126128 if opts .cfg .Labels == nil {
@@ -142,7 +144,7 @@ func (m command) run(c *cli.Context, opts *options) error {
142144 // SUSE: ec2-user
143145 opts .cfg .Spec .Username = "ubuntu"
144146 }
145- provider , err = aws .New (m .log , opts .cfg , opts .cachePath )
147+ provider , err = aws .New (m .log , opts .cfg , opts .cacheFile )
146148 if err != nil {
147149 return err
148150 }
@@ -161,7 +163,7 @@ func (m command) run(c *cli.Context, opts *options) error {
161163 }
162164
163165 // Read cache after creating the environment
164- opts .cache , err = jyaml.UnmarshalFromFile [v1alpha1.Environment ](opts .cachePath )
166+ opts .cache , err = jyaml.UnmarshalFromFile [v1alpha1.Environment ](opts .cacheFile )
165167 if err != nil {
166168 return fmt .Errorf ("failed to read cache file: %v" , err )
167169 }
@@ -170,15 +172,74 @@ func (m command) run(c *cli.Context, opts *options) error {
170172 err := runProvision (m .log , opts )
171173 if err != nil {
172174 // Handle provisioning failure with user interaction
173- return m .handleProvisionFailure (instanceID , opts .cachePath , err )
175+ return m .handleProvisionFailure (instanceID , opts .cacheFile , err )
174176 }
175177 }
176178
177- m .log .Info ("\n Created instance %s" , instanceID )
179+ // Show helpful success message with connection instructions
180+ m .showSuccessMessage (instanceID , opts )
178181 return nil
179182}
180183
181- func (m * command ) handleProvisionFailure (instanceID , cachePath string , provisionErr error ) error {
184+ func (m * command ) showSuccessMessage (instanceID string , opts * options ) {
185+ m .log .Info ("\n ✅ Successfully created instance: %s\n " , instanceID )
186+
187+ // Get public DNS name for AWS instances
188+ var publicDnsName string
189+ if opts .cfg .Spec .Provider == v1alpha1 .ProviderAWS {
190+ for _ , p := range opts .cache .Status .Properties {
191+ if p .Name == aws .PublicDnsName {
192+ publicDnsName = p .Value
193+ break
194+ }
195+ }
196+ } else if opts .cfg .Spec .Provider == v1alpha1 .ProviderSSH {
197+ publicDnsName = opts .cfg .Spec .HostUrl
198+ }
199+
200+ // Show SSH connection instructions if we have a public DNS name
201+ if publicDnsName != "" && opts .cfg .Spec .Username != "" && opts .cfg .Spec .PrivateKey != "" {
202+ m .log .Info ("📋 SSH Connection:" )
203+ m .log .Info (" ssh -i %s %s@%s" , opts .cfg .Spec .PrivateKey , opts .cfg .Spec .Username , publicDnsName )
204+ m .log .Info (" (If you get permission denied, run: chmod 600 %s)\n " , opts .cfg .Spec .PrivateKey )
205+ }
206+
207+ // Show kubeconfig instructions if Kubernetes was installed
208+ switch {
209+ case opts .cfg .Spec .Kubernetes .Install && opts .provision && opts .kubeconfig != "" :
210+ // Only show kubeconfig instructions if provisioning was done and kubeconfig was requested
211+ absPath , err := filepath .Abs (opts .kubeconfig )
212+ if err != nil {
213+ absPath = opts .kubeconfig
214+ }
215+
216+ // Check if the kubeconfig file actually exists
217+ if _ , err := os .Stat (absPath ); err == nil {
218+ m .log .Info ("📋 Kubernetes Access:" )
219+ m .log .Info (" Kubeconfig saved to: %s\n " , absPath )
220+ m .log .Info (" Option 1 - Copy to default location:" )
221+ m .log .Info (" cp %s ~/.kube/config\n " , absPath )
222+ m .log .Info (" Option 2 - Set KUBECONFIG environment variable:" )
223+ m .log .Info (" export KUBECONFIG=%s\n " , absPath )
224+ m .log .Info (" Option 3 - Use with kubectl directly:" )
225+ m .log .Info (" kubectl --kubeconfig=%s get nodes\n " , absPath )
226+ }
227+ case opts .cfg .Spec .Kubernetes .Install && opts .provision && (opts .cfg .Spec .Kubernetes .KubernetesInstaller == "microk8s" || opts .cfg .Spec .Kubernetes .KubernetesInstaller == "kind" ):
228+ m .log .Info ("📋 Kubernetes Access:" )
229+ m .log .Info (" Note: For %s, access kubeconfig on the instance after SSH\n " , opts .cfg .Spec .Kubernetes .KubernetesInstaller )
230+ case opts .cfg .Spec .Kubernetes .Install && ! opts .provision :
231+ m .log .Info ("📋 Kubernetes Access:" )
232+ m .log .Info (" Note: Run with --provision flag to install Kubernetes and download kubeconfig\n " )
233+ }
234+
235+ // Show next steps
236+ m .log .Info ("📋 Next Steps:" )
237+ m .log .Info (" - List instances: holodeck list" )
238+ m .log .Info (" - Get instance status: holodeck status %s\n " , instanceID )
239+ m .log .Info (" - Delete instance: holodeck delete %s" , instanceID )
240+ }
241+
242+ func (m * command ) handleProvisionFailure (instanceID , cacheFile string , provisionErr error ) error {
182243 m .log .Info ("\n ❌ Provisioning failed: %v\n " , provisionErr )
183244
184245 // Check if we're in a non-interactive environment
@@ -204,7 +265,9 @@ func (m *command) handleProvisionFailure(instanceID, cachePath string, provision
204265
205266 if response == "y" || response == "yes" {
206267 // Delete the instance
207- manager := instances .NewManager (m .log , cachePath )
268+ // Extract the directory path from the cache file path
269+ cacheDir := filepath .Dir (cacheFile )
270+ manager := instances .NewManager (m .log , cacheDir )
208271 if err := manager .DeleteInstance (instanceID ); err != nil {
209272 m .log .Info ("Failed to delete instance: %v" , err )
210273 return m .provideCleanupInstructions (instanceID , provisionErr )
@@ -275,7 +338,7 @@ func runProvision(log *logger.FunLogger, opts *options) error {
275338 if err != nil {
276339 return fmt .Errorf ("failed to marshal environment: %v" , err )
277340 }
278- if err := os .WriteFile (opts .cachePath , data , 0600 ); err != nil {
341+ if err := os .WriteFile (opts .cacheFile , data , 0600 ); err != nil {
279342 return fmt .Errorf ("failed to update cache file with provisioning status: %v" , err )
280343 }
281344 return fmt .Errorf ("failed to run provisioner: %v" , err )
@@ -287,7 +350,7 @@ func runProvision(log *logger.FunLogger, opts *options) error {
287350 if err != nil {
288351 return fmt .Errorf ("failed to marshal environment: %v" , err )
289352 }
290- if err := os .WriteFile (opts .cachePath , data , 0600 ); err != nil {
353+ if err := os .WriteFile (opts .cacheFile , data , 0600 ); err != nil {
291354 return fmt .Errorf ("failed to update cache file with provisioning status: %v" , err )
292355 }
293356
0 commit comments