Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sbx): unify app commands and support template compatibility #426

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 47 additions & 88 deletions cmd/lk/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var (
Name: "create",
Usage: "Bootstrap a new application from a template or through guided creation",
Before: requireProject,
Action: setupBootstrapTemplate,
Action: setupTemplate,
ArgsUsage: "`APP_NAME`",
Flags: []cli.Flag{
&cli.StringFlag{
Expand All @@ -61,25 +61,10 @@ var (
Usage: "`URL` to instantiate, must contain a taskfile.yaml",
Destination: &templateURL,
},
&cli.BoolFlag{
Name: "install",
Usage: "Run installation tasks after creating the app",
Hidden: true,
},
},
},
{
Name: "sandbox",
Usage: "Bootstrap a sandbox application created on your cloud dashboard",
Before: requireProject,
Action: setupSandboxTemplate,
ArgsUsage: "`APP_NAME`",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "`ID` of the sandbox, see your cloud dashboard",
Name: "sandbox",
Usage: "`NAME` of the sandbox, see your cloud dashboard",
Destination: &sandboxID,
Required: true,
},
&cli.StringFlag{
Name: "server-url",
Expand All @@ -95,6 +80,7 @@ var (
},
},
{
Hidden: true,
Name: "install",
Usage: "Execute installation defined in " + bootstrap.TaskFile,
ArgsUsage: "[DIR] location of the project directory (default: current directory)",
Expand Down Expand Up @@ -170,14 +156,42 @@ func requireProject(ctx context.Context, cmd *cli.Command) error {
return err
}

func setupBootstrapTemplate(ctx context.Context, cmd *cli.Command) error {
func setupTemplate(ctx context.Context, cmd *cli.Command) error {
verbose := cmd.Bool("verbose")
install := cmd.Bool("install")
isSandbox := sandboxID != ""

var preinstallPrompts []huh.Field
var templateOptions []bootstrap.Template

if templateName != "" && templateURL != "" {
return errors.New("only one of template or template-url can be specified")
}

if isSandbox {
token, err := requireToken(ctx, cmd)
if err != nil {
return err
}
details, err := bootstrap.FetchSandboxDetails(ctx, sandboxID, token, serverURL)
if err != nil {
return err
}
if len(details.ChildTemplates) == 0 {
return errors.New("no child templates found for sandbox")
}
templateOptions = details.ChildTemplates
} else {
var err error
templateOptions, err = bootstrap.FetchTemplates(ctx)
if err != nil {
return err
}
}

appName = cmd.Args().First()
if appName == "" {
appName = sandboxID
preinstallPrompts = append(preinstallPrompts, huh.NewInput().
Title("Application Name").
Placeholder("my-app").
Expand All @@ -197,35 +211,21 @@ func setupBootstrapTemplate(ctx context.Context, cmd *cli.Command) error {
WithTheme(theme))
}

if templateName != "" && templateURL != "" {
return errors.New("only one of template or template-url can be specified")
}

// if no template name or URL is specified, fetch template index and prompt user
// if no template name or URL is specified, prompt user to choose from available templates
if templateName == "" && templateURL == "" {
templates, err := bootstrap.FetchTemplates(ctx)
if err != nil {
return err
}
templateSelect := huh.NewSelect[string]().
Title("Select Template").
Value(&templateURL).
WithTheme(theme)
var options []huh.Option[string]
for _, t := range templates {
if t.IsSandbox {
options = append(options, huh.NewOption(t.Name, t.URL))
}
for _, t := range templateOptions {
options = append(options, huh.NewOption(t.Name, t.URL))
}
templateSelect.(*huh.Select[string]).Options(options...)
preinstallPrompts = append(preinstallPrompts, templateSelect)
// if templateName is specified, fetch template index and find it
// if templateName is specified, locate it in the list of templates
} else if templateName != "" {
templates, err := bootstrap.FetchTemplates(ctx)
if err != nil {
return err
}
for _, t := range templates {
for _, t := range templateOptions {
if t.Name == templateName {
template = &t
templateURL = t.URL
Expand All @@ -252,53 +252,16 @@ func setupBootstrapTemplate(ctx context.Context, cmd *cli.Command) error {
}

fmt.Println("Instantiating environment...")
if err := instantiateEnv(ctx, cmd, appName, nil); err != nil {
addlEnv := &map[string]string{"LIVEKIT_SANDBOX_ID": sandboxID}
if err := instantiateEnv(ctx, cmd, appName, addlEnv); err != nil {
return err
}

if install {
fmt.Println("Installing template...")
return doInstall(ctx, bootstrap.TaskInstall, appName, verbose)
} else {
return doPostCreate(ctx, cmd, appName, false)
}
}

func setupSandboxTemplate(ctx context.Context, cmd *cli.Command) error {
verbose := cmd.Bool("verbose")
install := cmd.Bool("install")

if sandboxID == "" {
return errors.New("sandbox ID is required")
}

token, err := requireToken(ctx, cmd)
if err != nil {
return err
}

details, err := bootstrap.FetchSandboxDetails(ctx, sandboxID, token, serverURL)
if err != nil {
return err
}
template = &details.Template

fmt.Println("Cloning template...")
if err := cloneTemplate(ctx, cmd, template.URL, details.Name); err != nil {
return err
}

fmt.Println("Instantiating environment...")
addlEnv := &map[string]string{"LIVEKIT_SANDBOX_ID": details.Name}
if err := instantiateEnv(ctx, cmd, details.Name, addlEnv); err != nil {
return err
}

if install {
fmt.Println("Installing template...")
return doInstall(ctx, bootstrap.TaskInstallSandbox, details.Name, verbose)
} else {
return doPostCreate(ctx, cmd, details.Name, false)
return doPostCreate(ctx, cmd, appName, verbose)
}
}

Expand Down Expand Up @@ -358,24 +321,20 @@ func installTemplate(ctx context.Context, cmd *cli.Command) error {
return doInstall(ctx, bootstrap.TaskInstall, rootPath, verbose)
}

func doPostCreate(ctx context.Context, _ *cli.Command, rootPath string, isBootstrap bool) error {
func doPostCreate(ctx context.Context, _ *cli.Command, rootPath string, verbose bool) error {
tf, err := bootstrap.ParseTaskfile(rootPath)
if err != nil {
return err
}

taskName := string(bootstrap.TaskPostCreateSandbox)
if isBootstrap {
taskName = string(bootstrap.TaskPostCreate)
task, err := bootstrap.NewTask(ctx, tf, rootPath, string(bootstrap.TaskPostCreate), verbose)
if task == nil || err != nil {
return nil
}

task, err := bootstrap.NewTask(ctx, tf, rootPath, taskName, true)
if err != nil {
return err
}
var cmdErr error
if err := spinner.New().
Title("Running task " + taskName + "...").
Title("Cleaning up...").
Action(func() { cmdErr = task() }).
Style(theme.Focused.Title).
Accessible(true).
Expand All @@ -401,7 +360,7 @@ func doInstall(ctx context.Context, task bootstrap.KnownTask, rootPath string, v
Title("Installing...").
Action(func() { cmdErr = install() }).
Style(theme.Focused.Title).
Accessible(verbose).
Accessible(true).
Run(); err != nil {
return err
}
Expand Down
23 changes: 10 additions & 13 deletions pkg/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,9 @@ const (
type KnownTask string

const (
TaskPostCreate KnownTask = "post_create"
TaskPostCreateSandbox KnownTask = "post_create_sandbox"
TaskInstall KnownTask = "install"
TaskInstallSandbox KnownTask = "install_sandbox"
TaskDev KnownTask = "dev"
TaskDevSandbox KnownTask = "dev_sandbox"
TaskPostCreate KnownTask = "post_create"
TaskInstall KnownTask = "install"
TaskDev KnownTask = "dev"
)

type Template struct {
Expand All @@ -68,12 +65,14 @@ type Template struct {
Docs string `yaml:"docs" json:"docs_url,omitempty"`
Image string `yaml:"image" json:"image_ref,omitempty"`
Tags []string `yaml:"tags" json:"tags,omitempty"`
Requires []string `yaml:"requires" json:"requires,omitempty"`
IsSandbox bool `yaml:"is_sandbox" json:"is_sandbox,omitempty"`
}

type SandboxDetails struct {
Name string `json:"name"`
Template Template `json:"template"`
Name string `json:"name"`
Template Template `json:"template"`
ChildTemplates []Template `json:"childTemplates"`
}

func FetchTemplates(ctx context.Context) ([]Template, error) {
Expand Down Expand Up @@ -127,11 +126,8 @@ func ParseTaskfile(rootPath string) (*ast.Taskfile, error) {
}

func NewTaskExecutor(dir string, verbose bool) *task.Executor {
var o io.Writer = io.Discard
var o io.Writer = os.Stdout
var e io.Writer = os.Stderr
if verbose {
o = os.Stdout
}
return &task.Executor{
Dir: dir,
Force: false,
Expand Down Expand Up @@ -162,7 +158,8 @@ func NewTask(ctx context.Context, tf *ast.Taskfile, dir, taskName string, verbos
}

task := &ast.Call{
Task: taskName,
Task: taskName,
Silent: !verbose,
}
if _, err := exe.GetTask(task); err != nil {
return nil, err
Expand Down
Loading