Files
coder/cli/templateinit.go
Ammar Bandukwala dd97fe2bce chore(cli): replace lipgloss with coder/pretty (#9564)
This change will improve over CLI performance and "snappiness" as well as
substantially reduce our test times. Preliminary benchmarks show
`coder server --help` times cut from 300ms to 120ms on my dogfood
instance.

The inefficiency of lipgloss disproportionately impacts our system, as all help
text for every command is generated whenever any command is invoked.

The `pretty` API could clean up a lot of the code (e.g., by replacing
complex string concatenations with Printf), but this commit is too
expansive as is so that work will be done in a follow up.
2023-09-07 16:28:22 -05:00

142 lines
3.9 KiB
Go

package cli
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/examples"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/pretty"
)
func (*RootCmd) templateInit() *clibase.Cmd {
var templateID string
exampleList, err := examples.List()
if err != nil {
// This should not happen. If it does, something is very wrong.
panic(err)
}
var templateIDs []string
for _, ex := range exampleList {
templateIDs = append(templateIDs, ex.ID)
}
sort.Strings(templateIDs)
cmd := &clibase.Cmd{
Use: "init [directory]",
Short: "Get started with a templated template.",
Middleware: clibase.RequireRangeArgs(0, 1),
Handler: func(inv *clibase.Invocation) error {
// If the user didn't specify any template, prompt them to select one.
if templateID == "" {
optsToID := map[string]string{}
for _, example := range exampleList {
name := fmt.Sprintf(
"%s\n%s\n%s\n",
cliui.Bold(example.Name),
pretty.Sprint(cliui.DefaultStyles.Wrap.With(pretty.XPad(6, 0)), example.Description),
pretty.Sprint(cliui.DefaultStyles.Keyword.With(pretty.XPad(6, 0)), example.URL),
)
optsToID[name] = example.ID
}
opts := maps.Keys(optsToID)
sort.Strings(opts)
_, _ = fmt.Fprintln(
inv.Stdout,
pretty.Sprint(
cliui.DefaultStyles.Wrap,
"A template defines infrastructure as code to be provisioned "+
"for individual developer workspaces. Select an example to be copied to the active directory:\n"),
)
selected, err := cliui.Select(inv, cliui.SelectOptions{
Options: opts,
})
if err != nil {
if errors.Is(err, io.EOF) {
return xerrors.Errorf(
"Couldn't find a matching template!\n" +
"Tip: if you're trying to automate template creation, try\n" +
"coder templates init --id <template_id> instead!",
)
}
return err
}
templateID = optsToID[selected]
}
selectedTemplate, ok := templateByID(templateID, exampleList)
if !ok {
// clibase.EnumOf would normally handle this.
return xerrors.Errorf("template not found: %q", templateID)
}
archive, err := examples.Archive(selectedTemplate.ID)
if err != nil {
return err
}
workingDir, err := os.Getwd()
if err != nil {
return err
}
var directory string
if len(inv.Args) > 0 {
directory = inv.Args[0]
} else {
directory = filepath.Join(workingDir, selectedTemplate.ID)
}
relPath, err := filepath.Rel(workingDir, directory)
if err != nil {
relPath = directory
} else {
relPath = "./" + relPath
}
_, _ = fmt.Fprintf(inv.Stdout, "Extracting %s to %s...\n", pretty.Sprint(cliui.DefaultStyles.Field, selectedTemplate.ID), relPath)
err = os.MkdirAll(directory, 0o700)
if err != nil {
return err
}
err = provisionersdk.Untar(directory, bytes.NewReader(archive))
if err != nil {
return err
}
_, _ = fmt.Fprintln(inv.Stdout, "Create your template by running:")
_, _ = fmt.Fprintln(
inv.Stdout,
pretty.Sprint(
cliui.DefaultStyles.Code,
"cd "+relPath+" && coder templates create"),
)
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "\nExamples provide a starting point and are expected to be edited! 🎨"))
return nil
},
}
cmd.Options = clibase.OptionSet{
{
Flag: "id",
Description: "Specify a given example template by ID.",
Value: clibase.EnumOf(&templateID, templateIDs...),
},
}
return cmd
}
func templateByID(templateID string, tes []codersdk.TemplateExample) (codersdk.TemplateExample, bool) {
for _, te := range tes {
if te.ID == templateID {
return te, true
}
}
return codersdk.TemplateExample{}, false
}