coder/coderd/templateversions_test.go

1662 lines
57 KiB
Go

package coderd_test
import (
"bytes"
"context"
"net/http"
"regexp"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/examples"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
func TestTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client, _, api := coderdtest.NewWithAPI(t, nil)
user := coderdtest.CreateFirstUser(t, client)
authz := coderdtest.AssertRBAC(t, api, client).Reset()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
req.Name = "bananas"
req.Message = "first try"
})
authz.AssertChecked(t, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(user.OrganizationID))
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
authz.Reset()
tv, err := client.TemplateVersion(ctx, version.ID)
authz.AssertChecked(t, rbac.ActionRead, tv)
require.NoError(t, err)
assert.Equal(t, "bananas", tv.Name)
assert.Equal(t, "first try", tv.Message)
})
t.Run("Message limit exceeded", func(t *testing.T) {
t.Parallel()
client, _, _ := coderdtest.NewWithAPI(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
file, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader([]byte{}))
require.NoError(t, err)
_, err = client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "bananas",
Message: strings.Repeat("a", 1048577),
StorageMethod: codersdk.ProvisionerStorageMethodFile,
FileID: file.ID,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.Error(t, err, "message too long, create should fail")
})
t.Run("MemberCanRead", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx := testutil.Context(t, testutil.WaitLong)
client1, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
_, err := client1.TemplateVersion(ctx, version.ID)
require.NoError(t, err)
})
}
func TestPostTemplateVersionsByOrganization(t *testing.T) {
t.Parallel()
t.Run("InvalidTemplate", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
templateID := uuid.New()
_, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
TemplateID: templateID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
FileID: uuid.New(),
Provisioner: codersdk.ProvisionerTypeEcho,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("FileNotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
StorageMethod: codersdk.ProvisionerStorageMethodFile,
FileID: uuid.New(),
Provisioner: codersdk.ProvisionerTypeEcho,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("WithParameters", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
user := coderdtest.CreateFirstUser(t, client)
data, err := echo.Tar(&echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: echo.ApplyComplete,
ProvisionPlan: echo.PlanComplete,
})
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
file, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader(data))
require.NoError(t, err)
version, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "bananas",
StorageMethod: codersdk.ProvisionerStorageMethodFile,
FileID: file.ID,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.NoError(t, err)
require.Equal(t, "bananas", version.Name)
require.Equal(t, provisionersdk.ScopeOrganization, version.Job.Tags[provisionersdk.TagScope])
require.Len(t, auditor.AuditLogs(), 2)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[1].Action)
})
t.Run("Example", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
ls, err := examples.List()
require.NoError(t, err)
// try a bad example ID
_, err = client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "my-example",
StorageMethod: codersdk.ProvisionerStorageMethodFile,
ExampleID: "not a real ID",
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.Error(t, err)
require.ErrorContains(t, err, "not found")
// try file and example IDs
_, err = client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "my-example",
StorageMethod: codersdk.ProvisionerStorageMethodFile,
ExampleID: ls[0].ID,
FileID: uuid.New(),
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.Error(t, err)
require.ErrorContains(t, err, "example_id")
require.ErrorContains(t, err, "file_id")
// try a good example ID
tv, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "my-example",
StorageMethod: codersdk.ProvisionerStorageMethodFile,
ExampleID: ls[0].ID,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.NoError(t, err)
require.Equal(t, "my-example", tv.Name)
// ensure the template tar was uploaded correctly
fl, ct, err := client.Download(ctx, tv.Job.FileID)
require.NoError(t, err)
require.Equal(t, "application/x-tar", ct)
tar, err := examples.Archive(ls[0].ID)
require.NoError(t, err)
require.EqualValues(t, tar, fl)
// ensure we don't get file conflicts on multiple uses of the same example
tv, err = client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "my-example",
StorageMethod: codersdk.ProvisionerStorageMethodFile,
ExampleID: ls[0].ID,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.NoError(t, err)
})
}
func TestPatchCancelTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("AlreadyCompleted", 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)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.CancelTemplateVersion(ctx, version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("AlreadyCanceled", 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, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
}},
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
coderdtest.AwaitTemplateVersionJobRunning(t, client, version.ID)
err := client.CancelTemplateVersion(ctx, version.ID)
require.NoError(t, err)
err = client.CancelTemplateVersion(ctx, version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
require.Eventually(t, func() bool {
var err error
version, err = client.TemplateVersion(ctx, version.ID)
return assert.NoError(t, err) && version.Job.Status == codersdk.ProvisionerJobFailed
}, testutil.WaitShort, testutil.IntervalFast)
})
// TODO(Cian): until we are able to test cancellation properly, validating
// Running -> Canceling is the best we can do for now.
t.Run("Canceling", 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, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
}},
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
require.Eventually(t, func() bool {
var err error
version, err = client.TemplateVersion(ctx, version.ID)
if !assert.NoError(t, err) {
return false
}
t.Logf("Status: %s", version.Job.Status)
return version.Job.Status == codersdk.ProvisionerJobRunning
}, testutil.WaitShort, testutil.IntervalFast)
err := client.CancelTemplateVersion(ctx, version.ID)
require.NoError(t, err)
require.Eventually(t, func() bool {
var err error
version, err = client.TemplateVersion(ctx, version.ID)
// job gets marked Failed when there is an Error; in practice we never get to Status = Canceled
// because provisioners report an Error when canceled. We check the Error string to ensure we don't mask
// other errors in this test.
t.Logf("got version %s | %s", version.Job.Error, version.Job.Status)
return assert.NoError(t, err) &&
strings.HasSuffix(version.Job.Error, "canceled") &&
version.Job.Status == codersdk.ProvisionerJobFailed
}, testutil.WaitShort, testutil.IntervalFast)
})
}
func TestTemplateVersionsExternalAuth(t *testing.T) {
t.Parallel()
t.Run("Empty", 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)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionExternalAuth(ctx, version.ID)
require.NoError(t, err)
})
t.Run("Authenticated", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
ExternalAuthConfigs: []*externalauth.Config{{
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github", Optional: true}},
},
},
}},
})
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
require.Empty(t, version.Job.Error)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Not authenticated to start!
providers, err := client.TemplateVersionExternalAuth(ctx, version.ID)
require.NoError(t, err)
require.Len(t, providers, 1)
require.False(t, providers[0].Authenticated)
// Perform the Git auth callback to authenticate the user...
resp := coderdtest.RequestExternalAuthCallback(t, "github", client)
_ = resp.Body.Close()
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
// Ensure that the returned Git auth for the template is authenticated!
providers, err = client.TemplateVersionExternalAuth(ctx, version.ID)
require.NoError(t, err)
require.Len(t, providers, 1)
require.True(t, providers[0].Authenticated)
require.True(t, providers[0].Optional)
})
}
func TestTemplateVersionResources(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionResources(ctx, version.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("List", 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, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "some",
Type: "example",
Agents: []*proto.Agent{{
Id: "something",
Auth: &proto.Agent_Token{},
}},
}, {
Name: "another",
Type: "example",
}},
},
},
}},
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
resources, err := client.TemplateVersionResources(ctx, version.ID)
require.NoError(t, err)
require.NotNil(t, resources)
require.Len(t, resources, 4)
require.Equal(t, "some", resources[2].Name)
require.Equal(t, "example", resources[2].Type)
require.Len(t, resources[2].Agents, 1)
})
}
func TestTemplateVersionLogs(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, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "example",
},
},
}, {
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "some",
Type: "example",
Agents: []*proto.Agent{{
Id: "something",
Auth: &proto.Agent_Token{
Token: uuid.NewString(),
},
}},
}, {
Name: "another",
Type: "example",
}},
},
},
}},
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
logs, closer, err := client.TemplateVersionLogsAfter(ctx, version.ID, 0)
require.NoError(t, err)
defer closer.Close()
for {
_, ok := <-logs
if !ok {
return
}
}
}
func TestTemplateVersionsByTemplate(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
})
require.NoError(t, err)
require.Len(t, versions, 1)
})
}
func TestTemplateVersionByName(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionByName(ctx, template.ID, "nothing")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionByName(ctx, template.ID, version.Name)
require.NoError(t, err)
})
}
func TestPatchActiveTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: uuid.New(),
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("CanceledBuild", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, template.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.CancelTemplateVersion(ctx, version.ID)
require.NoError(t, err)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
require.Contains(t, apiErr.Detail, "canceled")
})
t.Run("PendingBuild", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, template.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
require.Contains(t, apiErr.Detail, "pending")
})
t.Run("DoesNotBelong", 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)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("Archived", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
ownerClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
Auditor: auditor,
})
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
version = coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, nil, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.SetArchiveTemplateVersion(ctx, version.ID, true)
require.NoError(t, err)
err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
require.Error(t, err)
require.ErrorContains(t, err, "The provided template version is archived")
})
t.Run("SuccessfulBuild", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
Auditor: auditor,
})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, template.ID)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
require.NoError(t, err)
require.Len(t, auditor.AuditLogs(), 6)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[5].Action)
})
}
func TestTemplateVersionDryRun(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
resource := &proto.Resource{
Name: "cool-resource",
Type: "cool_resource_type",
}
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
},
{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{resource},
},
},
},
},
})
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Create template version dry-run
job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
require.NoError(t, err)
// Fetch template version dry-run
newJob, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID)
require.NoError(t, err)
require.Equal(t, job.ID, newJob.ID)
// Stream logs
logs, closer, err := client.TemplateVersionDryRunLogsAfter(ctx, version.ID, job.ID, 0)
require.NoError(t, err)
defer closer.Close()
logsDone := make(chan struct{})
go func() {
defer close(logsDone)
logCount := 0
for range logs {
logCount++
}
assert.GreaterOrEqual(t, logCount, 1, "unexpected log count")
}()
// Wait for the job to complete
require.Eventually(t, func() bool {
job, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID)
return assert.NoError(t, err) && job.Status == codersdk.ProvisionerJobSucceeded
}, testutil.WaitShort, testutil.IntervalFast)
<-logsDone
resources, err := client.TemplateVersionDryRunResources(ctx, version.ID, job.ID)
require.NoError(t, err)
require.Len(t, resources, 1)
require.Equal(t, resource.Name, resources[0].Name)
require.Equal(t, resource.Type, resources[0].Type)
})
t.Run("ImportNotFinished", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
// This import job will never finish
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
}},
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("Cancel", func(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
client, closer := coderdtest.NewWithProvisionerCloser(t, nil)
defer closer.Close()
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
},
{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{},
},
},
},
})
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
require.Equal(t, codersdk.ProvisionerJobSucceeded, version.Job.Status)
closer.Close()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Create the dry-run
job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
require.NoError(t, err)
require.Equal(t, codersdk.ProvisionerJobPending, job.Status)
err = client.CancelTemplateVersionDryRun(ctx, version.ID, job.ID)
require.NoError(t, err)
job, err = client.TemplateVersionDryRun(ctx, version.ID, job.ID)
require.NoError(t, err)
require.Equal(t, codersdk.ProvisionerJobCanceled, job.Status)
})
t.Run("AlreadyCompleted", 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)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Create the dry-run
job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
require.NoError(t, err)
require.Eventually(t, func() bool {
job, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID)
if !assert.NoError(t, err) {
return false
}
t.Logf("Status: %s", job.Status)
return job.Status == codersdk.ProvisionerJobSucceeded
}, testutil.WaitShort, testutil.IntervalFast)
err = client.CancelTemplateVersionDryRun(ctx, version.ID, job.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("AlreadyCanceled", func(t *testing.T) {
t.Parallel()
client, closer := coderdtest.NewWithProvisionerCloser(t, nil)
defer closer.Close()
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
},
{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{},
},
},
},
})
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
require.Equal(t, codersdk.ProvisionerJobSucceeded, version.Job.Status)
closer.Close()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Create the dry-run
job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
require.NoError(t, err)
err = client.CancelTemplateVersionDryRun(ctx, version.ID, job.ID)
require.NoError(t, err)
err = client.CancelTemplateVersionDryRun(ctx, version.ID, job.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
})
}
// TestPaginatedTemplateVersions creates a list of template versions and paginate.
func TestPaginatedTemplateVersions(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
// Populate database with template versions.
total := 9
eg, egCtx := errgroup.WithContext(ctx)
templateVersionIDs := make([]uuid.UUID, total)
data, err := echo.Tar(nil)
require.NoError(t, err)
file, err := client.Upload(egCtx, codersdk.ContentTypeTar, bytes.NewReader(data))
require.NoError(t, err)
for i := 0; i < total; i++ {
i := i
eg.Go(func() error {
templateVersion, err := client.CreateTemplateVersion(egCtx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: uuid.NewString(),
TemplateID: template.ID,
FileID: file.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeEcho,
})
if err != nil {
return err
}
templateVersionIDs[i] = templateVersion.ID
return nil
})
}
err = eg.Wait()
require.NoError(t, err, "create templates failed")
templateVersions, err := client.TemplateVersionsByTemplate(ctx,
codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
},
)
require.NoError(t, err)
require.Len(t, templateVersions, 10, "wrong number of template versions created")
type args struct {
pagination codersdk.Pagination
}
tests := []struct {
name string
args args
want []codersdk.TemplateVersion
expectedError string
}{
{
name: "Single result",
args: args{pagination: codersdk.Pagination{Limit: 1}},
want: templateVersions[:1],
},
{
name: "Single result, second page",
args: args{pagination: codersdk.Pagination{Limit: 1, Offset: 1}},
want: templateVersions[1:2],
},
{
name: "Last two results",
args: args{pagination: codersdk.Pagination{Limit: 2, Offset: 8}},
want: templateVersions[8:10],
},
{
name: "AfterID returns next two results",
args: args{pagination: codersdk.Pagination{Limit: 2, AfterID: templateVersions[1].ID}},
want: templateVersions[2:4],
},
{
name: "No result after last AfterID",
args: args{pagination: codersdk.Pagination{Limit: 2, AfterID: templateVersions[9].ID}},
want: []codersdk.TemplateVersion{},
},
{
name: "No result after last Offset",
args: args{pagination: codersdk.Pagination{Limit: 2, Offset: 10}},
want: []codersdk.TemplateVersion{},
},
{
name: "After_id does not exist",
args: args{pagination: codersdk.Pagination{AfterID: uuid.New()}},
expectedError: "does not exist",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
got, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
Pagination: tt.args.pagination,
})
if tt.expectedError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.expectedError)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
func TestTemplateVersionByOrganizationTemplateAndName(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, template.Name, "nothing")
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, template.Name, version.Name)
require.NoError(t, err)
})
}
func TestPreviousTemplateVersion(t *testing.T) {
t.Parallel()
t.Run("Previous version not found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
// Create two templates to be sure it is not returning a previous version
// from another template
templateAVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, templateAVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateAVersion1.ID)
// Create two versions for the template B to be sure if we try to get the
// previous version of the first version it will returns a 404
templateBVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
templateB := coderdtest.CreateTemplate(t, client, user.OrganizationID, templateBVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion1.ID)
templateBVersion2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, templateB.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion2.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion1.Name)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("Previous version found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
// Create two templates to be sure it is not returning a previous version
// from another template
templateAVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, templateAVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateAVersion1.ID)
// Create two versions for the template B so we can try to get the previous
// version of version 2
templateBVersion1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
templateB := coderdtest.CreateTemplate(t, client, user.OrganizationID, templateBVersion1.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion1.ID)
templateBVersion2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, templateB.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateBVersion2.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
result, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion2.Name)
require.NoError(t, err)
require.Equal(t, templateBVersion1.ID, result.ID)
})
}
func TestTemplateExamples(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
ex, err := client.TemplateExamples(ctx, user.OrganizationID)
require.NoError(t, err)
ls, err := examples.List()
require.NoError(t, err)
require.EqualValues(t, ls, ex)
})
}
func TestTemplateVersionVariables(t *testing.T) {
t.Parallel()
createEchoResponses := func(templateVariables []*proto.TemplateVariable) *echo.Responses {
return &echo.Responses{
Parse: []*proto.Response{
{
Type: &proto.Response_Parse{
Parse: &proto.ParseComplete{
TemplateVariables: templateVariables,
},
},
},
},
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
}
}
t.Run("Pass value for required variable", func(t *testing.T) {
t.Parallel()
templateVariables := []*proto.TemplateVariable{
{
Name: "first_variable",
Description: "This is the first variable",
Type: "string",
Required: true,
},
}
const firstVariableValue = "foobar"
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID,
createEchoResponses(templateVariables),
func(ctvr *codersdk.CreateTemplateVersionRequest) {
ctvr.UserVariableValues = []codersdk.VariableValue{
{
Name: templateVariables[0].Name,
Value: firstVariableValue,
},
}
},
)
templateVersion := coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
// As user passed the value for the first parameter, the job will succeed.
require.Empty(t, templateVersion.Job.Error)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
actualVariables, err := client.TemplateVersionVariables(ctx, templateVersion.ID)
require.NoError(t, err)
require.Len(t, actualVariables, 1)
require.Equal(t, templateVariables[0].Name, actualVariables[0].Name)
require.Equal(t, templateVariables[0].Description, actualVariables[0].Description)
require.Equal(t, templateVariables[0].Type, actualVariables[0].Type)
require.Equal(t, templateVariables[0].DefaultValue, actualVariables[0].DefaultValue)
require.Equal(t, templateVariables[0].Required, actualVariables[0].Required)
require.Equal(t, templateVariables[0].Sensitive, actualVariables[0].Sensitive)
require.Equal(t, firstVariableValue, actualVariables[0].Value)
})
t.Run("Missing value for required variable", func(t *testing.T) {
t.Parallel()
templateVariables := []*proto.TemplateVariable{
{
Name: "first_variable",
Description: "This is the first variable",
Type: "string",
Required: true,
},
{
Name: "second_variable",
Description: "This is the second variable",
DefaultValue: "123",
Type: "number",
},
}
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, createEchoResponses(templateVariables))
templateVersion := coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
// As the first variable is marked as required and misses the default value,
// the job will fail, but will populate the template_version_variables table with existing variables.
require.Contains(t, templateVersion.Job.Error, "required template variables need values")
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
actualVariables, err := client.TemplateVersionVariables(ctx, templateVersion.ID)
require.NoError(t, err)
require.Len(t, actualVariables, 2)
for i := range templateVariables {
require.Equal(t, templateVariables[i].Name, actualVariables[i].Name)
require.Equal(t, templateVariables[i].Description, actualVariables[i].Description)
require.Equal(t, templateVariables[i].Type, actualVariables[i].Type)
require.Equal(t, templateVariables[i].DefaultValue, actualVariables[i].DefaultValue)
require.Equal(t, templateVariables[i].Required, actualVariables[i].Required)
require.Equal(t, templateVariables[i].Sensitive, actualVariables[i].Sensitive)
}
require.Equal(t, "", actualVariables[0].Value)
require.Equal(t, templateVariables[1].DefaultValue, actualVariables[1].Value)
})
t.Run("Redact sensitive variables", func(t *testing.T) {
t.Parallel()
templateVariables := []*proto.TemplateVariable{
{
Name: "first_variable",
Description: "This is the first variable",
Type: "string",
Required: true,
Sensitive: true,
},
}
const firstVariableValue = "foobar"
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID,
createEchoResponses(templateVariables),
func(ctvr *codersdk.CreateTemplateVersionRequest) {
ctvr.UserVariableValues = []codersdk.VariableValue{
{
Name: templateVariables[0].Name,
Value: firstVariableValue,
},
}
},
)
templateVersion := coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
// As user passed the value for the first parameter, the job will succeed.
require.Empty(t, templateVersion.Job.Error)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
actualVariables, err := client.TemplateVersionVariables(ctx, templateVersion.ID)
require.NoError(t, err)
require.Len(t, actualVariables, 1)
require.Equal(t, templateVariables[0].Name, actualVariables[0].Name)
require.Equal(t, templateVariables[0].Description, actualVariables[0].Description)
require.Equal(t, templateVariables[0].Type, actualVariables[0].Type)
require.Equal(t, templateVariables[0].Required, actualVariables[0].Required)
require.Equal(t, templateVariables[0].Sensitive, actualVariables[0].Sensitive)
require.Equal(t, "*redacted*", actualVariables[0].DefaultValue)
require.Equal(t, "*redacted*", actualVariables[0].Value)
})
}
func TestTemplateVersionPatch(t *testing.T) {
t.Parallel()
t.Run("Update the name", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const newName = "new-name"
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{
Name: newName,
})
require.NoError(t, err)
assert.Equal(t, newName, updatedVersion.Name)
assert.NotEqual(t, updatedVersion.Name, version.Name)
})
t.Run("Update the message", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
req.Message = "Example message"
})
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
wantMessage := "Updated message"
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{
Message: &wantMessage,
})
require.NoError(t, err)
assert.Equal(t, wantMessage, updatedVersion.Message)
})
t.Run("Remove the message", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
req.Message = "Example message"
})
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
wantMessage := ""
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{
Message: &wantMessage,
})
require.NoError(t, err)
assert.Equal(t, wantMessage, updatedVersion.Message)
})
t.Run("Keep the message", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
wantMessage := "Example message"
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
req.Message = wantMessage
})
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
t.Log(version.Message)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{
Message: nil,
})
require.NoError(t, err)
assert.Equal(t, wantMessage, updatedVersion.Message)
})
t.Run("Use the same name if a new name is not passed", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{})
require.NoError(t, err)
assert.Equal(t, version.Name, updatedVersion.Name)
})
t.Run("Use the same name for two different templates", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version2.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const commonTemplateVersionName = "common-template-version-name"
updatedVersion1, err := client.UpdateTemplateVersion(ctx, version1.ID, codersdk.PatchTemplateVersionRequest{
Name: commonTemplateVersionName,
})
require.NoError(t, err)
updatedVersion2, err := client.UpdateTemplateVersion(ctx, version2.ID, codersdk.PatchTemplateVersionRequest{
Name: commonTemplateVersionName,
})
require.NoError(t, err)
assert.NotEqual(t, updatedVersion1.ID, updatedVersion2.ID)
assert.Equal(t, updatedVersion1.Name, updatedVersion2.Name)
})
t.Run("Use the same name for two versions for the same templates", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) {
ctvr.TemplateID = template.ID
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.UpdateTemplateVersion(ctx, version2.ID, codersdk.PatchTemplateVersionRequest{
Name: version1.Name,
})
require.Error(t, err)
})
t.Run("Rename the unassigned template", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const commonTemplateVersionName = "common-template-version-name"
updatedVersion1, err := client.UpdateTemplateVersion(ctx, version1.ID, codersdk.PatchTemplateVersionRequest{
Name: commonTemplateVersionName,
})
require.NoError(t, err)
assert.Equal(t, commonTemplateVersionName, updatedVersion1.Name)
})
t.Run("Use incorrect template version name", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const incorrectTemplateVersionName = "incorrect/name"
_, err := client.UpdateTemplateVersion(ctx, version1.ID, codersdk.PatchTemplateVersionRequest{
Name: incorrectTemplateVersionName,
})
require.Error(t, err)
})
}
func TestTemplateVersionParameters_Order(t *testing.T) {
t.Parallel()
const (
firstParameterName = "first_parameter"
firstParameterType = "string"
firstParameterValue = "aaa"
// no order
secondParameterName = "Second_parameter"
secondParameterType = "number"
secondParameterValue = "2"
secondParameterOrder = 3
thirdParameterName = "third_parameter"
thirdParameterType = "number"
thirdParameterValue = "3"
thirdParameterOrder = 3
fourthParameterName = "Fourth_parameter"
fourthParameterType = "number"
fourthParameterValue = "3"
fourthParameterOrder = 2
fifthParameterName = "Fifth_parameter"
fifthParameterType = "string"
fifthParameterValue = "aaa"
// no order
)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Response{
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{
{
Name: firstParameterName,
Type: firstParameterType,
// No order
},
{
Name: secondParameterName,
Type: secondParameterType,
Order: secondParameterOrder,
},
{
Name: thirdParameterName,
Type: thirdParameterType,
Order: thirdParameterOrder,
},
{
Name: fourthParameterName,
Type: fourthParameterType,
Order: fourthParameterOrder,
},
{
Name: fifthParameterName,
Type: fifthParameterType,
// No order
},
},
},
},
},
},
ProvisionApply: echo.ApplyComplete,
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
require.NoError(t, err)
require.Len(t, templateRichParameters, 5)
require.Equal(t, fifthParameterName, templateRichParameters[0].Name)
require.Equal(t, firstParameterName, templateRichParameters[1].Name)
require.Equal(t, fourthParameterName, templateRichParameters[2].Name)
require.Equal(t, secondParameterName, templateRichParameters[3].Name)
require.Equal(t, thirdParameterName, templateRichParameters[4].Name)
}
func TestTemplateArchiveVersions(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
var totalVersions int
// Create a template to archive
initialVersion := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
totalVersions++
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, initialVersion.ID)
allFailed := make([]uuid.UUID, 0)
expArchived := make([]uuid.UUID, 0)
// create some failed versions
for i := 0; i < 2; i++ {
failed := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanFailed,
ProvisionApply: echo.ApplyFailed,
}, func(req *codersdk.CreateTemplateVersionRequest) {
req.TemplateID = template.ID
})
allFailed = append(allFailed, failed.ID)
totalVersions++
}
// Create some unused versions
for i := 0; i < 2; i++ {
unused := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
}, func(req *codersdk.CreateTemplateVersionRequest) {
req.TemplateID = template.ID
})
expArchived = append(expArchived, unused.ID)
totalVersions++
}
// Create some used template versions
for i := 0; i < 2; i++ {
used := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
}, func(req *codersdk.CreateTemplateVersionRequest) {
req.TemplateID = template.ID
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, used.ID)
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, uuid.Nil, func(request *codersdk.CreateWorkspaceRequest) {
request.TemplateVersionID = used.ID
})
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
totalVersions++
}
ctx := testutil.Context(t, testutil.WaitMedium)
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
Pagination: codersdk.Pagination{
Limit: 100,
},
})
require.NoError(t, err, "fetch all versions")
require.Len(t, versions, totalVersions, "total versions")
// Archive failed versions
archiveFailed, err := client.ArchiveTemplateVersions(ctx, template.ID, false)
require.NoError(t, err, "archive failed versions")
require.ElementsMatch(t, archiveFailed.ArchivedIDs, allFailed, "all failed versions archived")
remaining, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
Pagination: codersdk.Pagination{
Limit: 100,
},
})
require.NoError(t, err, "fetch all non-failed versions")
require.Len(t, remaining, totalVersions-len(allFailed), "remaining non-failed versions")
// Try archiving "All" unused templates
archived, err := client.ArchiveTemplateVersions(ctx, template.ID, true)
require.NoError(t, err, "archive versions")
require.ElementsMatch(t, archived.ArchivedIDs, expArchived, "all expected versions archived")
remaining, err = client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
Pagination: codersdk.Pagination{
Limit: 100,
},
})
require.NoError(t, err, "fetch all versions")
require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed), "remaining versions")
// Unarchive a version
err = client.SetArchiveTemplateVersion(ctx, expArchived[0], false)
require.NoError(t, err, "unarchive a version")
tv, err := client.TemplateVersion(ctx, expArchived[0])
require.NoError(t, err, "fetch version")
require.False(t, tv.Archived, "expect unarchived")
// Check the remaining again
remaining, err = client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
TemplateID: template.ID,
Pagination: codersdk.Pagination{
Limit: 100,
},
})
require.NoError(t, err, "fetch all versions")
require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed)+1, "remaining versions")
}