diff --git a/cmd/playground/list.go b/cmd/playground/list.go index c2c0768..89c1901 100644 --- a/cmd/playground/list.go +++ b/cmd/playground/list.go @@ -107,7 +107,7 @@ func (p *listPrinter) printOne(play *api.Play) { } var link string - if play.Active { + if play.Active || play.TutorialName+play.ChallengeName+play.CourseName != "" { link = play.PageURL } diff --git a/cmd/playground/start.go b/cmd/playground/start.go index eda1980..1618e86 100644 --- a/cmd/playground/start.go +++ b/cmd/playground/start.go @@ -7,6 +7,7 @@ import ( "github.com/skratchdot/open-golang/open" "github.com/spf13/cobra" + "github.com/iximiuz/labctl/cmd/ssh" "github.com/iximiuz/labctl/internal/api" "github.com/iximiuz/labctl/internal/labcli" ) @@ -14,7 +15,11 @@ import ( type startOptions struct { playground string - open bool + open bool + + ssh bool + machine string + quiet bool } @@ -42,6 +47,18 @@ func newStartCommand(cli labcli.CLI) *cobra.Command { false, `Open the playground page in a browser`, ) + flags.BoolVar( + &opts.ssh, + "ssh", + false, + `SSH into the playground immediately after it's created`, + ) + flags.StringVar( + &opts.machine, + "machine", + "", + `SSH into the machine with the given name (requires --ssh flag, default to the first machine)`, + ) flags.BoolVarP( &opts.quiet, "quiet", @@ -61,10 +78,28 @@ func runStartPlayground(ctx context.Context, cli labcli.CLI, opts *startOptions) return fmt.Errorf("couldn't create a new playground: %w", err) } - cli.PrintAux("Opening %s in your browser...\n", play.PageURL) + cli.PrintAux("Playground %q has been created.\n", play.ID) + + if opts.open { + cli.PrintAux("Opening %s in your browser...\n", play.PageURL) + + if err := open.Run(play.PageURL); err != nil { + cli.PrintAux("Couldn't open the browser. Copy the above URL into a browser manually to access the playground.\n") + } + } + + if opts.ssh { + if opts.machine == "" { + opts.machine = play.Machines[0].Name + } else { + if play.GetMachine(opts.machine) == nil { + return fmt.Errorf("machine %q not found in the playground", opts.machine) + } + } + + cli.PrintAux("SSH-ing into %s machine...\n", opts.machine) - if err := open.Run(play.PageURL); err != nil { - cli.PrintAux("Couldn't open the browser. Copy the above URL into a browser manually to access the playground.\n") + return ssh.RunSSHSession(ctx, cli, play.ID, opts.machine, nil) } cli.PrintOut("%s\n", play.ID) diff --git a/cmd/ssh/ssh.go b/cmd/ssh/ssh.go index fe420ea..1830291 100644 --- a/cmd/ssh/ssh.go +++ b/cmd/ssh/ssh.go @@ -75,9 +75,19 @@ func runSSHSession(ctx context.Context, cli labcli.CLI, opts *options) error { } } + return RunSSHSession(ctx, cli, opts.playID, opts.machine, opts.command) +} + +func RunSSHSession( + ctx context.Context, + cli labcli.CLI, + playID string, + machine string, + command []string, +) error { tunnel, err := portforward.StartTunnel(ctx, cli.Client(), portforward.TunnelOptions{ - PlayID: opts.playID, - Machine: opts.machine, + PlayID: playID, + Machine: machine, PlaysDir: cli.Config().PlaysDir, SSHDir: cli.Config().SSHDir, }) @@ -133,7 +143,7 @@ func runSSHSession(ctx context.Context, cli labcli.CLI, opts *options) error { return fmt.Errorf("couldn't create SSH session: %w", err) } - if err := sess.Run(ctx, cli, strings.Join(opts.command, " ")); err != nil { + if err := sess.Run(ctx, cli, strings.Join(command, " ")); err != nil { return fmt.Errorf("SSH session error: %w", err) } diff --git a/internal/api/plays.go b/internal/api/plays.go index 19c918d..8bffe2d 100644 --- a/internal/api/plays.go +++ b/internal/api/plays.go @@ -15,11 +15,40 @@ type Play struct { Playground Playground `json:"playground" yaml:"playground"` - PageURL string `json:"pageUrl" yaml:"pageUrl"` - Active bool `json:"active" yaml:"active"` - Running bool `json:"running" yaml:"running"` - Destroyed bool `json:"destroyed" yaml:"destroyed"` - Failed bool `json:"failed" yaml:"failed"` + TutorialName string `json:"tutorialName,omitempty" yaml:"tutorialName"` + + Tutorial *struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + } `json:"tutorial,omitempty" yaml:"tutorial"` + + ChallengeName string `json:"challengeName,omitempty" yaml:"challengeName"` + + Challenge *struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + } `json:"challenge,omitempty" yaml:"challenge"` + + CourseName string `json:"courseName,omitempty" yaml:"courseName"` + + Course *struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + } `json:"course,omitempty" yaml:"course"` + + LessonPath string `json:"lessonPath,omitempty" yaml:"lessonPath"` + + Lesson *struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + } `json:"lesson,omitempty" yaml:"lesson"` + + PageURL string `json:"pageUrl" yaml:"pageUrl"` + + Active bool `json:"active" yaml:"active"` + Running bool `json:"running" yaml:"running"` + Destroyed bool `json:"destroyed" yaml:"destroyed"` + Failed bool `json:"failed" yaml:"failed"` Machines []Machine `json:"machines" yaml:"machines"` }