Skip to content

[pull] main from coder:main #137

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 3 commits into from
Aug 6, 2025
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
11 changes: 6 additions & 5 deletions cli/cliui/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
// Move the cursor up a single line for nicer display!
_, _ = fmt.Fprint(inv.Stdout, "\033[1A")

var options []string
err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &options)
var defaults []string
err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &defaults)
if err != nil {
return "", err
}

values, err := MultiSelect(inv, MultiSelectOptions{
Options: options,
Defaults: options,
values, err := RichMultiSelect(inv, RichMultiSelectOptions{
Options: templateVersionParameter.Options,
Defaults: defaults,
EnableCustomInput: templateVersionParameter.FormType == "tag-select",
})
if err == nil {
v, err := json.Marshal(&values)
Expand Down
68 changes: 68 additions & 0 deletions cli/cliui/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/signal"
"slices"
"strings"
"syscall"

Expand Down Expand Up @@ -299,6 +300,73 @@ func (m selectModel) filteredOptions() []string {
return options
}

type RichMultiSelectOptions struct {
Message string
Options []codersdk.TemplateVersionParameterOption
Defaults []string
EnableCustomInput bool
}

func RichMultiSelect(inv *serpent.Invocation, richOptions RichMultiSelectOptions) ([]string, error) {
var opts []string
var defaultOpts []string

asLine := func(option codersdk.TemplateVersionParameterOption) string {
line := option.Name
if len(option.Description) > 0 {
line += ": " + option.Description
}
return line
}

var predefinedOpts []string
for i, option := range richOptions.Options {
opts = append(opts, asLine(option)) // Some options may have description defined.

// Check if option is selected by default
if slices.Contains(richOptions.Defaults, option.Value) {
defaultOpts = append(defaultOpts, opts[i])
predefinedOpts = append(predefinedOpts, option.Value)
}
}

// Check if "defaults" contains extra/custom options, user could select them.
for _, def := range richOptions.Defaults {
if !slices.Contains(predefinedOpts, def) {
opts = append(opts, def)
defaultOpts = append(defaultOpts, def)
}
}

selected, err := MultiSelect(inv, MultiSelectOptions{
Message: richOptions.Message,
Options: opts,
Defaults: defaultOpts,
EnableCustomInput: richOptions.EnableCustomInput,
})
if err != nil {
return nil, err
}

// Check selected option, convert descriptions (line) to values
var results []string
for _, sel := range selected {
custom := true
for i, option := range richOptions.Options {
if asLine(option) == sel {
results = append(results, richOptions.Options[i].Value)
custom = false
break
}
}

if custom {
results = append(results, sel)
}
}
return results, nil
}

type MultiSelectOptions struct {
Message string
Options []string
Expand Down
157 changes: 103 additions & 54 deletions cli/cliui/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,8 @@ func TestRichSelect(t *testing.T) {
go func() {
resp, err := newRichSelect(ptty, cliui.RichSelectOptions{
Options: []codersdk.TemplateVersionParameterOption{
{
Name: "A-Name",
Value: "A-Value",
Description: "A-Description.",
}, {
Name: "B-Name",
Value: "B-Value",
Description: "B-Description.",
},
{Name: "A-Name", Value: "A-Value", Description: "A-Description."},
{Name: "B-Name", Value: "B-Value", Description: "B-Description."},
},
})
assert.NoError(t, err)
Expand All @@ -86,63 +79,119 @@ func newRichSelect(ptty *ptytest.PTY, opts cliui.RichSelectOptions) (string, err
return value, inv.Run()
}

func TestMultiSelect(t *testing.T) {
func TestRichMultiSelect(t *testing.T) {
t.Parallel()
t.Run("MultiSelect", func(t *testing.T) {
items := []string{"aaa", "bbb", "ccc"}

t.Parallel()
ptty := ptytest.New(t)
msgChan := make(chan []string)
go func() {
resp, err := newMultiSelect(ptty, items)
assert.NoError(t, err)
msgChan <- resp
}()
require.Equal(t, items, <-msgChan)
})
tests := []struct {
name string
options []codersdk.TemplateVersionParameterOption
defaults []string
allowCustom bool
want []string
}{
{
name: "Predefined",
options: []codersdk.TemplateVersionParameterOption{
{Name: "AAA", Description: "This is AAA", Value: "aaa"},
{Name: "BBB", Description: "This is BBB", Value: "bbb"},
{Name: "CCC", Description: "This is CCC", Value: "ccc"},
},
defaults: []string{"bbb", "ccc"},
allowCustom: false,
want: []string{"bbb", "ccc"},
},
{
name: "Custom",
options: []codersdk.TemplateVersionParameterOption{
{Name: "AAA", Description: "This is AAA", Value: "aaa"},
{Name: "BBB", Description: "This is BBB", Value: "bbb"},
{Name: "CCC", Description: "This is CCC", Value: "ccc"},
},
defaults: []string{"aaa", "bbb"},
allowCustom: true,
want: []string{"aaa", "bbb"},
},
}

t.Run("MultiSelectWithCustomInput", func(t *testing.T) {
t.Parallel()
items := []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"}
ptty := ptytest.New(t)
msgChan := make(chan []string)
go func() {
resp, err := newMultiSelectWithCustomInput(ptty, items)
assert.NoError(t, err)
msgChan <- resp
}()
require.Equal(t, items, <-msgChan)
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

func newMultiSelectWithCustomInput(ptty *ptytest.PTY, items []string) ([]string, error) {
var values []string
cmd := &serpent.Command{
Handler: func(inv *serpent.Invocation) error {
selectedItems, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Options: items,
Defaults: items,
EnableCustomInput: true,
})
if err == nil {
values = selectedItems
var selectedItems []string
var err error
cmd := &serpent.Command{
Handler: func(inv *serpent.Invocation) error {
selectedItems, err = cliui.RichMultiSelect(inv, cliui.RichMultiSelectOptions{
Options: tt.options,
Defaults: tt.defaults,
EnableCustomInput: tt.allowCustom,
})
return err
},
}
return err

doneChan := make(chan struct{})
go func() {
defer close(doneChan)
err := cmd.Invoke().Run()
assert.NoError(t, err)
}()
<-doneChan

require.Equal(t, tt.want, selectedItems)
})
}
}

func TestMultiSelect(t *testing.T) {
t.Parallel()

tests := []struct {
name string
items []string
allowCustom bool
want []string
}{
{
name: "MultiSelect",
items: []string{"aaa", "bbb", "ccc"},
allowCustom: false,
want: []string{"aaa", "bbb", "ccc"},
},
{
name: "MultiSelectWithCustomInput",
items: []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"},
allowCustom: true,
want: []string{"Code", "Chairs", "Whale", "Diamond", "Carrot"},
},
}
inv := cmd.Invoke()
ptty.Attach(inv)
return values, inv.Run()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ptty := ptytest.New(t)
msgChan := make(chan []string)

go func() {
resp, err := newMultiSelect(ptty, tt.items, tt.allowCustom)
assert.NoError(t, err)
msgChan <- resp
}()

require.Equal(t, tt.want, <-msgChan)
})
}
}

func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
func newMultiSelect(pty *ptytest.PTY, items []string, custom bool) ([]string, error) {
var values []string
cmd := &serpent.Command{
Handler: func(inv *serpent.Invocation) error {
selectedItems, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Options: items,
Defaults: items,
Options: items,
Defaults: items,
EnableCustomInput: custom,
})
if err == nil {
values = selectedItems
Expand All @@ -151,6 +200,6 @@ func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
},
}
inv := cmd.Invoke()
ptty.Attach(inv)
pty.Attach(inv)
return values, inv.Run()
}
14 changes: 14 additions & 0 deletions cli/exp_prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ func (RootCmd) promptExample() *serpent.Command {
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(multiSelectValues, ", "))
return multiSelectError
}, useThingsOption, enableCustomInputOption),
promptCmd("rich-multi-select", func(inv *serpent.Invocation) error {
if len(multiSelectValues) == 0 {
multiSelectValues, multiSelectError = cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Message: "Select some things:",
Options: []string{
"Apples", "Plums", "Grapes", "Oranges", "Bananas",
},
Defaults: []string{"Grapes", "Plums"},
EnableCustomInput: enableCustomInput,
})
}
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(multiSelectValues, ", "))
return multiSelectError
}, useThingsOption, enableCustomInputOption),
promptCmd("rich-parameter", func(inv *serpent.Invocation) error {
value, err := cliui.RichSelect(inv, cliui.RichSelectOptions{
Options: []codersdk.TemplateVersionParameterOption{
Expand Down
15 changes: 7 additions & 8 deletions docs/admin/templates/extending-templates/dynamic-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ They allow you to set resource guardrails by referencing Coder identity in the `

## How to enable Dynamic Parameters

In Coder v2.24.0, you can opt-in to Dynamic Parameters on a per-template basis.
In Coder v2.25.0, Dynamic Parameters are automatically enabled for new templates. You can opt-in to Dynamic Parameters for individual existing templates via template settings.

1. Go to your template's settings and enable the **Enable dynamic parameters for workspace creation** option.

![Enable dynamic parameters for workspace creation](../../../images/admin/templates/extend-templates/dyn-params/enable-dynamic-parameters.png)
![Enable dynamic parameters for workspace creation](../../../images/admin/templates/extend-templates/dyn-params/dynamic-parameters-ga-settings.png)

1. Update your template to use version >=2.4.0 of the Coder provider with the following Terraform block.

Expand Down Expand Up @@ -784,9 +784,9 @@ data "coder_parameter" "your_groups" {

## Troubleshooting

Dynamic Parameters is still in Beta as we continue to polish and improve the workflow.
Dynamic Parameters is now in general availability. We're tracking a list of known issues [here in Github](https://github.com/coder/coder/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aparameters) as we continue to polish and improve the workflow.
If you have any issues during upgrade, please file an issue in our
[GitHub repository](https://github.com/coder/coder/issues/new?labels=parameters) and include a
[GitHub repository](https://github.com/coder/coder/issues/new?labels=parameters) with the `parameters` label and include a
[Playground link](https://playground.coder.app/parameters) where applicable.
We appreciate the feedback and look forward to what the community creates with this system!

Expand All @@ -798,7 +798,7 @@ You can share anything you build with Dynamic Parameters in our [Discord](https:

Ensure that the following version requirements are met:

- `coder/coder`: >= [v2.24.0](https://github.com/coder/coder/releases/tag/v2.24.0)
- `coder/coder`: >= [v2.25.0](https://github.com/coder/coder/releases/tag/v2.25.0)
- `coder/terraform-provider-coder`: >= [v2.5.3](https://github.com/coder/terraform-provider-coder/releases/tag/v2.5.3)

Enabling Dynamic Parameters on an existing template requires administrators to publish a new template version.
Expand All @@ -818,10 +818,9 @@ To revert Dynamic Parameters on a template:

### Template variables not showing up

In beta, template variables are not supported in Dynamic Parameters.
Dynamic Parameters is GA as of [v2.25.0](https://github.com/coder/coder/releases/tag/v2.25.0), and this issue has been resolved. In beta ([v2.24.0](https://github.com/coder/coder/releases/tag/v2.24.0)), template variables were not supported in Dynamic Parameters.

This issue will be resolved by the next minor release of `coder/coder`.
If this is issue is blocking your usage of Dynamic Parameters, please let us know in [this thread](https://github.com/coder/coder/issues/18671).
If you are experiencing issues with template variables, try upgrading to the latest version with dynamic parameters in GA. Otherwise, please file an issue in our Github.

### Can I use registry modules with Dynamic Parameters?

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,7 @@
{
"title": "Dynamic Parameters",
"description": "Conditional, identity-aware parameter syntax for advanced users.",
"path": "./admin/templates/extending-templates/dynamic-parameters.md",
"state": ["beta"]
"path": "./admin/templates/extending-templates/dynamic-parameters.md"
},
{
"title": "Prebuilt workspaces",
Expand Down
1 change: 1 addition & 0 deletions site/src/pages/TemplatesPage/TemplatesFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const TemplatesFilter: FC<TemplatesFilterProps> = ({
<Filter
presets={[
{ query: "", name: "All templates" },
{ query: "deprecated:false author:me", name: "Templates you authored" },
{ query: "deprecated:true", name: "Deprecated templates" },
]}
// TODO: Add docs for this
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy