Skip to content

feat(cli): support ephemeral parameters #8415

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 14 commits into from
Jul 13, 2023
5 changes: 5 additions & 0 deletions cli/cliui/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
label = templateVersionParameter.DisplayName
}

if templateVersionParameter.Ephemeral {
label += DefaultStyles.Warn.Render(" (build option)")
}

_, _ = fmt.Fprintln(inv.Stdout, DefaultStyles.Bold.Render(label))

if templateVersionParameter.DescriptionPlaintext != "" {
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
}
Expand Down
11 changes: 10 additions & 1 deletion cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ func (r *RootCmd) create() *clibase.Cmd {
startAt string
stopAfter time.Duration
workspaceName string

parameterFlags workspaceParameterFlags
)
client := new(codersdk.Client)
cmd := &clibase.Cmd{
Expand Down Expand Up @@ -123,6 +125,7 @@ func (r *RootCmd) create() *clibase.Cmd {
Template: template,
RichParameterFile: richParameterFile,
NewWorkspaceName: workspaceName,
BuildOptions: parameterFlags.buildOptions,
})
if err != nil {
return xerrors.Errorf("prepare build: %w", err)
Expand Down Expand Up @@ -191,6 +194,7 @@ func (r *RootCmd) create() *clibase.Cmd {
},
cliui.SkipPromptOption(),
)
cmd.Options = append(cmd.Options, parameterFlags.options()...)

return cmd
}
Expand All @@ -202,6 +206,7 @@ type prepWorkspaceBuildArgs struct {
NewWorkspaceName string

UpdateWorkspace bool
BuildOptions bool
WorkspaceID uuid.UUID
}

Expand Down Expand Up @@ -240,13 +245,17 @@ func prepWorkspaceBuild(inv *clibase.Invocation, client *codersdk.Client, args p
richParameters := make([]codersdk.WorkspaceBuildParameter, 0)
PromptRichParamLoop:
for _, templateVersionParameter := range templateVersionParameters {
if !args.BuildOptions && templateVersionParameter.Ephemeral {
continue
}

if !disclaimerPrinted {
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Paragraph.Render("This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
disclaimerPrinted = true
}

// Param file is all or nothing
if !useParamFile {
if !useParamFile && !templateVersionParameter.Ephemeral {
for _, e := range args.ExistingRichParams {
if e.Name == templateVersionParameter.Name {
// If the param already exists, we do not need to prompt it again.
Expand Down
61 changes: 60 additions & 1 deletion cli/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,13 @@ func TestCreateWithRichParameters(t *testing.T) {
secondParameterDescription = "This is second parameter"
secondParameterValue = "2"

ephemeralParameterName = "ephemeral_parameter"
ephemeralParameterDescription = "This is ephemeral parameter"
ephemeralParameterValue = "3"

immutableParameterName = "third_parameter"
immutableParameterDescription = "This is not mutable parameter"
immutableParameterValue = "3"
immutableParameterValue = "4"
)

echoResponses := &echo.Responses{
Expand All @@ -209,6 +213,7 @@ func TestCreateWithRichParameters(t *testing.T) {
Parameters: []*proto.RichParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
{Name: secondParameterName, DisplayName: secondParameterDisplayName, Description: secondParameterDescription, Mutable: true},
{Name: ephemeralParameterName, Description: ephemeralParameterDescription, Mutable: true, Ephemeral: true},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
},
},
Expand Down Expand Up @@ -300,6 +305,60 @@ func TestCreateWithRichParameters(t *testing.T) {
}
<-doneChan
})

t.Run("BuildOptions", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

const workspaceName = "my-workspace"
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "--build-options")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()

matches := []string{
ephemeralParameterDescription, ephemeralParameterValue,
firstParameterDescription, firstParameterValue,
secondParameterDisplayName, "",
secondParameterDescription, secondParameterValue,
immutableParameterDescription, immutableParameterValue,
"Confirm create?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)

if value != "" {
pty.WriteLine(value)
}
}
<-doneChan

// Verify if build option is set
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

workspace, err := client.WorkspaceByOwnerAndName(ctx, user.UserID.String(), workspaceName, codersdk.WorkspaceOptions{})
require.NoError(t, err)
actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID)
require.NoError(t, err)
require.Contains(t, actualParameters, codersdk.WorkspaceBuildParameter{
Name: ephemeralParameterName,
Value: ephemeralParameterValue,
})
})
}

func TestCreateValidateRichParameters(t *testing.T) {
Expand Down
30 changes: 22 additions & 8 deletions cli/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
)

func (r *RootCmd) restart() *clibase.Cmd {
var parameterFlags workspaceParameterFlags

client := new(codersdk.Client)
cmd := &clibase.Cmd{
Annotations: workspaceCommand,
Expand All @@ -19,22 +21,33 @@ func (r *RootCmd) restart() *clibase.Cmd {
clibase.RequireNArgs(1),
r.InitClient(client),
),
Options: clibase.OptionSet{
cliui.SkipPromptOption(),
},
Options: append(parameterFlags.options(), cliui.SkipPromptOption()),
Handler: func(inv *clibase.Invocation) error {
ctx := inv.Context()
out := inv.Stdout

_, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Confirm restart workspace?",
IsConfirm: true,
workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
if err != nil {
return err
}

template, err := client.Template(inv.Context(), workspace.TemplateID)
if err != nil {
return err
}

buildParams, err := prepStartWorkspace(inv, client, prepStartWorkspaceArgs{
Template: template,
BuildOptions: parameterFlags.buildOptions,
})
if err != nil {
return err
}

workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
_, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: "Confirm restart workspace?",
IsConfirm: true,
})
if err != nil {
return err
}
Expand All @@ -51,7 +64,8 @@ func (r *RootCmd) restart() *clibase.Cmd {
}

build, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionStart,
Transition: codersdk.WorkspaceTransitionStart,
RichParameterValues: buildParams.richParameters,
})
if err != nil {
return err
Expand Down
83 changes: 83 additions & 0 deletions cli/restart_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
package cli_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)

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

echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: []*proto.RichParameter{
{
Name: ephemeralParameterName,
Description: ephemeralParameterDescription,
Mutable: true,
Ephemeral: true,
},
},
},
},
},
},
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
}

t.Run("OK", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -43,4 +73,57 @@ func TestRestart(t *testing.T) {
err := <-done
require.NoError(t, err, "execute failed")
})

t.Run("BuildOptions", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)

inv, root := clitest.New(t, "restart", workspace.Name, "--build-options")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()

matches := []string{
ephemeralParameterDescription, ephemeralParameterValue,
"Confirm restart workspace?", "yes",
"Stopping workspace", "",
"Starting workspace", "",
"workspace has been restarted", "",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)

if value != "" {
pty.WriteLine(value)
}
}
<-doneChan

// Verify if build option is set
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

workspace, err := client.WorkspaceByOwnerAndName(ctx, user.UserID.String(), workspace.Name, codersdk.WorkspaceOptions{})
require.NoError(t, err)
actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID)
require.NoError(t, err)
require.Contains(t, actualParameters, codersdk.WorkspaceBuildParameter{
Name: ephemeralParameterName,
Value: ephemeralParameterValue,
})
})
}
Loading