Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(cli): add --id parameter to templates init command #7116

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

Merged
merged 2 commits into from
Apr 13, 2023
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
97 changes: 72 additions & 25 deletions cli/templateinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ package cli

import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"

"golang.org/x/exp/maps"
"golang.org/x/xerrors"

"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/cliui"
Expand All @@ -14,38 +20,60 @@ import (
)

func (*RootCmd) templateInit() *clibase.Cmd {
return &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 {
exampleList, err := examples.List()
if err != nil {
return err
}
exampleNames := []string{}
exampleByName := map[string]codersdk.TemplateExample{}
for _, example := range exampleList {
name := fmt.Sprintf(
"%s\n%s\n%s\n",
cliui.Styles.Bold.Render(example.Name),
cliui.Styles.Wrap.Copy().PaddingLeft(6).Render(example.Description),
cliui.Styles.Keyword.Copy().PaddingLeft(6).Render(example.URL),
)
exampleNames = append(exampleNames, name)
exampleByName[name] = example
// 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.Styles.Bold.Render(example.Name),
cliui.Styles.Wrap.Copy().PaddingLeft(6).Render(example.Description),
cliui.Styles.Keyword.Copy().PaddingLeft(6).Render(example.URL),
)
optsToID[name] = example.ID
}
opts := maps.Keys(optsToID)
sort.Strings(opts)
_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Wrap.Render(
"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]
}

_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Wrap.Render(
"A template defines infrastructure as code to be provisioned "+
"for individual developer workspaces. Select an example to be copied to the active directory:\n"))
option, err := cliui.Select(inv, cliui.SelectOptions{
Options: exampleNames,
})
if err != nil {
return err
selectedTemplate, ok := templateByID(templateID, exampleList)
if !ok {
// clibase.EnumOf would normally handle this.
return xerrors.Errorf("template not found: %q", templateID)
}
selectedTemplate := exampleByName[option]
archive, err := examples.Archive(selectedTemplate.ID)
if err != nil {
return err
Expand Down Expand Up @@ -81,4 +109,23 @@ func (*RootCmd) templateInit() *clibase.Cmd {
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
}
23 changes: 23 additions & 0 deletions cli/templateinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,27 @@ func TestTemplateInit(t *testing.T) {
require.NoError(t, err)
require.Greater(t, len(files), 0)
})

t.Run("ExtractSpecific", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
inv, _ := clitest.New(t, "templates", "init", "--id", "docker", tempDir)
ptytest.New(t).Attach(inv)
clitest.Run(t, inv)
files, err := os.ReadDir(tempDir)
require.NoError(t, err)
require.Greater(t, len(files), 0)
})

t.Run("NotFound", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
inv, _ := clitest.New(t, "templates", "init", "--id", "thistemplatedoesnotexist", tempDir)
ptytest.New(t).Attach(inv)
err := inv.Run()
require.ErrorContains(t, err, "invalid choice: thistemplatedoesnotexist, should be one of")
files, err := os.ReadDir(tempDir)
require.NoError(t, err)
require.Empty(t, files)
})
}
6 changes: 5 additions & 1 deletion cli/testdata/coder_templates_init_--help.golden
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Usage: coder templates init [directory]
Usage: coder templates init [flags] [directory]

Get started with a templated template.

Options
--id aws-ecs-container|aws-linux|aws-windows|azure-linux|do-linux|docker|docker-with-dotfiles|fly-docker-image|gcp-linux|gcp-vm-container|gcp-windows|kubernetes
Specify a given example template by ID.

---
Run `coder --help` for a list of global options.
12 changes: 11 additions & 1 deletion docs/cli/templates_init.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,15 @@ Get started with a templated template.
## Usage

```console
coder templates init [directory]
coder templates init [flags] [directory]
```

## Options

### --id

| | |
| ---- | ---------------------------- | --------- | ----------- | ----------- | -------- | ------ | -------------------- | ---------------- | --------- | ---------------- | ----------- | ------------------ |
| Type | <code>enum[aws-ecs-container | aws-linux | aws-windows | azure-linux | do-linux | docker | docker-with-dotfiles | fly-docker-image | gcp-linux | gcp-vm-container | gcp-windows | kubernetes]</code> |

Specify a given example template by ID.
2 changes: 1 addition & 1 deletion examples/lima/coder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ provision:
[ ! -e ~/.config/coderv2/session ] && coder login http://localhost:3000 --first-user-username admin --first-user-email [email protected] --first-user-password $(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c12 | tee ${HOME}/.config/coderv2/password)
# Create an initial template
temp_template_dir=$(mktemp -d)
echo code-server | coder templates init "${temp_template_dir}"
coder templates init --id docker "${temp_template_dir}"
DOCKER_ARCH="amd64"
if [ "$(arch)" = "aarch64" ]; then
DOCKER_ARCH="arm64"
Expand Down
3 changes: 1 addition & 2 deletions scripts/develop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,14 @@ fatal() {

# If we have docker available and the "docker" template doesn't already
# exist, then let's try to create a template!
example_template="code-server"
template_name="docker"
if docker info >/dev/null 2>&1 && ! "${CODER_DEV_SHIM}" templates versions list "${template_name}" >/dev/null 2>&1; then
# sometimes terraform isn't installed yet when we go to create the
# template
sleep 5

temp_template_dir="$(mktemp -d)"
echo "${example_template}" | "${CODER_DEV_SHIM}" templates init "${temp_template_dir}"
"${CODER_DEV_SHIM}" templates init --id "${template_name}" "${temp_template_dir}"

DOCKER_HOST="$(docker context inspect --format '{{ .Endpoints.docker.Host }}')"
printf 'docker_arch: "%s"\ndocker_host: "%s"\n' "${GOARCH}" "${DOCKER_HOST}" >"${temp_template_dir}/params.yaml"
Expand Down