mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: run a terraform plan before creating workspaces with the given template parameters (#1732)
This commit is contained in:
@@ -2,6 +2,7 @@ package coderd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
@@ -448,6 +450,207 @@ func TestPatchActiveTemplateVersion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateVersionDryRun(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resource := &proto.Resource{
|
||||
Name: "cool-resource",
|
||||
Type: "cool_resource_type",
|
||||
}
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1, IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Resources: []*proto.Resource{resource},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
// Create template version dry-run
|
||||
after := time.Now()
|
||||
job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{
|
||||
ParameterValues: []codersdk.CreateParameterRequest{},
|
||||
})
|
||||
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, err := client.TemplateVersionDryRunLogsAfter(ctx, version.ID, job.ID, after)
|
||||
require.NoError(t, err)
|
||||
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return job.Status == codersdk.ProvisionerJobSucceeded
|
||||
}, 5*time.Second, 25*time.Millisecond)
|
||||
|
||||
<-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{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
// This import job will never finish
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
||||
_, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{
|
||||
ParameterValues: []codersdk.CreateParameterRequest{},
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("Cancel", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
forceCompleteTemplateVersionJob(t, api.Database, client, version)
|
||||
|
||||
// Create the dry-run
|
||||
job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{
|
||||
ParameterValues: []codersdk.CreateParameterRequest{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Logf("Status: %s", job.Status)
|
||||
return job.Status == codersdk.ProvisionerJobRunning
|
||||
}, 5*time.Second, 25*time.Millisecond)
|
||||
|
||||
err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Logf("Status: %s", job.Status)
|
||||
return job.Status == codersdk.ProvisionerJobCanceled
|
||||
}, 5*time.Second, 25*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("AlreadyCompleted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
// Create the dry-run
|
||||
job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{
|
||||
ParameterValues: []codersdk.CreateParameterRequest{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Logf("Status: %s", job.Status)
|
||||
return job.Status == codersdk.ProvisionerJobSucceeded
|
||||
}, 5*time.Second, 25*time.Millisecond)
|
||||
|
||||
err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("AlreadyCanceled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
forceCompleteTemplateVersionJob(t, api.Database, client, version)
|
||||
|
||||
// Create the dry-run
|
||||
job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{
|
||||
ParameterValues: []codersdk.CreateParameterRequest{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestPaginatedTemplateVersions creates a list of template versions and paginate.
|
||||
func TestPaginatedTemplateVersions(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -539,3 +742,23 @@ func TestPaginatedTemplateVersions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func forceCompleteTemplateVersionJob(t *testing.T, db database.Store, client *codersdk.Client, version codersdk.TemplateVersion) {
|
||||
t.Helper()
|
||||
|
||||
// HACK: we need the template version job to be finished so the dry-run job
|
||||
// can be created. We do this by canceling the job and then marking it as
|
||||
// successful.
|
||||
err := client.CancelTemplateVersion(context.Background(), version.ID)
|
||||
require.NoError(t, err)
|
||||
err = db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{
|
||||
ID: version.Job.ID,
|
||||
UpdatedAt: time.Now(),
|
||||
CompletedAt: sql.NullTime{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
Error: sql.NullString{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user