mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
This can cause bad refresh token errors, since it can only be used once. Looks like there was an attempt to fix this by checking the database after a failed refresh, but of course this depends on the first request having updated the database in time, so both that and this fix are required to fully solve.
2144 lines
80 KiB
Go
2144 lines
80 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sync/singleflight"
|
|
|
|
"github.com/coder/coder/v2/cli"
|
|
"github.com/coder/coder/v2/cli/clitest"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/externalauth"
|
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/provisioner/echo"
|
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/coder/v2/testutil/expecter"
|
|
)
|
|
|
|
func TestCreateDynamic(t *testing.T) {
|
|
t.Parallel()
|
|
owner := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
first := coderdtest.CreateFirstUser(t, owner)
|
|
member, _ := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
|
|
|
|
// Terraform template with conditional parameters.
|
|
// The "region" parameter only appears when "enable_region" is true.
|
|
const conditionalParamTF = `
|
|
terraform {
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
}
|
|
}
|
|
}
|
|
data "coder_workspace_owner" "me" {}
|
|
data "coder_parameter" "enable_region" {
|
|
name = "enable_region"
|
|
order = 1
|
|
type = "bool"
|
|
default = "false"
|
|
}
|
|
data "coder_parameter" "region" {
|
|
name = "region"
|
|
count = data.coder_parameter.enable_region.value == "true" ? 1 : 0
|
|
order = 2
|
|
type = "string"
|
|
# No default - this makes it required when it appears
|
|
}
|
|
`
|
|
|
|
// Test conditional parameters: a parameter that only appears when another
|
|
// parameter has a certain value.
|
|
t.Run("ConditionalParam", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
template, _ := coderdtest.DynamicParameterTemplate(t, owner, first.OrganizationID, coderdtest.DynamicParameterTemplateParams{
|
|
MainTF: conditionalParamTF,
|
|
})
|
|
|
|
// Test 1: Create without enabling region - region param should not exist
|
|
args := []string{
|
|
"create", "ws-no-region",
|
|
"--template", template.Name,
|
|
"--parameter", "enable_region=false",
|
|
"-y",
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
|
|
doneChan := make(chan error)
|
|
go func() {
|
|
doneChan <- inv.Run()
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "has been created")
|
|
err := testutil.RequireReceive(ctx, t, doneChan)
|
|
require.NoError(t, err)
|
|
|
|
// Verify workspace created with only enable_region parameter
|
|
ws, err := member.WorkspaceByOwnerAndName(t.Context(), codersdk.Me, "ws-no-region", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
buildParams, err := member.WorkspaceBuildParameters(t.Context(), ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParams, 1, "expected only enable_region parameter when enable_region=false")
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "enable_region", Value: "false"})
|
|
|
|
// Test 2: Create with region enabled - region param should exist
|
|
args = []string{
|
|
"create", "ws-with-region",
|
|
"--template", template.Name,
|
|
"--parameter", "enable_region=true",
|
|
"--parameter", "region=us-east",
|
|
"-y",
|
|
}
|
|
inv, root = clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout = expecter.NewAttachedToInvocation(t, inv)
|
|
|
|
doneChan = make(chan error)
|
|
go func() {
|
|
doneChan <- inv.Run()
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "has been created")
|
|
|
|
err = testutil.RequireReceive(ctx, t, doneChan)
|
|
require.NoError(t, err)
|
|
|
|
// Verify workspace created with both parameters
|
|
ws, err = member.WorkspaceByOwnerAndName(t.Context(), codersdk.Me, "ws-with-region", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
buildParams, err = member.WorkspaceBuildParameters(t.Context(), ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParams, 2, "expected both enable_region and region parameters when enable_region=true")
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "enable_region", Value: "true"})
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "region", Value: "us-east"})
|
|
})
|
|
|
|
// Test that the CLI prompts for missing conditional parameters.
|
|
// When enable_region=true, the region parameter becomes required and CLI should prompt.
|
|
t.Run("PromptForConditionalParam", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, time.Hour)
|
|
logger := testutil.Logger(t)
|
|
|
|
template, _ := coderdtest.DynamicParameterTemplate(t, owner, first.OrganizationID, coderdtest.DynamicParameterTemplateParams{
|
|
MainTF: conditionalParamTF,
|
|
})
|
|
|
|
// Only provide enable_region=true, don't provide region - CLI should prompt for it
|
|
args := []string{
|
|
"create", "ws-prompted",
|
|
"--template", template.Name,
|
|
"--parameter", "enable_region=true",
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
|
|
doneChan := make(chan error)
|
|
go func() {
|
|
doneChan <- inv.Run()
|
|
}()
|
|
|
|
// CLI should prompt for the region parameter since enable_region=true
|
|
stdout.ExpectMatch(ctx, "region")
|
|
stdin.WriteLine("eu-west")
|
|
|
|
// Confirm creation
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
|
|
stdout.ExpectMatch(ctx, "has been created")
|
|
|
|
err := <-doneChan
|
|
require.NoError(t, err)
|
|
|
|
// Verify workspace created with both parameters
|
|
ws, err := member.WorkspaceByOwnerAndName(t.Context(), codersdk.Me, "ws-prompted", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
buildParams, err := member.WorkspaceBuildParameters(t.Context(), ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParams, 2, "expected both enable_region and region parameters")
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "enable_region", Value: "true"})
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "region", Value: "eu-west"})
|
|
})
|
|
|
|
// Test that updating a template with a new required parameter causes start to fail
|
|
// when the user doesn't provide the new parameter value.
|
|
t.Run("UpdateTemplateRequiredParamStartFails", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Initial template with just enable_region parameter (no default, so required)
|
|
const initialTF = `
|
|
terraform {
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
}
|
|
}
|
|
}
|
|
data "coder_workspace_owner" "me" {}
|
|
data "coder_parameter" "enable_region" {
|
|
name = "enable_region"
|
|
type = "bool"
|
|
}
|
|
`
|
|
|
|
template, _ := coderdtest.DynamicParameterTemplate(t, owner, first.OrganizationID, coderdtest.DynamicParameterTemplateParams{
|
|
MainTF: initialTF,
|
|
})
|
|
|
|
// Create workspace with initial template
|
|
inv, root := clitest.New(t, "create", "ws-update-test",
|
|
"--template", template.Name,
|
|
"--parameter", "enable_region=false",
|
|
"-y",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
// Stop the workspace
|
|
inv, root = clitest.New(t, "stop", "ws-update-test", "-y")
|
|
clitest.SetupConfig(t, member, root)
|
|
err = inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
const updatedTF = `
|
|
terraform {
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
}
|
|
}
|
|
}
|
|
data "coder_workspace_owner" "me" {}
|
|
data "coder_parameter" "enable_region" {
|
|
name = "enable_region"
|
|
type = "bool"
|
|
}
|
|
data "coder_parameter" "region" {
|
|
count = data.coder_parameter.enable_region.value == "true" ? 1 : 0
|
|
name = "region"
|
|
type = "string"
|
|
# No default - required when enable_region is true
|
|
}
|
|
`
|
|
|
|
coderdtest.DynamicParameterTemplate(t, owner, first.OrganizationID, coderdtest.DynamicParameterTemplateParams{
|
|
MainTF: updatedTF,
|
|
TemplateID: template.ID,
|
|
})
|
|
|
|
// Try to start the workspace with update - should fail because region is now required
|
|
// (enable_region defaults to true, making region appear, but no value provided)
|
|
// and we're using -y to skip prompts
|
|
inv, root = clitest.New(t, "start", "ws-update-test", "-y", "--parameter", "enable_region=true")
|
|
clitest.SetupConfig(t, member, root)
|
|
err = inv.Run()
|
|
require.Error(t, err, "start should fail because new required parameter 'region' is missing")
|
|
require.Contains(t, err.Error(), "region")
|
|
})
|
|
|
|
// Test that dynamic validation allows values that would be invalid with static validation.
|
|
// A slider's max value is determined by another parameter, so a value of 8 is invalid
|
|
// when max_slider=5, but valid when max_slider=10.
|
|
t.Run("DynamicValidation", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
|
|
// Template where slider's max is controlled by another parameter
|
|
const dynamicValidationTF = `
|
|
terraform {
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
}
|
|
}
|
|
}
|
|
data "coder_workspace_owner" "me" {}
|
|
data "coder_parameter" "max_slider" {
|
|
name = "max_slider"
|
|
type = "number"
|
|
default = 5
|
|
}
|
|
data "coder_parameter" "slider" {
|
|
name = "slider"
|
|
type = "number"
|
|
default = 1
|
|
validation {
|
|
min = 1
|
|
max = data.coder_parameter.max_slider.value
|
|
}
|
|
}
|
|
`
|
|
|
|
template, _ := coderdtest.DynamicParameterTemplate(t, owner, first.OrganizationID, coderdtest.DynamicParameterTemplateParams{
|
|
MainTF: dynamicValidationTF,
|
|
})
|
|
|
|
// Test 1: slider=8 should fail when max_slider=5 (default)
|
|
inv, root := clitest.New(t, "create", "ws-validation-fail",
|
|
"--template", template.Name,
|
|
"--parameter", "slider=8",
|
|
"-y",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
err := inv.Run()
|
|
require.Error(t, err, "slider=8 should fail when max_slider=5")
|
|
|
|
// Test 2: slider=8 should succeed when max_slider=10
|
|
inv, root = clitest.New(t, "create", "ws-validation-pass",
|
|
"--template", template.Name,
|
|
"--parameter", "max_slider=10",
|
|
"--parameter", "slider=8",
|
|
"-y",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
|
|
doneChan := make(chan error)
|
|
go func() {
|
|
doneChan <- inv.Run()
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "has been created")
|
|
|
|
err = <-doneChan
|
|
require.NoError(t, err, "slider=8 should succeed when max_slider=10")
|
|
|
|
// Verify workspace created with correct parameters
|
|
ws, err := member.WorkspaceByOwnerAndName(t.Context(), codersdk.Me, "ws-validation-pass", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
buildParams, err := member.WorkspaceBuildParameters(t.Context(), ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "max_slider", Value: "10"})
|
|
require.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "slider", Value: "8"})
|
|
})
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Create", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
args := []string{
|
|
"create",
|
|
"my-workspace",
|
|
"--template", template.Name,
|
|
"--start-at", "9:30AM Mon-Fri US/Central",
|
|
"--stop-after", "8h",
|
|
"--automatic-updates", "always",
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
matches := []struct {
|
|
match string
|
|
write string
|
|
}{
|
|
{match: "compute.main"},
|
|
{match: "smith (linux, i386)"},
|
|
{match: "Confirm create", write: "yes"},
|
|
}
|
|
for _, m := range matches {
|
|
stdout.ExpectMatch(ctx, m.match)
|
|
if len(m.write) > 0 {
|
|
stdin.WriteLine(m.write)
|
|
}
|
|
}
|
|
<-doneChan
|
|
|
|
ws, err := member.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
if assert.NoError(t, err, "expected workspace to be created") {
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
if assert.NotNil(t, ws.AutostartSchedule) {
|
|
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
|
|
}
|
|
if assert.NotNil(t, ws.TTLMillis) {
|
|
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
|
|
}
|
|
assert.Equal(t, codersdk.AutomaticUpdatesAlways, ws.AutomaticUpdates)
|
|
}
|
|
})
|
|
|
|
t.Run("CreateForOtherUser", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
_, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
args := []string{
|
|
"create",
|
|
user.Username + "/their-workspace",
|
|
"--template", template.Name,
|
|
"--start-at", "9:30AM Mon-Fri US/Central",
|
|
"--stop-after", "8h",
|
|
}
|
|
|
|
inv, root := clitest.New(t, args...)
|
|
//nolint:gocritic // Creating a workspace for another user requires owner permissions.
|
|
clitest.SetupConfig(t, client, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
matches := []struct {
|
|
match string
|
|
write string
|
|
}{
|
|
{match: "compute.main"},
|
|
{match: "smith (linux, i386)"},
|
|
{match: "Confirm create", write: "yes"},
|
|
}
|
|
for _, m := range matches {
|
|
stdout.ExpectMatch(ctx, m.match)
|
|
if len(m.write) > 0 {
|
|
stdin.WriteLine(m.write)
|
|
}
|
|
}
|
|
<-doneChan
|
|
|
|
ws, err := client.WorkspaceByOwnerAndName(context.Background(), user.Username, "their-workspace", codersdk.WorkspaceOptions{})
|
|
if assert.NoError(t, err, "expected workspace to be created") {
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
if assert.NotNil(t, ws.AutostartSchedule) {
|
|
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
|
|
}
|
|
if assert.NotNil(t, ws.TTLMillis) {
|
|
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("CreateWithSpecificTemplateVersion", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent(), func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
|
ctvr.Name = "v1"
|
|
})
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// Create a new version
|
|
version2 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent(), func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
|
ctvr.Name = "v2"
|
|
ctvr.TemplateID = template.ID
|
|
})
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
|
|
|
|
args := []string{
|
|
"create",
|
|
"my-workspace",
|
|
"--template", template.Name,
|
|
"--template-version", version2.Name,
|
|
"--start-at", "9:30AM Mon-Fri US/Central",
|
|
"--stop-after", "8h",
|
|
"--automatic-updates", "always",
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
matches := []struct {
|
|
match string
|
|
write string
|
|
}{
|
|
{match: "compute.main"},
|
|
{match: "smith (linux, i386)"},
|
|
{match: "Confirm create", write: "yes"},
|
|
}
|
|
for _, m := range matches {
|
|
stdout.ExpectMatch(ctx, m.match)
|
|
if len(m.write) > 0 {
|
|
stdin.WriteLine(m.write)
|
|
}
|
|
}
|
|
<-doneChan
|
|
|
|
ws, err := member.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
if assert.NoError(t, err, "expected workspace to be created") {
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
// Check if the workspace is using the new template version
|
|
assert.Equal(t, ws.LatestBuild.TemplateVersionID, version2.ID, "expected workspace to use the specified template version")
|
|
if assert.NotNil(t, ws.AutostartSchedule) {
|
|
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
|
|
}
|
|
if assert.NotNil(t, ws.TTLMillis) {
|
|
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
|
|
}
|
|
assert.Equal(t, codersdk.AutomaticUpdatesAlways, ws.AutomaticUpdates)
|
|
}
|
|
})
|
|
|
|
t.Run("InheritStopAfterFromTemplate", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
var defaultTTLMillis int64 = 2 * 60 * 60 * 1000 // 2 hours
|
|
ctr.DefaultTTLMillis = &defaultTTLMillis
|
|
})
|
|
args := []string{
|
|
"create",
|
|
"my-workspace",
|
|
"--template", template.Name,
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
waiter := clitest.StartWithWaiter(t, inv)
|
|
matches := []struct {
|
|
match string
|
|
write string
|
|
}{
|
|
{match: "compute.main"},
|
|
{match: "smith (linux, i386)"},
|
|
{match: "Confirm create", write: "yes"},
|
|
}
|
|
for _, m := range matches {
|
|
stdout.ExpectMatch(ctx, m.match)
|
|
if len(m.write) > 0 {
|
|
stdin.WriteLine(m.write)
|
|
}
|
|
}
|
|
waiter.RequireSuccess()
|
|
|
|
ws, err := member.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err, "expected workspace to be created")
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
assert.Equal(t, *ws.TTLMillis, template.DefaultTTLMillis)
|
|
})
|
|
|
|
t.Run("CreateFromListWithSkip", 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, nil)
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
inv, root := clitest.New(t, "create", "my-workspace", "-y")
|
|
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
|
|
clitest.SetupConfig(t, member, root)
|
|
cmdCtx, done := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
go func() {
|
|
defer done()
|
|
err := inv.WithContext(cmdCtx).Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
// No pty interaction needed since we use the -y skip prompt flag
|
|
<-cmdCtx.Done()
|
|
require.ErrorIs(t, cmdCtx.Err(), context.Canceled)
|
|
})
|
|
|
|
t.Run("FromNothing", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
inv, root := clitest.New(t, "create", "")
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
matches := []string{
|
|
"Specify a name", "my-workspace",
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
stdin.WriteLine(value)
|
|
}
|
|
<-doneChan
|
|
|
|
ws, err := member.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
if assert.NoError(t, err, "expected workspace to be created") {
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil")
|
|
}
|
|
})
|
|
|
|
t.Run("NoWait", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
inv, root := clitest.New(t, "create", "my-workspace",
|
|
"--template", template.Name,
|
|
"-y",
|
|
"--no-wait",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "building in the background")
|
|
_ = testutil.TryReceive(ctx, t, doneChan)
|
|
|
|
// Verify workspace was actually created.
|
|
ws, err := member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
})
|
|
|
|
t.Run("NoWaitWithParameterDefaults", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses([]*proto.RichParameter{
|
|
{Name: "region", Type: "string", DefaultValue: "us-east-1"},
|
|
{Name: "instance_type", Type: "string", DefaultValue: "t3.micro"},
|
|
}))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
inv, root := clitest.New(t, "create", "my-workspace",
|
|
"--template", template.Name,
|
|
"-y",
|
|
"--use-parameter-defaults",
|
|
"--no-wait",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "building in the background")
|
|
_ = testutil.TryReceive(ctx, t, doneChan)
|
|
|
|
// Verify workspace was created and parameters were applied.
|
|
ws, err := member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ws.TemplateName, template.Name)
|
|
|
|
buildParams, err := member.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "region", Value: "us-east-1"})
|
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "instance_type", Value: "t3.micro"})
|
|
})
|
|
|
|
// Verifies that --use-parameter-defaults accepts empty-string
|
|
// defaults without prompting. Uses the classic parameter flow
|
|
// because the echo provisioner sets Required via proto fields,
|
|
// which the dynamic parameter evaluator does not read.
|
|
t.Run("EmptyStringDefaultNoPrompt", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses([]*proto.RichParameter{
|
|
{Name: "region", Type: "string", DefaultValue: "us-east-1"},
|
|
{Name: "optional_field", Type: "string", DefaultValue: ""},
|
|
}))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.UseClassicParameterFlow = ptr.Ref(true)
|
|
})
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
inv, root := clitest.New(t, "create", "my-workspace",
|
|
"--template", template.Name,
|
|
"-y",
|
|
"--use-parameter-defaults",
|
|
"--no-wait",
|
|
)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
stdout.ExpectMatch(ctx, "building in the background")
|
|
_ = testutil.TryReceive(ctx, t, doneChan)
|
|
|
|
ws, err := member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
|
require.NoError(t, err)
|
|
|
|
buildParams, err := member.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "region", Value: "us-east-1"})
|
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "optional_field", Value: ""})
|
|
})
|
|
}
|
|
|
|
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
|
return &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
ProvisionInit: echo.InitComplete,
|
|
ProvisionPlan: echo.PlanComplete,
|
|
ProvisionGraph: []*proto.Response{
|
|
{
|
|
Type: &proto.Response_Graph{
|
|
Graph: &proto.GraphComplete{
|
|
Parameters: parameters,
|
|
Presets: presets,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ProvisionApply: echo.ApplyComplete,
|
|
}
|
|
}
|
|
|
|
type param struct {
|
|
name string
|
|
ptype string
|
|
value string
|
|
mutable bool
|
|
}
|
|
|
|
func TestCreateWithRichParameters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Default parameters and their expected values.
|
|
params := []param{
|
|
{
|
|
name: "number_param",
|
|
ptype: "number",
|
|
value: "777",
|
|
mutable: true,
|
|
},
|
|
{
|
|
name: "string_param",
|
|
ptype: "string",
|
|
value: "qux",
|
|
mutable: true,
|
|
},
|
|
{
|
|
name: "bool_param",
|
|
// TODO: Setting the type breaks booleans. It claims the default is false
|
|
// but when you then accept this default it errors saying that the value
|
|
// must be true or false. For now, use a string.
|
|
ptype: "string",
|
|
value: "false",
|
|
mutable: true,
|
|
},
|
|
{
|
|
name: "immutable_string_param",
|
|
ptype: "string",
|
|
value: "i am eternal",
|
|
mutable: false,
|
|
},
|
|
}
|
|
|
|
type testContext struct {
|
|
client *codersdk.Client
|
|
member *codersdk.Client
|
|
owner codersdk.CreateFirstUserResponse
|
|
template codersdk.Template
|
|
workspaceName string
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
// setup runs before the command is started and return arguments that will
|
|
// be appended to the create command.
|
|
setup func() []string
|
|
// handlePty optionally runs after the command is started. It should handle
|
|
// all expected prompts from the pty.
|
|
handlePty func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer)
|
|
// postRun runs after the command has finished but before the workspace is
|
|
// verified. It must return the workspace name to check (used for the copy
|
|
// workspace tests).
|
|
postRun func(t *testing.T, args testContext) string
|
|
// errors contains expected errors. The workspace will not be verified if
|
|
// errors are expected.
|
|
errors []string
|
|
// inputParameters overrides the default parameters.
|
|
inputParameters []param
|
|
// expectedParameters defaults to inputParameters.
|
|
expectedParameters []param
|
|
// withDefaults sets DefaultValue to each parameter's value.
|
|
withDefaults bool
|
|
}{
|
|
{
|
|
name: "ValuesFromPrompt",
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Enter the value for each parameter as prompted.
|
|
for _, param := range params {
|
|
stdout.ExpectMatch(ctx, param.name)
|
|
stdin.WriteLine(param.value)
|
|
}
|
|
// Confirm the creation.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromDefaultFlags",
|
|
setup: func() []string {
|
|
// Provide the defaults on the command line.
|
|
args := []string{}
|
|
for _, param := range params {
|
|
args = append(args, "--parameter-default", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
return args
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Simply accept the defaults.
|
|
for _, param := range params {
|
|
stdout.ExpectMatch(ctx, param.name)
|
|
stdout.ExpectMatch(ctx, `Enter a value (default: "`+param.value+`")`)
|
|
stdin.WriteLine("")
|
|
}
|
|
// Confirm the creation.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromFile",
|
|
setup: func() []string {
|
|
// Create a file with the values.
|
|
tempDir := t.TempDir()
|
|
removeTmpDirUntilSuccessAfterTest(t, tempDir)
|
|
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
|
|
for _, param := range params {
|
|
_, err := parameterFile.WriteString(fmt.Sprintf("%s: %s\n", param.name, param.value))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return []string{"--rich-parameter-file", parameterFile.Name()}
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// No prompts, we only need to confirm.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromFlags",
|
|
setup: func() []string {
|
|
// Provide the values on the command line.
|
|
var args []string
|
|
for _, param := range params {
|
|
args = append(args, "--parameter", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
return args
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// No prompts, we only need to confirm.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
},
|
|
{
|
|
name: "MisspelledParameter",
|
|
setup: func() []string {
|
|
// Provide the values on the command line.
|
|
args := []string{}
|
|
for i, param := range params {
|
|
if i == 0 {
|
|
// Slightly misspell the first parameter with an extra character.
|
|
args = append(args, "--parameter", fmt.Sprintf("n%s=%s", param.name, param.value))
|
|
} else {
|
|
args = append(args, "--parameter", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
}
|
|
return args
|
|
},
|
|
errors: []string{
|
|
"parameter \"n" + params[0].name + "\" is not present in the template",
|
|
"Did you mean: " + params[0].name,
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromWorkspace",
|
|
setup: func() []string {
|
|
// Provide the values on the command line.
|
|
args := []string{"-y"}
|
|
for _, param := range params {
|
|
args = append(args, "--parameter", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
return args
|
|
},
|
|
postRun: func(t *testing.T, tctx testContext) string {
|
|
inv, root := clitest.New(t, "create", "--copy-parameters-from", tctx.workspaceName, "other-workspace", "-y")
|
|
clitest.SetupConfig(t, tctx.member, root)
|
|
err := inv.Run()
|
|
require.NoError(t, err, "failed to create a workspace based on the source workspace")
|
|
return "other-workspace"
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromOutdatedWorkspace",
|
|
setup: func() []string {
|
|
// Provide the values on the command line.
|
|
args := []string{"-y"}
|
|
for _, param := range params {
|
|
args = append(args, "--parameter", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
return args
|
|
},
|
|
postRun: func(t *testing.T, tctx testContext) string {
|
|
// Update the template to a new version.
|
|
version2 := coderdtest.CreateTemplateVersion(t, tctx.client, tctx.owner.OrganizationID, prepareEchoResponses([]*proto.RichParameter{
|
|
{Name: "another_parameter", Type: "string", DefaultValue: "not-relevant"},
|
|
}), func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
|
ctvr.Name = "v2"
|
|
ctvr.TemplateID = tctx.template.ID
|
|
})
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, tctx.client, version2.ID)
|
|
coderdtest.UpdateActiveTemplateVersion(t, tctx.client, tctx.template.ID, version2.ID)
|
|
|
|
// Then create the copy. It should use the old template version.
|
|
inv, root := clitest.New(t, "create", "--copy-parameters-from", tctx.workspaceName, "other-workspace", "-y")
|
|
clitest.SetupConfig(t, tctx.member, root)
|
|
err := inv.Run()
|
|
require.NoError(t, err, "failed to create a workspace based on the source workspace")
|
|
return "other-workspace"
|
|
},
|
|
},
|
|
{
|
|
name: "ValuesFromTemplateDefaults",
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Simply accept the defaults.
|
|
for _, param := range params {
|
|
stdout.ExpectMatch(ctx, param.name)
|
|
stdout.ExpectMatch(ctx, `Enter a value (default: "`+param.value+`")`)
|
|
stdin.WriteLine("")
|
|
}
|
|
// Confirm the creation.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
withDefaults: true,
|
|
},
|
|
{
|
|
name: "ValuesFromTemplateDefaultsNoPrompt",
|
|
setup: func() []string {
|
|
return []string{"--use-parameter-defaults"}
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Default values should get printed.
|
|
for _, param := range params {
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", param.name, param.value))
|
|
}
|
|
// No prompts, we only need to confirm.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
withDefaults: true,
|
|
},
|
|
{
|
|
name: "ValuesFromDefaultFlagsNoPrompt",
|
|
setup: func() []string {
|
|
// Provide the defaults on the command line.
|
|
args := []string{"--use-parameter-defaults"}
|
|
for _, param := range params {
|
|
args = append(args, "--parameter-default", fmt.Sprintf("%s=%s", param.name, param.value))
|
|
}
|
|
return args
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Default values should get printed.
|
|
for _, param := range params {
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", param.name, param.value))
|
|
}
|
|
// No prompts, we only need to confirm.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
},
|
|
{
|
|
// File and flags should override template defaults. Additionally, if a
|
|
// value has no default value we should still get a prompt for it.
|
|
name: "ValuesFromMultipleSources",
|
|
setup: func() []string {
|
|
tempDir := t.TempDir()
|
|
removeTmpDirUntilSuccessAfterTest(t, tempDir)
|
|
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
|
|
_, err := parameterFile.WriteString(`
|
|
file_param: from file
|
|
cli_param: from file`)
|
|
require.NoError(t, err)
|
|
return []string{
|
|
"--use-parameter-defaults",
|
|
"--rich-parameter-file", parameterFile.Name(),
|
|
"--parameter-default", "file_param=from cli default",
|
|
"--parameter-default", "cli_param=from cli default",
|
|
"--parameter", "cli_param=from cli",
|
|
}
|
|
},
|
|
handlePty: func(ctx context.Context, stdout *expecter.Expecter, stdin *testutil.Writer) {
|
|
// Should get prompted for the input param since it has no default.
|
|
stdout.ExpectMatch(ctx, "input_param")
|
|
stdin.WriteLine("from input")
|
|
|
|
// Confirm the creation.
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
},
|
|
withDefaults: true,
|
|
inputParameters: []param{
|
|
{
|
|
name: "template_param",
|
|
value: "from template default",
|
|
},
|
|
{
|
|
name: "file_param",
|
|
value: "from template default",
|
|
},
|
|
{
|
|
name: "cli_param",
|
|
value: "from template default",
|
|
},
|
|
{
|
|
name: "input_param",
|
|
},
|
|
},
|
|
expectedParameters: []param{
|
|
{
|
|
name: "template_param",
|
|
value: "from template default",
|
|
},
|
|
{
|
|
name: "file_param",
|
|
value: "from file",
|
|
},
|
|
{
|
|
name: "cli_param",
|
|
value: "from cli",
|
|
},
|
|
{
|
|
name: "input_param",
|
|
value: "from input",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
parameters := params
|
|
if len(tt.inputParameters) > 0 {
|
|
parameters = tt.inputParameters
|
|
}
|
|
|
|
// Convert parameters for the echo provisioner response.
|
|
var rparams []*proto.RichParameter
|
|
for i, param := range parameters {
|
|
defaultValue := ""
|
|
if tt.withDefaults {
|
|
defaultValue = param.value
|
|
}
|
|
rparams = append(rparams, &proto.RichParameter{
|
|
Name: param.name,
|
|
Type: param.ptype,
|
|
Mutable: param.mutable,
|
|
DefaultValue: defaultValue,
|
|
Order: int32(i), //nolint:gosec
|
|
})
|
|
}
|
|
|
|
// Set up the template.
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(rparams))
|
|
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// Run the command, possibly setting up values.
|
|
workspaceName := "my-workspace"
|
|
args := []string{"create", workspaceName, "--template", template.Name}
|
|
if tt.setup != nil {
|
|
args = append(args, tt.setup()...)
|
|
}
|
|
inv, root := clitest.New(t, args...)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan error)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
doneChan <- inv.Run()
|
|
}()
|
|
|
|
// The test may do something with the pty.
|
|
if tt.handlePty != nil {
|
|
tt.handlePty(ctx, stdout, stdin)
|
|
}
|
|
|
|
// Wait for the command to exit.
|
|
err := <-doneChan
|
|
|
|
// The test may want to run additional setup like copying the workspace.
|
|
if tt.postRun != nil {
|
|
workspaceName = tt.postRun(t, testContext{
|
|
client: client,
|
|
member: member,
|
|
owner: owner,
|
|
template: template,
|
|
workspaceName: workspaceName,
|
|
})
|
|
}
|
|
|
|
if len(tt.errors) > 0 {
|
|
require.Error(t, err)
|
|
for _, errstr := range tt.errors {
|
|
assert.ErrorContains(t, err, errstr)
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
|
|
// Verify the workspace was created and has the right template and values.
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{Name: workspaceName})
|
|
require.NoError(t, err, "expected to find created workspace")
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
if len(tt.expectedParameters) > 0 {
|
|
parameters = tt.expectedParameters
|
|
}
|
|
require.Len(t, buildParameters, len(parameters))
|
|
for _, param := range parameters {
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: param.name, Value: param.value})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateWithPreset(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
firstParameterName = "first_parameter"
|
|
firstParameterDisplayName = "First Parameter"
|
|
firstParameterDescription = "This is the first parameter"
|
|
firstParameterValue = "1"
|
|
|
|
firstOptionalParameterName = "first_optional_parameter"
|
|
firstOptionalParameterDescription = "This is the first optional parameter"
|
|
firstOptionalParameterValue = "1"
|
|
secondOptionalParameterName = "second_optional_parameter"
|
|
secondOptionalParameterDescription = "This is the second optional parameter"
|
|
secondOptionalParameterValue = "2"
|
|
|
|
thirdParameterName = "third_parameter"
|
|
thirdParameterDescription = "This is the third parameter"
|
|
thirdParameterValue = "3"
|
|
)
|
|
|
|
echoResponses := func(presets ...*proto.Preset) *echo.Responses {
|
|
return prepareEchoResponses([]*proto.RichParameter{
|
|
{
|
|
Name: firstParameterName,
|
|
DisplayName: firstParameterDisplayName,
|
|
Description: firstParameterDescription,
|
|
Mutable: true,
|
|
DefaultValue: firstParameterValue,
|
|
Options: []*proto.RichParameterOption{
|
|
{
|
|
Name: firstOptionalParameterName,
|
|
Description: firstOptionalParameterDescription,
|
|
Value: firstOptionalParameterValue,
|
|
},
|
|
{
|
|
Name: secondOptionalParameterName,
|
|
Description: secondOptionalParameterDescription,
|
|
Value: secondOptionalParameterValue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: thirdParameterName,
|
|
Description: thirdParameterDescription,
|
|
DefaultValue: thirdParameterValue,
|
|
Mutable: true,
|
|
},
|
|
}, presets...)
|
|
}
|
|
|
|
// This test verifies that when a template has presets,
|
|
// including a default preset, and the user specifies a `--preset` flag,
|
|
// the CLI uses the specified preset instead of the default
|
|
t.Run("PresetFlag", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version with two presets, including a default
|
|
defaultPreset := proto.Preset{
|
|
Name: "preset-default",
|
|
Default: true,
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&defaultPreset, &preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command with the specified preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y", "--preset", preset.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
// Should: display the selected preset as well as its parameters
|
|
presetName := fmt.Sprintf("Preset '%s' applied:", preset.Name)
|
|
stdout.ExpectMatch(ctx, presetName)
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", firstParameterName, secondOptionalParameterValue))
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", thirdParameterName, thirdParameterValue))
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
defer cancel()
|
|
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 2)
|
|
var selectedPreset *codersdk.Preset
|
|
for _, tvPreset := range tvPresets {
|
|
if tvPreset.Name == preset.Name {
|
|
selectedPreset = &tvPreset
|
|
}
|
|
}
|
|
require.NotNil(t, selectedPreset)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and the preset-defined parameters
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, selectedPreset.ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when a template has presets,
|
|
// including a default preset, and the user does not specify the `--preset` flag,
|
|
// the CLI automatically uses the default preset to create the workspace
|
|
t.Run("DefaultPreset", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version with two presets, including a default
|
|
defaultPreset := proto.Preset{
|
|
Name: "preset-default",
|
|
Default: true,
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&defaultPreset, &preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command without a preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y")
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
// Should: display the default preset as well as its parameters
|
|
presetName := fmt.Sprintf("Preset '%s' (default) applied:", defaultPreset.Name)
|
|
stdout.ExpectMatch(ctx, presetName)
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", firstParameterName, secondOptionalParameterValue))
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", thirdParameterName, thirdParameterValue))
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 2)
|
|
var selectedPreset *codersdk.Preset
|
|
for _, tvPreset := range tvPresets {
|
|
if tvPreset.Default {
|
|
selectedPreset = &tvPreset
|
|
}
|
|
}
|
|
require.NotNil(t, selectedPreset)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and the default preset parameters
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, selectedPreset.ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when a template has presets but no default preset,
|
|
// and the user does not provide the `--preset` flag,
|
|
// the CLI prompts the user to select a preset.
|
|
t.Run("NoDefaultPresetPromptUser", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version with a single, non-default preset.
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Description: "Preset Test.",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command without specifying a preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name,
|
|
"--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstOptionalParameterValue),
|
|
"--parameter", fmt.Sprintf("%s=%s", thirdParameterName, thirdParameterValue))
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
// Should: prompt the user for the preset
|
|
stdout.ExpectMatch(ctx, "Select a preset below:")
|
|
// We don't actually have to respond to the selector, since we hardcode the cliui.Select to return the
|
|
// first option in test scenarios (c.f. cliui/select.go)
|
|
stdout.ExpectMatch(ctx, "Preset 'preset-test' applied")
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
|
|
<-doneChan
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 1)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and the preset-defined parameters
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, tvPresets[0].ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when a template version has no presets,
|
|
// the CLI does not prompt the user to select a preset and proceeds
|
|
// with workspace creation without applying any preset.
|
|
t.Run("TemplateVersionWithoutPresets", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version without presets
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses())
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command without a preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y",
|
|
"--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstOptionalParameterValue),
|
|
"--parameter", fmt.Sprintf("%s=%s", thirdParameterName, thirdParameterValue))
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
stdout.ExpectMatch(ctx, "No preset applied.")
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and no preset
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Nil(t, workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when the user provides `--preset none`,
|
|
// the CLI skips applying any preset, even if the template version has a default preset.
|
|
// The workspace should be created without using any preset-defined parameters.
|
|
t.Run("PresetFlagNone", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version with a default preset
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Default: true,
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command with flag '--preset none'
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y", "--preset", cli.PresetNone,
|
|
"--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstOptionalParameterValue),
|
|
"--parameter", fmt.Sprintf("%s=%s", thirdParameterName, thirdParameterValue))
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
stdout.ExpectMatch(ctx, "No preset applied.")
|
|
|
|
// Verify that the new workspace doesn't use the preset parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 1)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and no preset
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Nil(t, workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that the CLI returns an appropriate error
|
|
// when a user provides a `--preset` value that does not correspond
|
|
// to any existing preset in the template version.
|
|
t.Run("FailsWhenPresetDoesNotExist", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template and a template version where the preset defines values for all required parameters
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
{Name: thirdParameterName, Value: thirdParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command with a non-existent preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y", "--preset", "invalid-preset")
|
|
clitest.SetupConfig(t, member, root)
|
|
err := inv.Run()
|
|
|
|
// Should: fail with an error indicating the preset was not found
|
|
require.Contains(t, err.Error(), "preset \"invalid-preset\" not found")
|
|
})
|
|
|
|
// This test verifies that when both a preset and a user-provided
|
|
// `--parameter` flag define a value for the same parameter,
|
|
// the preset's value takes precedence over the user's.
|
|
//
|
|
// The preset defines one parameter (A), and two `--parameter` flags provide A and B.
|
|
// The workspace should be created using:
|
|
// - the value of parameter A from the preset (overriding the parameter flag's value),
|
|
// - and the value of parameter B from the parameter flag.
|
|
t.Run("PresetOverridesParameterFlagValues", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template version with a preset that defines one parameter
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: creating a workspace with a preset and passing overlapping and additional parameters via `--parameter`
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y",
|
|
"--preset", preset.Name,
|
|
"--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstOptionalParameterValue),
|
|
"--parameter", fmt.Sprintf("%s=%s", thirdParameterName, thirdParameterValue))
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
// Should: display the selected preset as well as its parameter
|
|
presetName := fmt.Sprintf("Preset '%s' applied:", preset.Name)
|
|
stdout.ExpectMatch(ctx, presetName)
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", firstParameterName, secondOptionalParameterValue))
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 1)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: include both parameters, one from the preset and one from the `--parameter` flag
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, tvPresets[0].ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when both a preset and a user-provided
|
|
// `--rich-parameter-file` define a value for the same parameter,
|
|
// the preset's value takes precedence over the one in the file.
|
|
//
|
|
// The preset defines one parameter (A), and the parameter file provides two parameters (A and B).
|
|
// The workspace should be created using:
|
|
// - the value of parameter A from the preset (overriding the file's value),
|
|
// - and the value of parameter B from the file.
|
|
t.Run("PresetOverridesParameterFileValues", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template version with a preset that defines one parameter
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: creating a workspace with the preset and passing the second required parameter via `--rich-parameter-file`
|
|
workspaceName := "my-workspace"
|
|
tempDir := t.TempDir()
|
|
removeTmpDirUntilSuccessAfterTest(t, tempDir)
|
|
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
|
|
_, _ = parameterFile.WriteString(
|
|
firstParameterName + ": " + firstOptionalParameterValue + "\n" +
|
|
thirdParameterName + ": " + thirdParameterValue)
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "-y",
|
|
"--preset", preset.Name,
|
|
"--rich-parameter-file", parameterFile.Name())
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
err := inv.Run()
|
|
require.NoError(t, err)
|
|
|
|
// Should: display the selected preset as well as its parameter
|
|
presetName := fmt.Sprintf("Preset '%s' applied:", preset.Name)
|
|
stdout.ExpectMatch(ctx, presetName)
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", firstParameterName, secondOptionalParameterValue))
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 1)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: include both parameters, one from the preset and one from the `--rich-parameter-file` flag
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, tvPresets[0].ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
|
|
// This test verifies that when a preset provides only some parameters,
|
|
// and the remaining ones are not provided via flags,
|
|
// the CLI prompts the user for input to fill in the missing parameters.
|
|
t.Run("PromptsForMissingParametersWhenPresetIsIncomplete", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
logger := testutil.Logger(t)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
|
|
// Given: a template version with a preset that defines one parameter
|
|
preset := proto.Preset{
|
|
Name: "preset-test",
|
|
Parameters: []*proto.PresetParameter{
|
|
{Name: firstParameterName, Value: secondOptionalParameterValue},
|
|
},
|
|
}
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses(&preset))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// When: running the create command with the specified preset
|
|
workspaceName := "my-workspace"
|
|
inv, root := clitest.New(t, "create", workspaceName, "--template", template.Name, "--preset", preset.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
// Should: display the selected preset as well as its parameters
|
|
presetName := fmt.Sprintf("Preset '%s' applied:", preset.Name)
|
|
stdout.ExpectMatch(ctx, presetName)
|
|
stdout.ExpectMatch(ctx, fmt.Sprintf("%s: '%s'", firstParameterName, secondOptionalParameterValue))
|
|
|
|
// Should: prompt for the missing parameter
|
|
stdout.ExpectMatch(ctx, thirdParameterDescription)
|
|
stdin.WriteLine(thirdParameterValue)
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
|
|
<-doneChan
|
|
|
|
// Verify if the new workspace uses expected parameters.
|
|
tvPresets, err := client.TemplateVersionPresets(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, tvPresets, 1)
|
|
|
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
Name: workspaceName,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces.Workspaces, 1)
|
|
|
|
// Should: create a workspace using the expected template version and the preset-defined parameters
|
|
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
|
|
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
|
|
require.Equal(t, tvPresets[0].ID, *workspaceLatestBuild.TemplateVersionPresetID)
|
|
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, buildParameters, 2)
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: secondOptionalParameterValue})
|
|
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: thirdParameterName, Value: thirdParameterValue})
|
|
})
|
|
}
|
|
|
|
func TestCreateValidateRichParameters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
stringParameterName = "string_parameter"
|
|
stringParameterValue = "abc"
|
|
|
|
listOfStringsParameterName = "list_of_strings_parameter"
|
|
|
|
numberParameterName = "number_parameter"
|
|
numberParameterValue = "7"
|
|
|
|
boolParameterName = "bool_parameter"
|
|
boolParameterValue = "true"
|
|
)
|
|
|
|
numberRichParameters := []*proto.RichParameter{
|
|
{Name: numberParameterName, Type: "number", Mutable: true, ValidationMin: ptr.Ref(int32(3)), ValidationMax: ptr.Ref(int32(10))},
|
|
}
|
|
|
|
numberCustomErrorRichParameters := []*proto.RichParameter{
|
|
{
|
|
Name: numberParameterName, Type: "number", Mutable: true,
|
|
ValidationMin: ptr.Ref(int32(3)), ValidationMax: ptr.Ref(int32(10)),
|
|
ValidationError: "These are values: {min}, {max}, and {value}.",
|
|
},
|
|
}
|
|
|
|
stringRichParameters := []*proto.RichParameter{
|
|
{Name: stringParameterName, Type: "string", Mutable: true, ValidationRegex: "^[a-z]+$", ValidationError: "this is error"},
|
|
}
|
|
|
|
listOfStringsRichParameters := []*proto.RichParameter{
|
|
{Name: listOfStringsParameterName, Type: "list(string)", Mutable: true, DefaultValue: `["aaa","bbb","ccc"]`},
|
|
}
|
|
|
|
boolRichParameters := []*proto.RichParameter{
|
|
{Name: boolParameterName, Type: "bool", Mutable: true},
|
|
}
|
|
|
|
t.Run("ValidateString", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(stringRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
matches := []string{
|
|
stringParameterName, "$$",
|
|
"does not match", "",
|
|
"Enter a value", "abc",
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
if value != "" {
|
|
stdin.WriteLine(value)
|
|
}
|
|
}
|
|
<-doneChan
|
|
})
|
|
|
|
t.Run("ValidateNumber", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(numberRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
matches := []string{
|
|
numberParameterName, "12",
|
|
"is more than the maximum", "",
|
|
"Enter a value", "8",
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
if value != "" {
|
|
stdin.WriteLine(value)
|
|
}
|
|
}
|
|
<-doneChan
|
|
})
|
|
|
|
t.Run("ValidateNumber_CustomError", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(numberCustomErrorRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
matches := []string{
|
|
numberParameterName, "12",
|
|
"These are values: 3, 10, and 12.", "",
|
|
"Enter a value", "8",
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
if value != "" {
|
|
stdin.WriteLine(value)
|
|
}
|
|
}
|
|
<-doneChan
|
|
})
|
|
|
|
t.Run("ValidateBool", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(boolRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
doneChan := make(chan struct{})
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
go func() {
|
|
defer close(doneChan)
|
|
err := inv.Run()
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
matches := []string{
|
|
boolParameterName, "cat",
|
|
"boolean value can be either", "",
|
|
"Enter a value", "true",
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
if value != "" {
|
|
stdin.WriteLine(value)
|
|
}
|
|
}
|
|
<-doneChan
|
|
})
|
|
|
|
t.Run("ValidateListOfStrings", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(listOfStringsRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
t.Run("Prompt", func(t *testing.T) {
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
inv, root := clitest.New(t, "create", "my-workspace-1", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
clitest.Start(t, inv)
|
|
|
|
stdout.ExpectMatch(ctx, listOfStringsParameterName)
|
|
stdout.ExpectMatch(ctx, "aaa, bbb, ccc")
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
})
|
|
|
|
t.Run("Default", func(t *testing.T) {
|
|
t.Parallel()
|
|
inv, root := clitest.New(t, "create", "my-workspace-2", "--template", template.Name, "--yes")
|
|
clitest.SetupConfig(t, member, root)
|
|
clitest.Run(t, inv)
|
|
})
|
|
|
|
t.Run("CLIOverride/DoubleQuote", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Note: see https://go.dev/play/p/vhTUTZsVrEb for how to escape this properly
|
|
parameterArg := fmt.Sprintf(`"%s=[""ddd=foo"",""eee=bar"",""fff=baz""]"`, listOfStringsParameterName)
|
|
inv, root := clitest.New(t, "create", "my-workspace-3", "--template", template.Name, "--parameter", parameterArg, "--yes")
|
|
clitest.SetupConfig(t, member, root)
|
|
clitest.Run(t, inv)
|
|
})
|
|
})
|
|
|
|
t.Run("ValidateListOfStrings_YAMLFile", func(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(listOfStringsRichParameters))
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
tempDir := t.TempDir()
|
|
removeTmpDirUntilSuccessAfterTest(t, tempDir)
|
|
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
|
|
_, _ = parameterFile.WriteString(listOfStringsParameterName + `:
|
|
- ddd
|
|
- eee
|
|
- fff`)
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name, "--rich-parameter-file", parameterFile.Name())
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
clitest.Start(t, inv)
|
|
|
|
matches := []string{
|
|
"Confirm create?", "yes",
|
|
}
|
|
for i := 0; i < len(matches); i += 2 {
|
|
match := matches[i]
|
|
value := matches[i+1]
|
|
stdout.ExpectMatch(ctx, match)
|
|
if value != "" {
|
|
stdin.WriteLine(value)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCreateWithGitAuth(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t)
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
echoResponses := &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
ProvisionInit: echo.InitComplete,
|
|
ProvisionPlan: echo.PlanComplete,
|
|
ProvisionGraph: []*proto.Response{
|
|
{
|
|
Type: &proto.Response_Graph{
|
|
Graph: &proto.GraphComplete{
|
|
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ProvisionApply: echo.ApplyComplete,
|
|
}
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
ExternalAuthConfigs: []*externalauth.Config{{
|
|
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
|
|
ID: "github",
|
|
Regex: regexp.MustCompile(`github\.com`),
|
|
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
|
|
DisplayName: "GitHub",
|
|
RefreshGroup: new(singleflight.Group),
|
|
}},
|
|
IncludeProvisionerDaemon: true,
|
|
})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses)
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
|
|
clitest.SetupConfig(t, member, root)
|
|
stdout := expecter.NewAttachedToInvocation(t, inv)
|
|
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
|
|
clitest.Start(t, inv)
|
|
|
|
stdout.ExpectMatch(ctx, "You must authenticate with GitHub to create a workspace")
|
|
resp := coderdtest.RequestExternalAuthCallback(t, "github", member)
|
|
_ = resp.Body.Close()
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
|
stdout.ExpectMatch(ctx, "Confirm create?")
|
|
stdin.WriteLine("yes")
|
|
}
|