Files
coder/coderd/templateversions_test.go
Steven Masley cb6b5e8fbd chore: push rbac actions to policy package (#13274)
Just moved `rbac.Action` -> `policy.Action`. This is for the stacked PR to not have circular dependencies when doing autogen. Without this, the autogen can produce broken golang code, which prevents the autogen from compiling.

So just avoiding circular dependencies. Doing this in it's own PR to reduce LoC diffs in the primary PR, since this has 0 functional changes.
2024-05-15 09:46:35 -05:00

1663 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/coderd/rbac/policy"
"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, policy.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, policy.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")
}