mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: distinct operations for provisioner's 'parse', 'init', 'plan', 'apply', 'graph' (#21064)
Provisioner steps broken into smaller granular actions. Changes: - `ExtractArchive` moved to `init` request (was in `configure`) - Writing `tfstate` moved to `plan` (was in `configure`) - Moved most plan/apply outputs to `GraphComplete`
This commit is contained in:
+12
-8
@@ -301,11 +301,13 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: parameters,
|
||||
Presets: presets,
|
||||
},
|
||||
@@ -1573,11 +1575,13 @@ func TestCreateValidateRichParameters(t *testing.T) {
|
||||
func TestCreateWithGitAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
echoResponses := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github"}},
|
||||
},
|
||||
},
|
||||
|
||||
+3
-3
@@ -306,10 +306,10 @@ func TestRestartWithParameters(t *testing.T) {
|
||||
echoResponses := func() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: immutableParameterName,
|
||||
|
||||
+5
-5
@@ -155,7 +155,7 @@ func TestSSH(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
@@ -244,7 +244,7 @@ func TestSSH(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
@@ -305,7 +305,7 @@ func TestSSH(t *testing.T) {
|
||||
echoResponses := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
}
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses)
|
||||
@@ -326,7 +326,7 @@ func TestSSH(t *testing.T) {
|
||||
echoResponses2 := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken2),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken2),
|
||||
}
|
||||
version = coderdtest.UpdateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses2, template.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
|
||||
@@ -655,7 +655,7 @@ func TestSSH(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
|
||||
+12
-10
@@ -36,10 +36,10 @@ const (
|
||||
func mutableParamsResponse() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: mutableParameterName,
|
||||
@@ -59,10 +59,10 @@ func mutableParamsResponse() *echo.Responses {
|
||||
func immutableParamsResponse() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: immutableParameterName,
|
||||
@@ -83,11 +83,13 @@ func TestStart(t *testing.T) {
|
||||
|
||||
echoResponses := func() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: ephemeralParameterName,
|
||||
|
||||
+4
-12
@@ -285,19 +285,10 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID
|
||||
taskAppID := uuid.New()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
HasAiTasks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
@@ -321,6 +312,7 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID
|
||||
},
|
||||
},
|
||||
},
|
||||
HasAiTasks: true,
|
||||
AiTasks: []*proto.AITask{
|
||||
{
|
||||
AppId: taskAppID.String(),
|
||||
|
||||
@@ -282,10 +282,10 @@ func TestTemplatePresets(t *testing.T) {
|
||||
func templateWithPresets(presets []*proto.Preset) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: presets,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1306,31 +1306,10 @@ func createEchoResponsesWithTemplateVariables(templateVariables []*proto.Templat
|
||||
func completeWithAgent() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
|
||||
@@ -71,6 +71,7 @@ func TestTemplateVersionsArchive(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionPlan: echo.PlanFailed,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
}, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||
request.TemplateID = template.ID
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestWorkspaceActivityBump(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(agentToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
+19
-27
@@ -61,19 +61,11 @@ func TestTasks(t *testing.T) {
|
||||
taskAppID := uuid.New()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
@@ -951,8 +943,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -995,8 +987,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}},
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
@@ -1097,8 +1089,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -1218,8 +1210,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -1275,8 +1267,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -1309,8 +1301,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -1359,8 +1351,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -1371,8 +1363,8 @@ func TestTasksCreate(t *testing.T) {
|
||||
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
|
||||
+3
-30
@@ -476,37 +476,10 @@ func TestAuditLogsFilter(t *testing.T) {
|
||||
func completeWithAgentAndApp() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
Apps: []*proto.App{
|
||||
{
|
||||
Slug: "app",
|
||||
DisplayName: "App",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
|
||||
@@ -233,9 +233,9 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
|
||||
// Since initial version has no parameters, any parameters in the new version will be incompatible
|
||||
res = &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "new",
|
||||
@@ -1105,8 +1105,10 @@ func TestExecutorFailedWorkspace(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
||||
@@ -1644,10 +1646,10 @@ func mustProvisionWorkspaceWithParameters(t *testing.T, client *codersdk.Client,
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: richParameters,
|
||||
},
|
||||
},
|
||||
@@ -1774,17 +1776,10 @@ func TestExecutorTaskWorkspace(t *testing.T) {
|
||||
taskAppID := uuid.New()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{HasAiTasks: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Agents: []*proto.Agent{
|
||||
@@ -1804,6 +1799,7 @@ func TestExecutorTaskWorkspace(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
HasAiTasks: true,
|
||||
AiTasks: []*proto.AITask{
|
||||
{
|
||||
AppId: taskAppID.String(),
|
||||
|
||||
@@ -199,7 +199,7 @@ func TestDERPForceWebSockets(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -50,12 +50,24 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU
|
||||
}
|
||||
|
||||
files := echo.WithExtraFiles(extraFiles)
|
||||
files.ProvisionInit = []*proto.Response{{
|
||||
Type: &proto.Response_Init{
|
||||
Init: &proto.InitComplete{
|
||||
ModuleFiles: args.ModulesArchive,
|
||||
},
|
||||
},
|
||||
}}
|
||||
files.ProvisionPlan = []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Plan: args.Plan,
|
||||
ModuleFiles: args.ModulesArchive,
|
||||
Parameters: args.StaticParams,
|
||||
Plan: args.Plan,
|
||||
},
|
||||
},
|
||||
}}
|
||||
files.ProvisionGraph = []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: args.StaticParams,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -476,7 +476,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -507,7 +507,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -607,7 +607,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -668,7 +668,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -714,7 +714,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -779,7 +779,7 @@ func TestExternalAuthCallback(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestAgentGitSSHKey(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -149,7 +149,7 @@ func TestAgentGitSSHKey_APIKeyScopes(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||
})
|
||||
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
+10
-16
@@ -78,7 +78,7 @@ func TestDeploymentInsights(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
||||
@@ -168,7 +168,7 @@ func TestUserActivityInsights_SanityCheck(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
||||
@@ -266,7 +266,7 @@ func TestUserLatencyInsights(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
||||
@@ -641,22 +641,16 @@ func TestTemplateInsights_Golden(t *testing.T) {
|
||||
// Create the template version and template.
|
||||
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: parameters,
|
||||
Resources: resources,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: resources,
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -1561,9 +1555,9 @@ func TestUserActivityInsights_Golden(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: resources,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -536,9 +536,9 @@ func TestAgents(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -866,7 +866,7 @@ func prepareWorkspaceAndAgent(ctx context.Context, t *testing.T, client *codersd
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -1771,7 +1771,7 @@ func TestTemplateMetrics(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
require.Equal(t, -1, template.ActiveUserCount)
|
||||
|
||||
@@ -760,7 +760,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
@@ -793,7 +793,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
@@ -857,9 +857,9 @@ func TestTemplateVersionsExternalAuth(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github", Optional: true}},
|
||||
},
|
||||
},
|
||||
@@ -912,9 +912,9 @@ func TestTemplateVersionResources(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -953,7 +953,7 @@ func TestTemplateVersionLogs(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
@@ -961,8 +961,8 @@ func TestTemplateVersionLogs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -1211,15 +1211,15 @@ func TestTemplateVersionDryRun(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{resource},
|
||||
},
|
||||
},
|
||||
@@ -2060,10 +2060,10 @@ func TestTemplateVersionParameters_Order(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
@@ -2133,6 +2133,7 @@ func TestTemplateArchiveVersions(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanFailed,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||
req.TemplateID = template.ID
|
||||
})
|
||||
@@ -2228,10 +2229,10 @@ func TestTemplateVersionHasExternalAgent(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
|
||||
@@ -495,7 +495,7 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
@@ -506,9 +506,9 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) {
|
||||
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -463,9 +463,9 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
|
||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -121,9 +121,9 @@ func Test_ResolveRequest(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -556,13 +556,16 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
||||
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,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
// Echo will never applying since there is no complete message
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
@@ -603,13 +606,16 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Logger: &logger})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
// Echo will never applying
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
@@ -694,13 +700,16 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
||||
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,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
// Echo will never applying
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
@@ -791,13 +800,16 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
||||
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,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
// Echo will never applying
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{},
|
||||
},
|
||||
}},
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
@@ -825,9 +837,9 @@ func TestWorkspaceBuildResources(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "first_resource",
|
||||
Type: "example",
|
||||
@@ -1032,7 +1044,7 @@ func TestWorkspaceBuildLogs(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
@@ -1040,8 +1052,8 @@ func TestWorkspaceBuildLogs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -1208,9 +1220,9 @@ func TestWorkspaceDeleteSuspendedUser(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Error: "",
|
||||
Resources: nil,
|
||||
Parameters: nil,
|
||||
@@ -1488,10 +1500,18 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
ProvisionApply: []*proto.Response{{}},
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Error: "failed to plan",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
@@ -1642,9 +1662,9 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: []*proto.Preset{
|
||||
{
|
||||
Name: "autodetected",
|
||||
|
||||
@@ -26,9 +26,9 @@ func TestPostWorkspaceAuthAzureInstanceIdentity(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "somename",
|
||||
Type: "someinstance",
|
||||
@@ -70,9 +70,9 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "somename",
|
||||
Type: "someinstance",
|
||||
@@ -151,9 +151,9 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "somename",
|
||||
Type: "someinstance",
|
||||
|
||||
+67
-65
@@ -213,9 +213,9 @@ func TestWorkspace(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -254,9 +254,9 @@ func TestWorkspace(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -299,9 +299,9 @@ func TestWorkspace(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -357,9 +357,9 @@ func TestWorkspace(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -735,9 +735,9 @@ func TestWorkspace(t *testing.T) {
|
||||
authz := coderdtest.AssertRBAC(t, api, client)
|
||||
|
||||
// Create a plan response with the specified presets and parameters
|
||||
planResponse := &proto.Response{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
graphResponse := &proto.Response{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: tc.presets,
|
||||
Parameters: tc.templateVersionParameters,
|
||||
},
|
||||
@@ -746,7 +746,7 @@ func TestWorkspace(t *testing.T) {
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{planResponse},
|
||||
ProvisionGraph: []*proto.Response{graphResponse},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -2269,7 +2269,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -2297,7 +2297,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -2328,9 +2328,9 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -2420,7 +2420,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -2525,10 +2525,10 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
||||
makeParameters := func(extra ...*proto.RichParameter) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: append([]*proto.RichParameter{
|
||||
{Name: paramOneName, Description: "", Mutable: true, Type: "string"},
|
||||
{Name: paramTwoName, DisplayName: "", Description: "", Mutable: true, Type: "string"},
|
||||
@@ -3382,9 +3382,9 @@ func TestWorkspaceWatcher(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -3467,8 +3467,10 @@ func TestWorkspaceWatcher(t *testing.T) {
|
||||
|
||||
// Add a new version that will fail.
|
||||
badVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
@@ -3536,9 +3538,9 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "beta",
|
||||
Type: "example",
|
||||
@@ -3604,9 +3606,9 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -3679,9 +3681,9 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -3723,9 +3725,9 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
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{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -3803,10 +3805,10 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
@@ -3907,10 +3909,10 @@ func TestWorkspaceWithMultiSelectFailure(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "param",
|
||||
@@ -3986,10 +3988,10 @@ func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
@@ -4077,10 +4079,10 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
@@ -4879,8 +4881,8 @@ func TestWorkspaceListTasks(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
},
|
||||
@@ -4949,9 +4951,9 @@ func TestWorkspaceAppUpsertRestart(t *testing.T) {
|
||||
// Create template version with workspace app
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "test-resource",
|
||||
Type: "example",
|
||||
@@ -5023,9 +5025,9 @@ func TestMultipleAITasksDisallowed(t *testing.T) {
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
HasAiTasks: true,
|
||||
AiTasks: []*proto.AITask{
|
||||
{
|
||||
@@ -5320,10 +5322,10 @@ func TestWorkspaceCreateWithImplicitPreset(t *testing.T) {
|
||||
createTemplateWithPresets := func(t *testing.T, client *codersdk.Client, user codersdk.CreateFirstUserResponse, presets []*proto.Preset) (codersdk.Template, codersdk.TemplateVersion) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: presets,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1015,8 +1015,8 @@ func TestTools(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{{Name: "AI Prompt", Type: "string"}},
|
||||
HasAiTasks: true,
|
||||
}}},
|
||||
|
||||
@@ -560,20 +560,12 @@ func TestEnterpriseCreateWithPreset(t *testing.T) {
|
||||
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: parameters,
|
||||
Presets: presets,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
func completeWithExternalAgent() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "coder_external_agent",
|
||||
@@ -46,27 +46,6 @@ func completeWithExternalAgent() *echo.Responses {
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "coder_external_agent",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "external-agent",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,31 +53,10 @@ func completeWithExternalAgent() *echo.Responses {
|
||||
func completeWithRegularAgent() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "regular-agent",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
|
||||
@@ -660,21 +660,21 @@ func TestManagedAgentLimit(t *testing.T) {
|
||||
// build.
|
||||
appID := uuid.NewString()
|
||||
echoRes := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Plan: []byte("{}"),
|
||||
ModuleFiles: []byte{},
|
||||
HasAiTasks: true,
|
||||
Plan: []byte("{}"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestAgentGitSSHKeyCustomRoles(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, org.ID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
project := coderdtest.CreateTemplate(t, client, org.ID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -384,10 +384,10 @@ func TestClaimPrebuild(t *testing.T) {
|
||||
func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
@@ -442,26 +442,5 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,21 +256,16 @@ func TestProvisionerDaemonServe(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*sdkproto.Response{{
|
||||
Type: &sdkproto.Response_Plan{
|
||||
Plan: &sdkproto.PlanComplete{
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*sdkproto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Name: "example",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken, func(g *sdkproto.GraphComplete) {
|
||||
g.Resources = []*sdkproto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*sdkproto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Name: "example",
|
||||
}},
|
||||
}}
|
||||
}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
//nolint:gocritic // Not testing file upload in this test.
|
||||
@@ -446,9 +441,9 @@ func TestProvisionerDaemonServe(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*sdkproto.Response{{
|
||||
Type: &sdkproto.Response_Apply{
|
||||
Apply: &sdkproto.ApplyComplete{
|
||||
ProvisionGraph: []*sdkproto.Response{{
|
||||
Type: &sdkproto.Response_Graph{
|
||||
Graph: &sdkproto.GraphComplete{
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -147,7 +147,7 @@ func TestTemplates(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
@@ -155,8 +155,8 @@ func TestTemplates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "some",
|
||||
Type: "example",
|
||||
@@ -2161,10 +2161,10 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) {
|
||||
})
|
||||
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||
|
||||
buildPlanResponse := func(presets ...*proto.Preset) *proto.Response {
|
||||
buildGraphResponse := func(presets ...*proto.Preset) *proto.Response {
|
||||
return &proto.Response{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: presets,
|
||||
Parameters: templateVersionParameters,
|
||||
},
|
||||
@@ -2174,8 +2174,8 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) {
|
||||
|
||||
version1 := coderdtest.CreateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{buildPlanResponse(presetWithParameters1, presetWithParameters2)},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{buildGraphResponse(presetWithParameters1, presetWithParameters2)},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version1.ID)
|
||||
template := coderdtest.CreateTemplate(t, templateAdminClient, owner.OrganizationID, version1.ID)
|
||||
@@ -2193,7 +2193,7 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) {
|
||||
// Given the template is updated...
|
||||
version2 := coderdtest.UpdateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{buildPlanResponse(presetWithParameters2, presetWithParameters3)},
|
||||
ProvisionGraph: []*proto.Response{buildGraphResponse(presetWithParameters2, presetWithParameters3)},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
}, template.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version2.ID)
|
||||
@@ -2239,10 +2239,10 @@ func TestInvalidateTemplatePrebuilds_RegularUser(t *testing.T) {
|
||||
// Given
|
||||
version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: []*proto.Preset{presetWithParameters1},
|
||||
Parameters: templateVersionParameters,
|
||||
},
|
||||
|
||||
@@ -134,10 +134,10 @@ func TestReinitializeAgent(t *testing.T) {
|
||||
agentToken := uuid.UUID{3}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: []*proto.Preset{
|
||||
{
|
||||
Name: "test-preset",
|
||||
@@ -146,25 +146,6 @@ func TestReinitializeAgent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
@@ -191,6 +172,13 @@ func TestReinitializeAgent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -273,9 +261,9 @@ func setupWorkspaceAgent(t *testing.T, client *codersdk.Client, user codersdk.Cr
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -633,7 +633,7 @@ func TestIssueSignedAppToken(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -756,7 +756,7 @@ func TestReconnectingPTYSignedToken(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -121,9 +121,16 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
DailyCost: 1,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -216,14 +223,17 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
verifyQuota(ctx, t, client, user.OrganizationID.String(), 0, 4)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_START: planWithCost(2),
|
||||
proto.WorkspaceTransition_STOP: planWithCost(1),
|
||||
},
|
||||
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_START: applyWithCost(2),
|
||||
proto.WorkspaceTransition_STOP: applyWithCost(1),
|
||||
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_START: graphWithCost(2),
|
||||
proto.WorkspaceTransition_STOP: graphWithCost(1),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -422,10 +432,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
// Create a template with a workspace that costs 1 credit
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
DailyCost: 1,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -458,10 +477,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
|
||||
// Test with a template that has zero cost - should pass
|
||||
versionZeroCost := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
DailyCost: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -542,10 +570,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
// Create templates for both organizations
|
||||
authToken := uuid.NewString()
|
||||
version1 := coderdtest.CreateTemplateVersion(t, owner, first.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
DailyCost: 1,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -566,10 +603,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
||||
template1 := coderdtest.CreateTemplate(t, owner, first.OrganizationID, version1.ID)
|
||||
|
||||
version2 := coderdtest.CreateTemplateVersion(t, owner, second.ID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
DailyCost: 1,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -1156,20 +1202,16 @@ func planWithCost(cost int32) []*proto.Response {
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
DailyCost: cost,
|
||||
}},
|
||||
DailyCost: cost,
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func applyWithCost(cost int32) []*proto.Response {
|
||||
func graphWithCost(cost int32) []*proto.Response {
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -629,6 +629,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
||||
@@ -680,6 +682,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
||||
@@ -861,7 +865,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
@@ -1384,6 +1388,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
@@ -1397,6 +1403,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: echo.ApplyFailed,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
}, func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
||||
ctvr.TemplateID = template.ID
|
||||
})
|
||||
@@ -2579,21 +2587,11 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
||||
return r
|
||||
}
|
||||
|
||||
applyResponse := func(withAgent bool) *proto.Response {
|
||||
graphResponse := func(withAgent bool) *proto.Response {
|
||||
return &proto.Response{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{resource(withAgent)},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Presets: []*proto.Preset{{
|
||||
Name: "preset-test",
|
||||
Parameters: []*proto.PresetParameter{{Name: "k1", Value: "v1"}},
|
||||
@@ -2601,10 +2599,19 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{},
|
||||
},
|
||||
}},
|
||||
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_START: {applyResponse(true)},
|
||||
proto.WorkspaceTransition_STOP: {applyResponse(false)},
|
||||
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_START: {graphResponse(true)},
|
||||
proto.WorkspaceTransition_STOP: {graphResponse(false)},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2612,10 +2619,10 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
||||
func templateWithFailedResponseAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Presets: []*proto.Preset{
|
||||
{
|
||||
Name: "preset-test",
|
||||
|
||||
@@ -74,10 +74,14 @@ func TestRemoteConnector_Mainline(t *testing.T) {
|
||||
c := resp.Client
|
||||
s, err := c.Session(ctx)
|
||||
require.NoError(t, err)
|
||||
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Config{Config: &sdkproto.Config{
|
||||
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Config{Config: &sdkproto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Init{Init: &sdkproto.InitRequest{
|
||||
TemplateSourceArchive: arc,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
_, err = s.Recv()
|
||||
require.NoError(t, err)
|
||||
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Parse{Parse: &sdkproto.ParseRequest{}}})
|
||||
require.NoError(t, err)
|
||||
r, err := s.Recv()
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestDERP(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -411,7 +411,7 @@ func TestDERPEndToEnd(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
+246
-47
@@ -12,6 +12,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/xerrors"
|
||||
protobuf "google.golang.org/protobuf/proto"
|
||||
|
||||
@@ -21,12 +22,12 @@ import (
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
// ProvisionApplyWithAgent returns provision responses that will mock a fake
|
||||
// ProvisionGraphWithAgentAndAPIKeyScope returns provision responses that will mock a fake
|
||||
// "aws_instance" resource with an agent that has the given auth token.
|
||||
func ProvisionApplyWithAgentAndAPIKeyScope(authToken string, apiKeyScope string) []*proto.Response {
|
||||
func ProvisionGraphWithAgentAndAPIKeyScope(authToken string, apiKeyScope string) []*proto.Response {
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example_with_scope",
|
||||
Type: "aws_instance",
|
||||
@@ -44,24 +45,29 @@ func ProvisionApplyWithAgentAndAPIKeyScope(authToken string, apiKeyScope string)
|
||||
}}
|
||||
}
|
||||
|
||||
// ProvisionApplyWithAgent returns provision responses that will mock a fake
|
||||
// ProvisionGraphWithAgent returns provision responses that will mock a fake
|
||||
// "aws_instance" resource with an agent that has the given auth token.
|
||||
func ProvisionApplyWithAgent(authToken string) []*proto.Response {
|
||||
func ProvisionGraphWithAgent(authToken string, muts ...func(g *proto.GraphComplete)) []*proto.Response {
|
||||
gc := &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Name: "example",
|
||||
Auth: &proto.Agent_Token{
|
||||
Token: authToken,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
for _, mut := range muts {
|
||||
mut(gc)
|
||||
}
|
||||
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Name: "example",
|
||||
Auth: &proto.Agent_Token{
|
||||
Token: authToken,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: gc,
|
||||
},
|
||||
}}
|
||||
}
|
||||
@@ -73,12 +79,19 @@ var (
|
||||
Parse: &proto.ParseComplete{},
|
||||
},
|
||||
}}
|
||||
// InitComplete is a helper to indicate an empty init completion.
|
||||
InitComplete = []*proto.Response{{
|
||||
Type: &proto.Response_Init{
|
||||
Init: &proto.InitComplete{
|
||||
ModuleFiles: []byte{},
|
||||
},
|
||||
},
|
||||
}}
|
||||
// PlanComplete is a helper to indicate an empty provision completion.
|
||||
PlanComplete = []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Plan: []byte("{}"),
|
||||
ModuleFiles: []byte{},
|
||||
Plan: []byte("{}"),
|
||||
},
|
||||
},
|
||||
}}
|
||||
@@ -88,7 +101,19 @@ var (
|
||||
Apply: &proto.ApplyComplete{},
|
||||
},
|
||||
}}
|
||||
GraphComplete = []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{},
|
||||
},
|
||||
}}
|
||||
|
||||
InitFailed = []*proto.Response{{
|
||||
Type: &proto.Response_Init{
|
||||
Init: &proto.InitComplete{
|
||||
Error: "failed!",
|
||||
},
|
||||
},
|
||||
}}
|
||||
// PlanFailed is a helper to convey a failed plan operation
|
||||
PlanFailed = []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
@@ -105,6 +130,13 @@ var (
|
||||
},
|
||||
},
|
||||
}}
|
||||
GraphFailed = []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Error: "failed!",
|
||||
},
|
||||
},
|
||||
}}
|
||||
)
|
||||
|
||||
// Serve starts the echo provisioner.
|
||||
@@ -174,6 +206,59 @@ func (*echo) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan
|
||||
return provisionersdk.ParseErrorf("complete response missing")
|
||||
}
|
||||
|
||||
func (*echo) Init(sess *provisionersdk.Session, req *proto.InitRequest, canceledOrComplete <-chan struct{}) *proto.InitComplete {
|
||||
err := sess.Files.ExtractArchive(sess.Context(), sess.Logger, afero.NewOsFs(), req.TemplateSourceArchive)
|
||||
if err != nil {
|
||||
return provisionersdk.InitErrorf("extract archive: %s", err.Error())
|
||||
}
|
||||
|
||||
responses, err := readResponses(
|
||||
sess,
|
||||
"", // transition not supported for init graph responses
|
||||
"init.protobuf")
|
||||
if err != nil {
|
||||
return &proto.InitComplete{Error: err.Error()}
|
||||
}
|
||||
for _, response := range responses {
|
||||
if log := response.GetLog(); log != nil {
|
||||
sess.ProvisionLog(log.Level, log.Output)
|
||||
}
|
||||
if complete := response.GetInit(); complete != nil {
|
||||
return complete
|
||||
}
|
||||
}
|
||||
|
||||
// some tests use Echo without a complete response to test cancel
|
||||
<-canceledOrComplete
|
||||
return provisionersdk.InitErrorf("canceled")
|
||||
}
|
||||
|
||||
func (*echo) Graph(sess *provisionersdk.Session, req *proto.GraphRequest, canceledOrComplete <-chan struct{}) *proto.GraphComplete {
|
||||
responses, err := readResponses(
|
||||
sess,
|
||||
strings.ToLower(req.GetMetadata().GetWorkspaceTransition().String()),
|
||||
"graph.protobuf")
|
||||
if err != nil {
|
||||
return &proto.GraphComplete{Error: err.Error()}
|
||||
}
|
||||
for _, response := range responses {
|
||||
if log := response.GetLog(); log != nil {
|
||||
sess.ProvisionLog(log.Level, log.Output)
|
||||
}
|
||||
if complete := response.GetGraph(); complete != nil {
|
||||
if len(complete.AiTasks) > 0 {
|
||||
// These two fields are linked; if there are AI tasks, indicate that.
|
||||
complete.HasAiTasks = true
|
||||
}
|
||||
return complete
|
||||
}
|
||||
}
|
||||
|
||||
// some tests use Echo without a complete response to test cancel
|
||||
<-canceledOrComplete
|
||||
return provisionersdk.GraphError("canceled")
|
||||
}
|
||||
|
||||
// Plan reads requests from the provided directory to stream responses.
|
||||
func (*echo) Plan(sess *provisionersdk.Session, req *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete {
|
||||
responses, err := readResponses(
|
||||
@@ -228,19 +313,73 @@ func (*echo) Shutdown(_ context.Context, _ *proto.Empty) (*proto.Empty, error) {
|
||||
type Responses struct {
|
||||
Parse []*proto.Response
|
||||
|
||||
// ProvisionApply and ProvisionPlan are used to mock ALL responses of
|
||||
// Apply and Plan, regardless of transition.
|
||||
ProvisionApply []*proto.Response
|
||||
// Used to mock ALL responses regardless of transition.
|
||||
ProvisionInit []*proto.Response
|
||||
ProvisionPlan []*proto.Response
|
||||
ProvisionApply []*proto.Response
|
||||
ProvisionGraph []*proto.Response
|
||||
|
||||
// ProvisionApplyMap and ProvisionPlanMap are used to mock specific
|
||||
// transition responses. They are prioritized over the generic responses.
|
||||
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
|
||||
// Used to mock specific transition responses. They are prioritized over the generic responses.
|
||||
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
|
||||
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
|
||||
ProvisionGraphMap map[proto.WorkspaceTransition][]*proto.Response
|
||||
|
||||
ExtraFiles map[string][]byte
|
||||
}
|
||||
|
||||
func isType[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *Responses) Valid() error {
|
||||
isLog := isType[*proto.Response_Log]
|
||||
isParse := isType[*proto.Response_Parse]
|
||||
isInit := isType[*proto.Response_Init]
|
||||
isDataUpload := isType[*proto.Response_DataUpload]
|
||||
isChunkPiece := isType[*proto.Response_ChunkPiece]
|
||||
isPlan := isType[*proto.Response_Plan]
|
||||
isApply := isType[*proto.Response_Apply]
|
||||
isGraph := isType[*proto.Response_Graph]
|
||||
|
||||
for _, parse := range r.Parse {
|
||||
ty := parse.Type
|
||||
if !(isParse(ty) || isLog(ty)) {
|
||||
return xerrors.Errorf("invalid parse response type: %T", ty)
|
||||
}
|
||||
}
|
||||
|
||||
for _, init := range r.ProvisionInit {
|
||||
ty := init.Type
|
||||
if !(isInit(ty) || isLog(ty) || isChunkPiece(ty) || isDataUpload(ty)) {
|
||||
return xerrors.Errorf("invalid init response type: %T", ty)
|
||||
}
|
||||
}
|
||||
|
||||
for _, plan := range r.ProvisionPlan {
|
||||
ty := plan.Type
|
||||
if !(isPlan(ty) || isLog(ty)) {
|
||||
return xerrors.Errorf("invalid plan response type: %T", ty)
|
||||
}
|
||||
}
|
||||
|
||||
for _, apply := range r.ProvisionApply {
|
||||
ty := apply.Type
|
||||
if !(isApply(ty) || isLog(ty)) {
|
||||
return xerrors.Errorf("invalid apply response type: %T", ty)
|
||||
}
|
||||
}
|
||||
|
||||
for _, graph := range r.ProvisionGraph {
|
||||
ty := graph.Type
|
||||
if !(isGraph(ty) || isLog(ty)) {
|
||||
return xerrors.Errorf("invalid graph response type: %T", ty)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tar returns a tar archive of responses to provisioner operations.
|
||||
func Tar(responses *Responses) ([]byte, error) {
|
||||
logger := slog.Make()
|
||||
@@ -255,31 +394,56 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
if responses == nil {
|
||||
responses = &Responses{
|
||||
Parse: ParseComplete,
|
||||
ProvisionApply: ApplyComplete,
|
||||
ProvisionInit: InitComplete,
|
||||
ProvisionPlan: PlanComplete,
|
||||
ProvisionApply: ApplyComplete,
|
||||
ProvisionGraph: GraphComplete,
|
||||
ProvisionApplyMap: nil,
|
||||
ProvisionPlanMap: nil,
|
||||
ExtraFiles: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sane defaults for missing responses.
|
||||
if responses.Parse == nil {
|
||||
responses.Parse = ParseComplete
|
||||
}
|
||||
if responses.ProvisionInit == nil {
|
||||
responses.ProvisionInit = InitComplete
|
||||
}
|
||||
if responses.ProvisionPlan == nil {
|
||||
for _, resp := range responses.ProvisionApply {
|
||||
responses.ProvisionPlan = PlanComplete
|
||||
|
||||
// If a graph response exists, make sure it matches the plan.
|
||||
for _, resp := range responses.ProvisionGraph {
|
||||
if resp.GetLog() != nil {
|
||||
responses.ProvisionPlan = append(responses.ProvisionPlan, resp)
|
||||
continue
|
||||
}
|
||||
responses.ProvisionPlan = append(responses.ProvisionPlan, &proto.Response{
|
||||
Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
Error: resp.GetApply().GetError(),
|
||||
Resources: resp.GetApply().GetResources(),
|
||||
Parameters: resp.GetApply().GetParameters(),
|
||||
ExternalAuthProviders: resp.GetApply().GetExternalAuthProviders(),
|
||||
Plan: []byte("{}"),
|
||||
ModuleFiles: []byte{},
|
||||
}},
|
||||
})
|
||||
if g := resp.GetGraph(); g != nil {
|
||||
dailycost := int32(0)
|
||||
for _, r := range g.GetResources() {
|
||||
dailycost += r.DailyCost
|
||||
}
|
||||
responses.ProvisionPlan = []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Plan: []byte("{}"),
|
||||
//nolint:gosec // the number of resources will not exceed int32
|
||||
AiTaskCount: int32(len(g.GetAiTasks())),
|
||||
DailyCost: dailycost,
|
||||
},
|
||||
},
|
||||
}}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if responses.ProvisionApply == nil {
|
||||
responses.ProvisionApply = ApplyComplete
|
||||
}
|
||||
if responses.ProvisionGraph == nil {
|
||||
responses.ProvisionGraph = GraphComplete
|
||||
}
|
||||
|
||||
for _, resp := range responses.ProvisionPlan {
|
||||
plan := resp.GetPlan()
|
||||
@@ -315,6 +479,13 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := new(proto.Response)
|
||||
err = protobuf.Unmarshal(data, response)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("you must have saved the wrong type, the proto cannot unmarshal: %w", err)
|
||||
}
|
||||
|
||||
logger.Debug(context.Background(), "proto written", slog.F("name", name), slog.F("bytes_written", n))
|
||||
|
||||
return nil
|
||||
@@ -325,6 +496,12 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, response := range responses.ProvisionInit {
|
||||
err := writeProto(fmt.Sprintf("%d.init.protobuf", index), response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, response := range responses.ProvisionApply {
|
||||
err := writeProto(fmt.Sprintf("%d.apply.protobuf", index), response)
|
||||
if err != nil {
|
||||
@@ -337,6 +514,12 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, response := range responses.ProvisionGraph {
|
||||
err := writeProto(fmt.Sprintf("%d.graph.protobuf", index), response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for trans, m := range responses.ProvisionApplyMap {
|
||||
for i, rs := range m {
|
||||
err := writeProto(fmt.Sprintf("%d.%s.apply.protobuf", i, strings.ToLower(trans.String())), rs)
|
||||
@@ -360,6 +543,14 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
}
|
||||
}
|
||||
}
|
||||
for trans, m := range responses.ProvisionGraphMap {
|
||||
for i, resp := range m {
|
||||
err := writeProto(fmt.Sprintf("%d.%s.graph.protobuf", i, strings.ToLower(trans.String())), resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
dirs := []string{}
|
||||
for name, content := range responses.ExtraFiles {
|
||||
logger.Debug(ctx, "extra file", slog.F("name", name))
|
||||
@@ -401,8 +592,8 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
||||
// that matches the parameters defined in the responses. Dynamic parameters
|
||||
// parsed these, even in the echo provisioner.
|
||||
var mainTF bytes.Buffer
|
||||
for _, respPlan := range responses.ProvisionPlan {
|
||||
plan := respPlan.GetPlan()
|
||||
for _, respPlan := range responses.ProvisionGraph {
|
||||
plan := respPlan.GetGraph()
|
||||
if plan == nil {
|
||||
continue
|
||||
}
|
||||
@@ -440,6 +631,11 @@ terraform {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := responses.Valid(); err != nil {
|
||||
return nil, xerrors.Errorf("responses invalid: %w", err)
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -508,13 +704,14 @@ data "coder_parameter" "{{ .Name }}" {
|
||||
|
||||
func WithResources(resources []*proto.Resource) *Responses {
|
||||
return &Responses{
|
||||
Parse: ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{
|
||||
Parse: ParseComplete,
|
||||
ProvisionInit: InitComplete,
|
||||
ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{}}}},
|
||||
ProvisionGraph: []*proto.Response{{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||
Resources: resources,
|
||||
}}}},
|
||||
ProvisionPlan: []*proto.Response{{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
||||
Resources: resources,
|
||||
Plan: []byte("{}"),
|
||||
Plan: []byte("{}"),
|
||||
}}}},
|
||||
}
|
||||
}
|
||||
@@ -522,8 +719,10 @@ func WithResources(resources []*proto.Resource) *Responses {
|
||||
func WithExtraFiles(extraFiles map[string][]byte) *Responses {
|
||||
return &Responses{
|
||||
Parse: ParseComplete,
|
||||
ProvisionInit: InitComplete,
|
||||
ProvisionApply: ApplyComplete,
|
||||
ProvisionPlan: PlanComplete,
|
||||
ProvisionGraph: GraphComplete,
|
||||
ExtraFiles: extraFiles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ func TestEcho(t *testing.T) {
|
||||
},
|
||||
}
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
Parse: responses,
|
||||
Parse: responses,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
client, err := api.Session(ctx)
|
||||
@@ -65,13 +66,19 @@ func TestEcho(t *testing.T) {
|
||||
err := client.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{
|
||||
TemplateSourceArchive: data,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
log, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, responses[0].GetLog().Output, log.GetLog().Output)
|
||||
@@ -85,7 +92,7 @@ func TestEcho(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
planResponses := []*proto.Response{
|
||||
graphResponses := []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
@@ -95,27 +102,8 @@ func TestEcho(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "resource",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
applyResponses := []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "log-output",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "resource",
|
||||
}},
|
||||
@@ -123,9 +111,12 @@ func TestEcho(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
ProvisionPlan: planResponses,
|
||||
ProvisionApply: applyResponses,
|
||||
ProvisionGraph: graphResponses,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
client, err := api.Session(ctx)
|
||||
@@ -134,30 +125,38 @@ func TestEcho(t *testing.T) {
|
||||
err := client.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{
|
||||
TemplateSourceArchive: data,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
||||
require.NoError(t, err)
|
||||
log, err := client.Recv()
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, planResponses[0].GetLog().Output, log.GetLog().Output)
|
||||
complete, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, planResponses[1].GetPlan().Resources[0].Name,
|
||||
complete.GetPlan().Resources[0].Name)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
||||
require.NoError(t, err)
|
||||
log, err = client.Recv()
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, applyResponses[0].GetLog().Output, log.GetLog().Output)
|
||||
complete, err = client.Recv()
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Graph{Graph: &proto.GraphRequest{
|
||||
Source: proto.GraphSource_SOURCE_STATE,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, applyResponses[1].GetApply().Resources[0].Name,
|
||||
complete.GetApply().Resources[0].Name)
|
||||
|
||||
log, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, graphResponses[0].GetLog().Output, log.GetLog().Output)
|
||||
complete, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, graphResponses[1].GetGraph().Resources[0].Name,
|
||||
complete.GetGraph().Resources[0].Name)
|
||||
})
|
||||
|
||||
t.Run("ProvisionStop", func(t *testing.T) {
|
||||
@@ -165,13 +164,11 @@ func TestEcho(t *testing.T) {
|
||||
|
||||
// Stop responses should be returned when the workspace is being stopped.
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
ProvisionApply: applyCompleteResource("DEFAULT"),
|
||||
ProvisionPlan: planCompleteResource("DEFAULT"),
|
||||
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_STOP: planCompleteResource("STOP"),
|
||||
},
|
||||
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_STOP: applyCompleteResource("STOP"),
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionGraph: graphCompleteResource("DEFAULT"),
|
||||
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||
proto.WorkspaceTransition_STOP: graphCompleteResource("STOP"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -182,10 +179,15 @@ func TestEcho(t *testing.T) {
|
||||
err := client.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{
|
||||
TemplateSourceArchive: data,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Do stop.
|
||||
err = client.Send(&proto.Request{
|
||||
@@ -199,17 +201,32 @@ func TestEcho(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{
|
||||
Type: &proto.Request_Graph{
|
||||
Graph: &proto.GraphRequest{
|
||||
Metadata: &proto.Metadata{
|
||||
WorkspaceTransition: proto.WorkspaceTransition_STOP,
|
||||
},
|
||||
Source: proto.GraphSource_SOURCE_STATE,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
complete, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t,
|
||||
"STOP",
|
||||
complete.GetPlan().Resources[0].Name,
|
||||
complete.GetGraph().Resources[0].Name,
|
||||
)
|
||||
|
||||
// Do start.
|
||||
err = client.Send(&proto.Request{
|
||||
Type: &proto.Request_Plan{
|
||||
Plan: &proto.PlanRequest{
|
||||
Type: &proto.Request_Graph{
|
||||
Graph: &proto.GraphRequest{
|
||||
Metadata: &proto.Metadata{
|
||||
WorkspaceTransition: proto.WorkspaceTransition_START,
|
||||
},
|
||||
@@ -222,7 +239,7 @@ func TestEcho(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t,
|
||||
"DEFAULT",
|
||||
complete.GetPlan().Resources[0].Name,
|
||||
complete.GetGraph().Resources[0].Name,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -246,8 +263,8 @@ func TestEcho(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "resource",
|
||||
}},
|
||||
@@ -256,7 +273,9 @@ func TestEcho(t *testing.T) {
|
||||
}}
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: responses,
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: responses,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
client, err := api.Session(ctx)
|
||||
@@ -266,11 +285,17 @@ func TestEcho(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
|
||||
TemplateSourceArchive: data,
|
||||
ProvisionerLogLevel: "debug",
|
||||
ProvisionerLogLevel: "debug",
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{
|
||||
TemplateSourceArchive: data,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Plan is required before apply
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
||||
require.NoError(t, err)
|
||||
@@ -280,33 +305,29 @@ func TestEcho(t *testing.T) {
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Send(&proto.Request{Type: &proto.Request_Graph{Graph: &proto.GraphRequest{
|
||||
Source: proto.GraphSource_SOURCE_STATE,
|
||||
}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
log, err := client.Recv()
|
||||
require.NoError(t, err)
|
||||
// Skip responses[0] as it's trace level
|
||||
require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output)
|
||||
complete, err = client.Recv()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, responses[2].GetApply().Resources[0].Name,
|
||||
complete.GetApply().Resources[0].Name)
|
||||
require.Equal(t, responses[2].GetGraph().Resources[0].Name,
|
||||
complete.GetGraph().Resources[0].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func planCompleteResource(name string) []*proto.Response {
|
||||
func graphCompleteResource(name string) []*proto.Response {
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: name,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func applyCompleteResource(name string) []*proto.Response {
|
||||
return []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: name,
|
||||
}},
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/provisionersdk/tfpath"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
@@ -283,7 +282,7 @@ func (e *executor) init(ctx, killCtx context.Context, logr logSink) error {
|
||||
func checksumFileCRC32(ctx context.Context, logger slog.Logger, path string) uint32 {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
logger.Debug(ctx, "file %s does not exist or can't be read, skip checksum calculation")
|
||||
logger.Debug(ctx, "file does not exist or can't be read, skip checksum calculation", slog.F("path", path))
|
||||
return 0
|
||||
}
|
||||
return crc32.ChecksumIEEE(content)
|
||||
@@ -330,34 +329,16 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
|
||||
return nil, xerrors.Errorf("terraform plan: %w", err)
|
||||
}
|
||||
|
||||
// Capture the duration of the call to `terraform graph`.
|
||||
graphTimings := newTimingAggregator(database.ProvisionerJobTimingStageGraph)
|
||||
graphTimings.ingest(createGraphTimingsEvent(timingGraphStart))
|
||||
|
||||
state, plan, err := e.planResources(ctx, killCtx, planfilePath)
|
||||
plan, err := e.parsePlan(ctx, killCtx, planfilePath)
|
||||
if err != nil {
|
||||
graphTimings.ingest(createGraphTimingsEvent(timingGraphErrored))
|
||||
return nil, xerrors.Errorf("plan resources: %w", err)
|
||||
return nil, xerrors.Errorf("show terraform plan file: %w", err)
|
||||
}
|
||||
|
||||
planJSON, err := json.Marshal(plan)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("marshal plan: %w", err)
|
||||
}
|
||||
|
||||
graphTimings.ingest(createGraphTimingsEvent(timingGraphComplete))
|
||||
|
||||
var moduleFiles []byte
|
||||
// Skipping modules archiving is useful if the caller does not need it, eg during
|
||||
// a workspace build. This removes some added costs of sending the modules
|
||||
// payload back to coderd if coderd is just going to ignore it.
|
||||
if !req.OmitModuleFiles {
|
||||
moduleFiles, err = GetModulesArchive(os.DirFS(e.files.WorkDirectory()))
|
||||
if err != nil {
|
||||
// TODO: we probably want to persist this error or make it louder eventually
|
||||
e.logger.Warn(ctx, "failed to archive terraform modules", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// When a prebuild claim attempt is made, log a warning if a resource is due to be replaced, since this will obviate
|
||||
// the point of prebuilding if the expensive resource is replaced once claimed!
|
||||
var (
|
||||
@@ -384,18 +365,16 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
|
||||
}
|
||||
}
|
||||
|
||||
state, err := ConvertPlanState(plan)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("convert plan state: %w", err)
|
||||
}
|
||||
|
||||
msg := &proto.PlanComplete{
|
||||
Parameters: state.Parameters,
|
||||
Resources: state.Resources,
|
||||
ExternalAuthProviders: state.ExternalAuthProviders,
|
||||
Timings: graphTimings.aggregate(),
|
||||
Presets: state.Presets,
|
||||
Plan: planJSON,
|
||||
ResourceReplacements: resReps,
|
||||
ModuleFiles: moduleFiles,
|
||||
HasAiTasks: state.HasAITasks,
|
||||
AiTasks: state.AITasks,
|
||||
HasExternalAgents: state.HasExternalAgents,
|
||||
Plan: planJSON,
|
||||
DailyCost: state.DailyCost,
|
||||
ResourceReplacements: resReps,
|
||||
AiTaskCount: state.AITaskCount,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
@@ -418,42 +397,6 @@ func onlyDataResources(sm tfjson.StateModule) tfjson.StateModule {
|
||||
return filtered
|
||||
}
|
||||
|
||||
// planResources must only be called while the lock is held.
|
||||
func (e *executor) planResources(ctx, killCtx context.Context, planfilePath string) (*State, *tfjson.Plan, error) {
|
||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
|
||||
plan, err := e.parsePlan(ctx, killCtx, planfilePath)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("show terraform plan file: %w", err)
|
||||
}
|
||||
|
||||
rawGraph, err := e.graph(ctx, killCtx)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("graph: %w", err)
|
||||
}
|
||||
modules := []*tfjson.StateModule{}
|
||||
if plan.PriorState != nil {
|
||||
// We need the data resources for rich parameters. For some reason, they
|
||||
// only show up in the PriorState.
|
||||
//
|
||||
// We don't want all prior resources, because Quotas (and
|
||||
// future features) would never know which resources are getting
|
||||
// deleted by a stop.
|
||||
|
||||
filtered := onlyDataResources(*plan.PriorState.Values.RootModule)
|
||||
modules = append(modules, &filtered)
|
||||
}
|
||||
modules = append(modules, plan.PlannedValues.RootModule)
|
||||
|
||||
state, err := ConvertState(ctx, modules, rawGraph, e.server.logger)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return state, plan, nil
|
||||
}
|
||||
|
||||
// parsePlan must only be called while the lock is held.
|
||||
func (e *executor) parsePlan(ctx, killCtx context.Context, planfilePath string) (*tfjson.Plan, error) {
|
||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
||||
@@ -541,9 +484,11 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
|
||||
// TODO: When the plan is present, we should probably use it?
|
||||
// "-plan=" + e.files.PlanFilePath(),
|
||||
}
|
||||
|
||||
if ver.GreaterThanOrEqual(version170) {
|
||||
args = append(args, "-type=plan")
|
||||
}
|
||||
|
||||
var out strings.Builder
|
||||
cmd := exec.CommandContext(killCtx, e.binaryPath, args...) // #nosec
|
||||
cmd.Stdout = &out
|
||||
@@ -602,11 +547,6 @@ func (e *executor) apply(
|
||||
return nil, xerrors.Errorf("terraform apply: %w", err)
|
||||
}
|
||||
|
||||
// `terraform show` & `terraform graph`
|
||||
state, err := e.stateResources(ctx, killCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statefilePath := e.files.StateFilePath()
|
||||
stateContent, err := os.ReadFile(statefilePath)
|
||||
if err != nil {
|
||||
@@ -614,41 +554,10 @@ func (e *executor) apply(
|
||||
}
|
||||
|
||||
return &proto.ApplyComplete{
|
||||
Parameters: state.Parameters,
|
||||
Resources: state.Resources,
|
||||
ExternalAuthProviders: state.ExternalAuthProviders,
|
||||
State: stateContent,
|
||||
AiTasks: state.AITasks,
|
||||
State: stateContent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// stateResources must only be called while the lock is held.
|
||||
func (e *executor) stateResources(ctx, killCtx context.Context) (*State, error) {
|
||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
|
||||
state, err := e.state(ctx, killCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawGraph, err := e.graph(ctx, killCtx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get terraform graph: %w", err)
|
||||
}
|
||||
converted := &State{}
|
||||
if state.Values == nil {
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
converted, err = ConvertState(ctx, []*tfjson.StateModule{
|
||||
state.Values.RootModule,
|
||||
}, rawGraph, e.server.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
// state must only be called while the lock is held.
|
||||
func (e *executor) state(ctx, killCtx context.Context) (*tfjson.State, error) {
|
||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
||||
|
||||
@@ -4,6 +4,8 @@ package terraform_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -15,14 +17,19 @@ import (
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, api := setupProvisioner(t, nil)
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||
// Fake all actual terraform, since parse doesn't need it.
|
||||
binaryPath: filepath.Join(cwd, "testdata", "timings-aggregation", "fake-terraform.sh"),
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Files map[string]string
|
||||
Response *proto.ParseComplete
|
||||
// If ErrorContains is not empty, then the ParseComplete should have an Error containing the given string
|
||||
ErrorContains string
|
||||
Name string
|
||||
Files map[string]string
|
||||
Response *proto.ParseComplete
|
||||
ParseErrorContains string
|
||||
}{
|
||||
{
|
||||
Name: "single-variable",
|
||||
@@ -63,6 +70,7 @@ func TestParse(t *testing.T) {
|
||||
"main.tf": `variable "A" {
|
||||
validation {
|
||||
condition = var.A == "value"
|
||||
error_message = "A must be 'value'"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
@@ -80,7 +88,7 @@ func TestParse(t *testing.T) {
|
||||
Files: map[string]string{
|
||||
"main.tf": "a;sd;ajsd;lajsd;lasjdf;a",
|
||||
},
|
||||
ErrorContains: `The ";" character is not valid.`,
|
||||
ParseErrorContains: `The ";" character is not valid.`,
|
||||
},
|
||||
{
|
||||
Name: "multiple-variables",
|
||||
@@ -205,6 +213,8 @@ func TestParse(t *testing.T) {
|
||||
{
|
||||
Name: "workspace-tags",
|
||||
Files: map[string]string{
|
||||
`main.tf`: `
|
||||
`,
|
||||
"parameters.tf": `data "coder_parameter" "os_selector" {
|
||||
name = "os_selector"
|
||||
display_name = "Operating System"
|
||||
@@ -266,7 +276,6 @@ func TestParse(t *testing.T) {
|
||||
Name: "workspace-tags-in-a-single-file",
|
||||
Files: map[string]string{
|
||||
"main.tf": `
|
||||
|
||||
data "coder_parameter" "os_selector" {
|
||||
name = "os_selector"
|
||||
display_name = "Operating System"
|
||||
@@ -330,7 +339,6 @@ func TestParse(t *testing.T) {
|
||||
Name: "workspace-tags-duplicate-tag",
|
||||
Files: map[string]string{
|
||||
"main.tf": `
|
||||
|
||||
data "coder_workspace_tags" "custom_workspace_tags" {
|
||||
tags = {
|
||||
"cluster" = "developers"
|
||||
@@ -341,23 +349,22 @@ func TestParse(t *testing.T) {
|
||||
}
|
||||
`,
|
||||
},
|
||||
ErrorContains: `workspace tag "debug" is defined multiple times`,
|
||||
ParseErrorContains: `workspace tag "debug" is defined multiple times`,
|
||||
},
|
||||
{
|
||||
Name: "workspace-tags-wrong-tag-format",
|
||||
Files: map[string]string{
|
||||
"main.tf": `
|
||||
|
||||
data "coder_workspace_tags" "custom_workspace_tags" {
|
||||
tags {
|
||||
cluster = "developers"
|
||||
debug = "yes"
|
||||
cache = "no-cache"
|
||||
data "coder_workspace_tags" "custom_workspace_tags" {
|
||||
tags {
|
||||
cluster = "developers"
|
||||
debug = "yes"
|
||||
cache = "no-cache"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
ErrorContains: `"tags" attribute is required by coder_workspace_tags`,
|
||||
ParseErrorContains: `"tags" attribute is required by coder_workspace_tags`,
|
||||
},
|
||||
{
|
||||
Name: "empty-main",
|
||||
@@ -379,27 +386,38 @@ func TestParse(t *testing.T) {
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
session := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, testCase.Files),
|
||||
})
|
||||
session := configure(ctx, t, api, &proto.Config{})
|
||||
err := sendInit(session, testutil.CreateTar(t, testCase.Files))
|
||||
require.NoError(t, err)
|
||||
|
||||
err := session.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||
// Init stage -- a fake terraform, will always succeed quickly.
|
||||
for {
|
||||
msg, err := session.Recv()
|
||||
require.NoError(t, err)
|
||||
if msgLog, ok := msg.Type.(*proto.Response_Log); ok {
|
||||
t.Logf("init log: %s", msgLog.Log.Output)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
err = session.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
for {
|
||||
msg, err := session.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
if testCase.ErrorContains != "" {
|
||||
require.Contains(t, msg.GetParse().GetError(), testCase.ErrorContains)
|
||||
break
|
||||
}
|
||||
|
||||
// Ignore logs in this test
|
||||
if msg.GetLog() != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if testCase.ParseErrorContains != "" {
|
||||
require.Contains(t, msg.GetParse().GetError(), testCase.ParseErrorContains)
|
||||
return // Stop test at this point
|
||||
}
|
||||
|
||||
// Ensure the want and got are equivalent!
|
||||
want, err := json.Marshal(testCase.Response)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type PlanState struct {
|
||||
DailyCost int32
|
||||
AITaskCount int32
|
||||
}
|
||||
|
||||
func planModules(plan *tfjson.Plan) []*tfjson.StateModule {
|
||||
modules := []*tfjson.StateModule{}
|
||||
if plan.PriorState != nil {
|
||||
// We need the data resources for rich parameters. For some reason, they
|
||||
// only show up in the PriorState.
|
||||
//
|
||||
// We don't want all prior resources, because Quotas (and
|
||||
// future features) would never know which resources are getting
|
||||
// deleted by a stop.
|
||||
|
||||
filtered := onlyDataResources(*plan.PriorState.Values.RootModule)
|
||||
modules = append(modules, &filtered)
|
||||
}
|
||||
modules = append(modules, plan.PlannedValues.RootModule)
|
||||
return modules
|
||||
}
|
||||
|
||||
// ConvertPlanState consumes a terraform plan json output and produces a thinner
|
||||
// version of `State` to be used before `terraform apply`. `ConvertState`
|
||||
// requires `terraform graph`, this does not.
|
||||
func ConvertPlanState(plan *tfjson.Plan) (*PlanState, error) {
|
||||
modules := planModules(plan)
|
||||
|
||||
var dailyCost int32
|
||||
var aiTaskCount int32
|
||||
for _, mod := range modules {
|
||||
err := forEachResource(mod, func(res *tfjson.StateResource) error {
|
||||
switch res.Type {
|
||||
case "coder_metadata":
|
||||
var attrs resourceMetadataAttributes
|
||||
err := mapstructure.Decode(res.AttributeValues, &attrs)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decode metadata attributes: %w", err)
|
||||
}
|
||||
dailyCost += attrs.DailyCost
|
||||
case "coder_ai_task":
|
||||
aiTaskCount++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse plan: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &PlanState{
|
||||
DailyCost: dailyCost,
|
||||
AITaskCount: aiTaskCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func forEachResource(input *tfjson.StateModule, do func(res *tfjson.StateResource) error) error {
|
||||
for _, res := range input.Resources {
|
||||
err := do(res)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("in module %s: %w", input.Address, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mod := range input.ChildModules {
|
||||
err := forEachResource(mod, do)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("in module %s: %w", mod.Address, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -67,51 +68,34 @@ func (s *server) setupContexts(parent context.Context, canceledOrComplete <-chan
|
||||
return ctx, cancel, killCtx, kill
|
||||
}
|
||||
|
||||
func (s *server) Plan(
|
||||
sess *provisionersdk.Session, request *proto.PlanRequest, canceledOrComplete <-chan struct{},
|
||||
) *proto.PlanComplete {
|
||||
func (s *server) Init(
|
||||
sess *provisionersdk.Session, request *proto.InitRequest, canceledOrComplete <-chan struct{},
|
||||
) *proto.InitComplete {
|
||||
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
|
||||
defer span.End()
|
||||
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
|
||||
defer cancel()
|
||||
defer kill()
|
||||
|
||||
e := s.executor(sess.Files, database.ProvisionerJobTimingStagePlan)
|
||||
e := s.executor(sess.Files, database.ProvisionerJobTimingStageInit)
|
||||
if err := e.checkMinVersion(ctx); err != nil {
|
||||
return provisionersdk.PlanErrorf("%s", err.Error())
|
||||
return provisionersdk.InitErrorf("%s", err.Error())
|
||||
}
|
||||
logTerraformEnvVars(sess)
|
||||
|
||||
// If we're destroying, exit early if there's no state. This is necessary to
|
||||
// avoid any cases where a workspace is "locked out" of terraform due to
|
||||
// e.g. bad template param values and cannot be deleted. This is just for
|
||||
// contingency, in the future we will try harder to prevent workspaces being
|
||||
// broken this hard.
|
||||
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(sess.Config.State) == 0 {
|
||||
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform state does not exist, there is nothing to do")
|
||||
return &proto.PlanComplete{}
|
||||
}
|
||||
|
||||
statefilePath := sess.Files.StateFilePath()
|
||||
if len(sess.Config.State) > 0 {
|
||||
err := os.WriteFile(statefilePath, sess.Config.State, 0o600)
|
||||
if err != nil {
|
||||
return provisionersdk.PlanErrorf("write statefile %q: %s", statefilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
err := CleanStaleTerraformPlugins(sess.Context(), s.cachePath, afero.NewOsFs(), time.Now(), s.logger)
|
||||
// TODO: These logs should probably be streamed back to the provisioner runner.
|
||||
err := sess.Files.ExtractArchive(ctx, s.logger, afero.NewOsFs(), request.GetTemplateSourceArchive())
|
||||
if err != nil {
|
||||
return provisionersdk.PlanErrorf("unable to clean stale Terraform plugins: %s", err)
|
||||
return provisionersdk.InitErrorf("extract template archive: %s", err)
|
||||
}
|
||||
|
||||
s.logger.Debug(ctx, "running initialization")
|
||||
|
||||
// The JSON output of `terraform init` doesn't include discrete fields for capturing timings of each plugin,
|
||||
// so we capture the whole init process.
|
||||
initTimings := newTimingAggregator(database.ProvisionerJobTimingStageInit)
|
||||
endStage := initTimings.startStage(database.ProvisionerJobTimingStageInit)
|
||||
err = CleanStaleTerraformPlugins(sess.Context(), s.cachePath, afero.NewOsFs(), time.Now(), s.logger)
|
||||
if err != nil {
|
||||
return provisionersdk.InitErrorf("unable to clean stale Terraform plugins: %s", err)
|
||||
}
|
||||
|
||||
s.logger.Debug(ctx, "running terraform initialization")
|
||||
endStage := e.timings.startStage(database.ProvisionerJobTimingStageInit)
|
||||
err = e.init(ctx, killCtx, sess)
|
||||
endStage(err)
|
||||
if err != nil {
|
||||
@@ -137,7 +121,7 @@ func (s *server) Plan(
|
||||
slog.F("provider_coder_stacktrace", stacktrace),
|
||||
)
|
||||
}
|
||||
return provisionersdk.PlanErrorf("initialize terraform: %s", err)
|
||||
return provisionersdk.InitErrorf("initialize terraform: %s", err)
|
||||
}
|
||||
|
||||
modules, err := getModules(sess.Files)
|
||||
@@ -147,8 +131,61 @@ func (s *server) Plan(
|
||||
s.logger.Error(ctx, "failed to get modules from disk", slog.Error(err))
|
||||
}
|
||||
|
||||
var moduleFiles []byte
|
||||
// Skipping modules archiving is useful if the caller does not need it, eg during
|
||||
// a workspace build. This removes some added costs of sending the modules
|
||||
// payload back to coderd if coderd is just going to ignore it.
|
||||
if !request.OmitModuleFiles {
|
||||
moduleFiles, err = GetModulesArchive(os.DirFS(e.files.WorkDirectory()))
|
||||
if err != nil {
|
||||
// TODO: we probably want to persist this error or make it louder eventually
|
||||
e.logger.Warn(ctx, "failed to archive terraform modules", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Debug(ctx, "ran initialization")
|
||||
|
||||
return &proto.InitComplete{
|
||||
Timings: e.timings.aggregate(),
|
||||
Modules: modules,
|
||||
ModuleFiles: moduleFiles,
|
||||
ModuleFilesHash: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Plan(
|
||||
sess *provisionersdk.Session, request *proto.PlanRequest, canceledOrComplete <-chan struct{},
|
||||
) *proto.PlanComplete {
|
||||
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
|
||||
defer span.End()
|
||||
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
|
||||
defer cancel()
|
||||
defer kill()
|
||||
|
||||
e := s.executor(sess.Files, database.ProvisionerJobTimingStagePlan)
|
||||
if err := e.checkMinVersion(ctx); err != nil {
|
||||
return provisionersdk.PlanErrorf("%s", err.Error())
|
||||
}
|
||||
logTerraformEnvVars(sess)
|
||||
|
||||
// If we're destroying, exit early if there's no state. This is necessary to
|
||||
// avoid any cases where a workspace is "locked out" of terraform due to
|
||||
// e.g. bad template param values and cannot be deleted. This is just for
|
||||
// contingency, in the future we will try harder to prevent workspaces being
|
||||
// broken this hard.
|
||||
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(request.GetState()) == 0 {
|
||||
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform state does not exist, there is nothing to do")
|
||||
return &proto.PlanComplete{}
|
||||
}
|
||||
|
||||
statefilePath := sess.Files.StateFilePath()
|
||||
if len(request.GetState()) > 0 {
|
||||
err := os.WriteFile(statefilePath, request.GetState(), 0o600)
|
||||
if err != nil {
|
||||
return provisionersdk.PlanErrorf("write statefile %q: %s", statefilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders)
|
||||
if err != nil {
|
||||
return provisionersdk.PlanErrorf("setup env: %s", err)
|
||||
@@ -160,20 +197,78 @@ func (s *server) Plan(
|
||||
return provisionersdk.PlanErrorf("plan vars: %s", err)
|
||||
}
|
||||
|
||||
endPlanStage := e.timings.startStage(database.ProvisionerJobTimingStagePlan)
|
||||
endStage := e.timings.startStage(database.ProvisionerJobTimingStagePlan)
|
||||
resp, err := e.plan(ctx, killCtx, env, vars, sess, request)
|
||||
endPlanStage(err)
|
||||
endStage(err)
|
||||
if err != nil {
|
||||
return provisionersdk.PlanErrorf("%s", err.Error())
|
||||
}
|
||||
|
||||
// Prepend init timings since they occur prior to plan timings.
|
||||
// Order is irrelevant; this is merely indicative.
|
||||
resp.Timings = append(resp.Timings, append(initTimings.aggregate(), e.timings.aggregate()...)...)
|
||||
resp.Modules = modules
|
||||
resp.Timings = e.timings.aggregate()
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *server) Graph(
|
||||
sess *provisionersdk.Session, request *proto.GraphRequest, canceledOrComplete <-chan struct{},
|
||||
) *proto.GraphComplete {
|
||||
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
|
||||
defer span.End()
|
||||
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
|
||||
defer cancel()
|
||||
defer kill()
|
||||
|
||||
e := s.executor(sess.Files, database.ProvisionerJobTimingStageGraph)
|
||||
if err := e.checkMinVersion(ctx); err != nil {
|
||||
return provisionersdk.GraphError("%s", err.Error())
|
||||
}
|
||||
logTerraformEnvVars(sess)
|
||||
|
||||
modules := []*tfjson.StateModule{}
|
||||
switch request.Source {
|
||||
case proto.GraphSource_SOURCE_PLAN:
|
||||
plan, err := e.parsePlan(ctx, killCtx, e.files.PlanFilePath())
|
||||
if err != nil {
|
||||
return provisionersdk.GraphError("parse plan for graph: %s", err)
|
||||
}
|
||||
|
||||
modules = planModules(plan)
|
||||
case proto.GraphSource_SOURCE_STATE:
|
||||
tfState, err := e.state(ctx, killCtx)
|
||||
if err != nil {
|
||||
return provisionersdk.GraphError("load tfstate for graph: %s", err)
|
||||
}
|
||||
if tfState.Values != nil {
|
||||
modules = []*tfjson.StateModule{tfState.Values.RootModule}
|
||||
}
|
||||
default:
|
||||
return provisionersdk.GraphError("unknown graph source: %q", request.Source.String())
|
||||
}
|
||||
|
||||
endStage := e.timings.startStage(database.ProvisionerJobTimingStageGraph)
|
||||
rawGraph, err := e.graph(ctx, killCtx)
|
||||
endStage(err)
|
||||
if err != nil {
|
||||
return provisionersdk.GraphError("generate graph: %s", err)
|
||||
}
|
||||
|
||||
state, err := ConvertState(ctx, modules, rawGraph, e.server.logger)
|
||||
if err != nil {
|
||||
return provisionersdk.GraphError("convert state for graph: %s", err)
|
||||
}
|
||||
|
||||
return &proto.GraphComplete{
|
||||
Error: "",
|
||||
Timings: e.timings.aggregate(),
|
||||
Resources: state.Resources,
|
||||
Parameters: state.Parameters,
|
||||
ExternalAuthProviders: state.ExternalAuthProviders,
|
||||
Presets: state.Presets,
|
||||
HasAiTasks: state.HasAITasks,
|
||||
AiTasks: state.AITasks,
|
||||
HasExternalAgents: state.HasExternalAgents,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Apply(
|
||||
sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{},
|
||||
) *proto.ApplyComplete {
|
||||
@@ -194,7 +289,7 @@ func (s *server) Apply(
|
||||
// e.g. bad template param values and cannot be deleted. This is just for
|
||||
// contingency, in the future we will try harder to prevent workspaces being
|
||||
// broken this hard.
|
||||
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(sess.Config.State) == 0 {
|
||||
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(request.GetState()) == 0 {
|
||||
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform plan does not exist, there is nothing to do")
|
||||
return &proto.ApplyComplete{}
|
||||
}
|
||||
@@ -217,8 +312,9 @@ func (s *server) Apply(
|
||||
// In this case, we return Complete with an explicit error message.
|
||||
stateData, _ := os.ReadFile(statefilePath)
|
||||
return &proto.ApplyComplete{
|
||||
State: stateData,
|
||||
Error: errorMessage,
|
||||
State: stateData,
|
||||
Error: errorMessage,
|
||||
Timings: e.timings.aggregate(),
|
||||
}
|
||||
}
|
||||
resp.Timings = e.timings.aggregate()
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -81,6 +82,27 @@ func setupProvisioner(t *testing.T, opts *provisionerServeOptions) (context.Cont
|
||||
return ctx, api
|
||||
}
|
||||
|
||||
// sendInitAndGetResp will send the init request and wait for and return the InitComplete response.
|
||||
func sendInitAndGetResp(t *testing.T, sess proto.DRPCProvisioner_SessionClient, archive []byte, onLog ...func(log string)) *proto.InitComplete {
|
||||
t.Helper()
|
||||
err := sendInit(sess, archive)
|
||||
require.NoError(t, err)
|
||||
for {
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
if logMsg, ok := msg.Type.(*proto.Response_Log); ok {
|
||||
for _, do := range onLog {
|
||||
do(logMsg.Log.Output)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
init := msg.GetInit()
|
||||
require.NotNil(t, init)
|
||||
return init
|
||||
}
|
||||
}
|
||||
|
||||
func configure(ctx context.Context, t *testing.T, client proto.DRPCProvisionerClient, config *proto.Config) proto.DRPCProvisioner_SessionClient {
|
||||
t.Helper()
|
||||
sess, err := client.Session(ctx)
|
||||
@@ -107,6 +129,12 @@ func readProvisionLog(t *testing.T, response proto.DRPCProvisioner_SessionClient
|
||||
return logBuf.String()
|
||||
}
|
||||
|
||||
func sendInit(sess proto.DRPCProvisioner_SessionClient, archive []byte) error {
|
||||
return sess.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{
|
||||
TemplateSourceArchive: archive,
|
||||
}}})
|
||||
}
|
||||
|
||||
func sendPlan(sess proto.DRPCProvisioner_SessionClient, transition proto.WorkspaceTransition) error {
|
||||
return sess.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
|
||||
Metadata: &proto.Metadata{WorkspaceTransition: transition},
|
||||
@@ -119,6 +147,12 @@ func sendApply(sess proto.DRPCProvisioner_SessionClient, transition proto.Worksp
|
||||
}}})
|
||||
}
|
||||
|
||||
func sendGraph(sess proto.DRPCProvisioner_SessionClient, source proto.GraphSource) error {
|
||||
return sess.Send(&proto.Request{Type: &proto.Request_Graph{Graph: &proto.GraphRequest{
|
||||
Source: source,
|
||||
}}})
|
||||
}
|
||||
|
||||
// below we exec fake_cancel.sh, which causes the kernel to execute it, and if more than
|
||||
// one process tries to do this simultaneously, it can cause "text file busy"
|
||||
// nolint: paralleltest
|
||||
@@ -161,30 +195,46 @@ func TestProvision_Cancel(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
t.Logf("wrote fake terraform script to %s", binPath)
|
||||
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).
|
||||
With(slog.F("source", "provisioner")).
|
||||
Leveled(slog.LevelDebug)
|
||||
|
||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||
binaryPath: binPath,
|
||||
logger: &logger,
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
err = sendInit(sess, testutil.CreateTar(t, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
var planOnce sync.Once
|
||||
|
||||
for _, line := range tt.startSequence {
|
||||
LoopStart:
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(msg.Type)
|
||||
if msg.GetInit() != nil && msg.GetInit().GetError() == "" {
|
||||
planOnce.Do(func() {
|
||||
t.Log("Sending terraform plan request")
|
||||
// Send plan after init
|
||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
goto LoopStart
|
||||
}
|
||||
|
||||
log := msg.GetLog()
|
||||
if log == nil {
|
||||
goto LoopStart
|
||||
}
|
||||
|
||||
require.Equal(t, line, log.Output)
|
||||
}
|
||||
|
||||
t.Log("Sending the cancel request")
|
||||
err = sess.Send(&proto.Request{
|
||||
Type: &proto.Request_Cancel{
|
||||
Cancel: &proto.CancelRequest{},
|
||||
@@ -199,10 +249,14 @@ func TestProvision_Cancel(t *testing.T) {
|
||||
|
||||
if log := msg.GetLog(); log != nil {
|
||||
gotLog = append(gotLog, log.Output)
|
||||
}
|
||||
if c := msg.GetPlan(); c != nil {
|
||||
} else if c := msg.GetPlan(); c != nil {
|
||||
require.Contains(t, c.Error, "exit status 1")
|
||||
break
|
||||
} else if c := msg.GetInit(); c != nil {
|
||||
require.Contains(t, c.Error, "exit status 1")
|
||||
break
|
||||
} else {
|
||||
t.Fatalf("unexpected message: %v", msg)
|
||||
}
|
||||
}
|
||||
require.Equal(t, tt.wantLog, gotLog)
|
||||
@@ -231,15 +285,14 @@ func TestProvision_CancelTimeout(t *testing.T) {
|
||||
exitTimeout: time.Second,
|
||||
})
|
||||
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
sendInitAndGetResp(t, sess, testutil.CreateTar(t, nil))
|
||||
|
||||
// provisioner requires plan before apply, so test cancel with plan.
|
||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, line := range []string{"init", "plan_start"} {
|
||||
for _, line := range []string{"plan_start"} {
|
||||
LoopStart:
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
@@ -316,11 +369,9 @@ func TestProvision_TextFileBusy(t *testing.T) {
|
||||
logger: &logger,
|
||||
})
|
||||
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
err = sendInit(sess, testutil.CreateTar(t, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
@@ -328,7 +379,7 @@ func TestProvision_TextFileBusy(t *testing.T) {
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
if c := msg.GetPlan(); c != nil {
|
||||
if c := msg.GetInit(); c != nil {
|
||||
require.Contains(t, c.Error, "exit status 1")
|
||||
found = true
|
||||
break
|
||||
@@ -347,11 +398,14 @@ func TestProvision(t *testing.T) {
|
||||
Metadata *proto.Metadata
|
||||
Request *proto.PlanRequest
|
||||
// Response may be nil to not check the response.
|
||||
Response *proto.PlanComplete
|
||||
Response *proto.GraphComplete
|
||||
InitResponse *proto.InitComplete
|
||||
InitErrorContains string
|
||||
InitExpectLogContains string
|
||||
// If ErrorContains is not empty, PlanComplete should have an Error containing the given string
|
||||
ErrorContains string
|
||||
// If ExpectLogContains is not empty, then the logs should contain it.
|
||||
ExpectLogContains string
|
||||
PlanErrorContains string
|
||||
// If PlanExpectLogContains is not empty, then the logs should contain it.
|
||||
PlanExpectLogContains string
|
||||
// If Apply is true, then send an Apply request and check we get the same Resources as in Response.
|
||||
Apply bool
|
||||
// Some tests may need to be skipped until the relevant provider version is released.
|
||||
@@ -365,8 +419,8 @@ func TestProvision(t *testing.T) {
|
||||
"main.tf": `variable "A" {
|
||||
}`,
|
||||
},
|
||||
ErrorContains: "terraform plan:",
|
||||
ExpectLogContains: "No value for required variable",
|
||||
PlanErrorContains: "terraform plan:",
|
||||
PlanExpectLogContains: "No value for required variable",
|
||||
},
|
||||
{
|
||||
Name: "missing-variable-dry-run",
|
||||
@@ -374,15 +428,15 @@ func TestProvision(t *testing.T) {
|
||||
"main.tf": `variable "A" {
|
||||
}`,
|
||||
},
|
||||
ErrorContains: "terraform plan:",
|
||||
ExpectLogContains: "No value for required variable",
|
||||
PlanErrorContains: "terraform plan:",
|
||||
PlanExpectLogContains: "No value for required variable",
|
||||
},
|
||||
{
|
||||
Name: "single-resource-dry-run",
|
||||
Files: map[string]string{
|
||||
"main.tf": `resource "null_resource" "A" {}`,
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "A",
|
||||
Type: "null_resource",
|
||||
@@ -394,7 +448,7 @@ func TestProvision(t *testing.T) {
|
||||
Files: map[string]string{
|
||||
"main.tf": `resource "null_resource" "A" {}`,
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "A",
|
||||
Type: "null_resource",
|
||||
@@ -415,7 +469,7 @@ func TestProvision(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "A",
|
||||
Type: "null_resource",
|
||||
@@ -428,18 +482,18 @@ func TestProvision(t *testing.T) {
|
||||
Files: map[string]string{
|
||||
"main.tf": `a`,
|
||||
},
|
||||
ErrorContains: "initialize terraform",
|
||||
ExpectLogContains: "Argument or block definition required",
|
||||
SkipCacheProviders: true,
|
||||
InitErrorContains: "initialize terraform",
|
||||
InitExpectLogContains: "Argument or block definition required",
|
||||
SkipCacheProviders: true,
|
||||
},
|
||||
{
|
||||
Name: "bad-syntax-2",
|
||||
Files: map[string]string{
|
||||
"main.tf": `;asdf;`,
|
||||
},
|
||||
ErrorContains: "initialize terraform",
|
||||
ExpectLogContains: `The ";" character is not valid.`,
|
||||
SkipCacheProviders: true,
|
||||
InitErrorContains: "initialize terraform",
|
||||
InitExpectLogContains: `The ";" character is not valid.`,
|
||||
SkipCacheProviders: true,
|
||||
},
|
||||
{
|
||||
Name: "destroy-no-state",
|
||||
@@ -449,7 +503,7 @@ func TestProvision(t *testing.T) {
|
||||
Metadata: &proto.Metadata{
|
||||
WorkspaceTransition: proto.WorkspaceTransition_DESTROY,
|
||||
},
|
||||
ExpectLogContains: "nothing to do",
|
||||
PlanExpectLogContains: "nothing to do",
|
||||
},
|
||||
{
|
||||
Name: "rich-parameter-with-value",
|
||||
@@ -493,7 +547,7 @@ func TestProvision(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "Example",
|
||||
@@ -571,7 +625,7 @@ func TestProvision(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "Example",
|
||||
@@ -623,7 +677,7 @@ func TestProvision(t *testing.T) {
|
||||
AccessToken: "some-value",
|
||||
}},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -666,7 +720,7 @@ func TestProvision(t *testing.T) {
|
||||
WorkspaceOwnerSshPrivateKey: "fake private key",
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -709,7 +763,7 @@ func TestProvision(t *testing.T) {
|
||||
WorkspaceOwnerLoginType: "github",
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -738,16 +792,7 @@ func TestProvision(t *testing.T) {
|
||||
`,
|
||||
},
|
||||
Request: &proto.PlanRequest{},
|
||||
Response: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
ModulePath: "module.hello",
|
||||
}, {
|
||||
Name: "inner_example",
|
||||
Type: "null_resource",
|
||||
ModulePath: "module.hello.module.there",
|
||||
}},
|
||||
InitResponse: &proto.InitComplete{
|
||||
Modules: []*proto.Module{{
|
||||
Key: "hello",
|
||||
Version: "",
|
||||
@@ -758,6 +803,17 @@ func TestProvision(t *testing.T) {
|
||||
Source: "./inner_module",
|
||||
}},
|
||||
},
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
ModulePath: "module.hello",
|
||||
}, {
|
||||
Name: "inner_example",
|
||||
Type: "null_resource",
|
||||
ModulePath: "module.hello.module.there",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "workspace-owner-rbac-roles",
|
||||
@@ -792,7 +848,7 @@ func TestProvision(t *testing.T) {
|
||||
WorkspaceOwnerRbacRoles: []*proto.Role{{Name: "member", OrgId: ""}},
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -833,7 +889,7 @@ func TestProvision(t *testing.T) {
|
||||
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CREATE,
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -871,7 +927,7 @@ func TestProvision(t *testing.T) {
|
||||
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CLAIM,
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
@@ -910,7 +966,7 @@ func TestProvision(t *testing.T) {
|
||||
`, provider.TaskPromptParameterName),
|
||||
},
|
||||
Request: &proto.PlanRequest{},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "a",
|
||||
@@ -962,7 +1018,7 @@ func TestProvision(t *testing.T) {
|
||||
}
|
||||
`,
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "coder_external_agent",
|
||||
@@ -987,7 +1043,7 @@ func TestProvision(t *testing.T) {
|
||||
}
|
||||
`,
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Response: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "my-task",
|
||||
@@ -1004,6 +1060,14 @@ func TestProvision(t *testing.T) {
|
||||
},
|
||||
SkipCacheProviders: true,
|
||||
},
|
||||
{
|
||||
Name: "malicious-tar",
|
||||
Files: map[string]string{
|
||||
// Non-local path outside the working directory.
|
||||
"../../../etc/passwd": "content",
|
||||
},
|
||||
InitErrorContains: "refusing to extract to non-local path",
|
||||
},
|
||||
}
|
||||
|
||||
// Remove unused cache dirs before running tests.
|
||||
@@ -1043,9 +1107,18 @@ func TestProvision(t *testing.T) {
|
||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||
cliConfigPath: cliConfigPath,
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, testCase.Files),
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
initLogGot := testCase.InitExpectLogContains == ""
|
||||
initComplete := sendInitAndGetResp(t, sess, testutil.CreateTar(t, testCase.Files), func(log string) {
|
||||
if strings.Contains(log, testCase.InitExpectLogContains) {
|
||||
initLogGot = true
|
||||
}
|
||||
})
|
||||
require.Truef(t, initLogGot, "did not get expected init log substring %q", testCase.InitExpectLogContains)
|
||||
if testCase.InitErrorContains != "" {
|
||||
require.Contains(t, initComplete.Error, testCase.InitErrorContains)
|
||||
return
|
||||
}
|
||||
|
||||
planRequest := &proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
|
||||
Metadata: testCase.Metadata,
|
||||
@@ -1054,7 +1127,7 @@ func TestProvision(t *testing.T) {
|
||||
planRequest = &proto.Request{Type: &proto.Request_Plan{Plan: testCase.Request}}
|
||||
}
|
||||
|
||||
gotExpectedLog := testCase.ExpectLogContains == ""
|
||||
gotExpectedLog := testCase.PlanExpectLogContains == ""
|
||||
|
||||
provision := func(req *proto.Request) *proto.Response {
|
||||
err := sess.Send(req)
|
||||
@@ -1063,7 +1136,7 @@ func TestProvision(t *testing.T) {
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
if msg.GetLog() != nil {
|
||||
if testCase.ExpectLogContains != "" && strings.Contains(msg.GetLog().Output, testCase.ExpectLogContains) {
|
||||
if testCase.PlanExpectLogContains != "" && strings.Contains(msg.GetLog().Output, testCase.PlanExpectLogContains) {
|
||||
gotExpectedLog = true
|
||||
}
|
||||
|
||||
@@ -1078,35 +1151,43 @@ func TestProvision(t *testing.T) {
|
||||
planComplete := resp.GetPlan()
|
||||
require.NotNil(t, planComplete)
|
||||
|
||||
if testCase.ErrorContains != "" {
|
||||
require.Contains(t, planComplete.GetError(), testCase.ErrorContains)
|
||||
if testCase.PlanErrorContains != "" {
|
||||
require.Contains(t, planComplete.GetError(), testCase.PlanErrorContains)
|
||||
}
|
||||
|
||||
graphCompleteResp := provision(&proto.Request{Type: &proto.Request_Graph{Graph: &proto.GraphRequest{
|
||||
Source: proto.GraphSource_SOURCE_PLAN,
|
||||
}}})
|
||||
graphComplete := graphCompleteResp.GetGraph()
|
||||
require.NotNil(t, graphCompleteResp)
|
||||
|
||||
if testCase.Response != nil {
|
||||
require.Equal(t, testCase.Response.Error, planComplete.Error)
|
||||
require.Equal(t, testCase.Response.Error, graphComplete.Error)
|
||||
|
||||
// Remove randomly generated data and sort by name.
|
||||
normalizeResources(planComplete.Resources)
|
||||
resourcesGot, err := json.Marshal(planComplete.Resources)
|
||||
normalizeResources(graphComplete.Resources)
|
||||
resourcesGot, err := json.Marshal(graphComplete.Resources)
|
||||
require.NoError(t, err)
|
||||
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(resourcesWant), string(resourcesGot))
|
||||
|
||||
parametersGot, err := json.Marshal(planComplete.Parameters)
|
||||
parametersGot, err := json.Marshal(graphComplete.Parameters)
|
||||
require.NoError(t, err)
|
||||
parametersWant, err := json.Marshal(testCase.Response.Parameters)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(parametersWant), string(parametersGot))
|
||||
|
||||
modulesGot, err := json.Marshal(planComplete.Modules)
|
||||
modulesGot, err := json.Marshal(initComplete.Modules)
|
||||
require.NoError(t, err)
|
||||
modulesWant, err := json.Marshal(testCase.Response.Modules)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(modulesWant), string(modulesGot))
|
||||
if testCase.InitResponse != nil {
|
||||
modulesWant, err := json.Marshal(testCase.InitResponse.Modules)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(modulesWant), string(modulesGot))
|
||||
}
|
||||
|
||||
require.Equal(t, planComplete.HasAiTasks, testCase.Response.HasAiTasks)
|
||||
require.Equal(t, planComplete.HasExternalAgents, testCase.Response.HasExternalAgents)
|
||||
require.Equal(t, graphComplete.HasAiTasks, testCase.Response.HasAiTasks)
|
||||
require.Equal(t, graphComplete.HasExternalAgents, testCase.Response.HasExternalAgents)
|
||||
}
|
||||
|
||||
if testCase.Apply {
|
||||
@@ -1117,8 +1198,8 @@ func TestProvision(t *testing.T) {
|
||||
require.NotNil(t, applyComplete)
|
||||
|
||||
if testCase.Response != nil {
|
||||
normalizeResources(applyComplete.Resources)
|
||||
resourcesGot, err := json.Marshal(applyComplete.Resources)
|
||||
normalizeResources(graphComplete.Resources)
|
||||
resourcesGot, err := json.Marshal(graphComplete.Resources)
|
||||
require.NoError(t, err)
|
||||
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
||||
require.NoError(t, err)
|
||||
@@ -1127,7 +1208,7 @@ func TestProvision(t *testing.T) {
|
||||
}
|
||||
|
||||
if !gotExpectedLog {
|
||||
t.Fatalf("expected log string %q but never saw it", testCase.ExpectLogContains)
|
||||
t.Fatalf("expected log string %q but never saw it", testCase.PlanExpectLogContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1160,9 +1241,10 @@ func TestProvision_ExtraEnv(t *testing.T) {
|
||||
t.Setenv("TF_SUPERSECRET", secretValue)
|
||||
|
||||
ctx, api := setupProvisioner(t, nil)
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{"main.tf": `resource "null_resource" "A" {}`}),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
resp := sendInitAndGetResp(t, sess, testutil.CreateTar(t, map[string]string{"main.tf": `resource "null_resource" "A" {}`}))
|
||||
require.Empty(t, resp.Error)
|
||||
|
||||
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
@@ -1210,9 +1292,10 @@ func TestProvision_SafeEnv(t *testing.T) {
|
||||
`
|
||||
|
||||
ctx, api := setupProvisioner(t, nil)
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{"main.tf": echoResource}),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
resp := sendInitAndGetResp(t, sess, testutil.CreateTar(t, map[string]string{"main.tf": echoResource}))
|
||||
require.Empty(t, resp.Error)
|
||||
|
||||
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
@@ -1232,15 +1315,14 @@ func TestProvision_MalformedModules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, api := setupProvisioner(t, nil)
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{
|
||||
"main.tf": `module "hello" { source = "./module" }`,
|
||||
"module/module.tf": `resource "null_`,
|
||||
}),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
err := sendInit(sess, testutil.CreateTar(t, map[string]string{
|
||||
"main.tf": `module "hello" { source = "./module" }`,
|
||||
"module/module.tf": `resource "null_`,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
log := readProvisionLog(t, sess)
|
||||
require.Contains(t, log, "Invalid block definition")
|
||||
}
|
||||
|
||||
@@ -281,12 +281,3 @@ func (e *timingSpan) toProto() *proto.Timing {
|
||||
State: e.state,
|
||||
}
|
||||
}
|
||||
|
||||
func createGraphTimingsEvent(event timingKind) (time.Time, *timingSpan) {
|
||||
return dbtime.Now(), &timingSpan{
|
||||
kind: event,
|
||||
action: "building terraform dependency graph",
|
||||
provider: "terraform",
|
||||
resource: "state file",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package terraform_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -35,65 +36,66 @@ func TestTimingsFromProvision(t *testing.T) {
|
||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||
binaryPath: fakeBin,
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{
|
||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
||||
})
|
||||
sess := configure(ctx, t, api, &proto.Config{})
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, testutil.WaitLong)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
var timings []*proto.Timing
|
||||
|
||||
handleResponse := func(t *testing.T, stage string) {
|
||||
t.Helper()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
default:
|
||||
}
|
||||
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
if log := msg.GetLog(); log != nil {
|
||||
t.Logf("%s: %s: %s", stage, log.Level.String(), log.Output)
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case msg.GetInit() != nil:
|
||||
timings = append(timings, msg.GetInit().GetTimings()...)
|
||||
case msg.GetPlan() != nil:
|
||||
timings = append(timings, msg.GetPlan().GetTimings()...)
|
||||
case msg.GetApply() != nil:
|
||||
timings = append(timings, msg.GetApply().GetTimings()...)
|
||||
case msg.GetGraph() != nil:
|
||||
timings = append(timings, msg.GetGraph().GetTimings()...)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// When: configured, our fake terraform will fake an init setup
|
||||
err = sendInit(sess, testutil.CreateTar(t, nil))
|
||||
require.NoError(t, err)
|
||||
handleResponse(t, "init")
|
||||
|
||||
// When: a plan is executed in the provisioner, our fake terraform will be executed and will produce a
|
||||
// state file and some log content.
|
||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
|
||||
var timings []*proto.Timing
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
default:
|
||||
}
|
||||
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
if log := msg.GetLog(); log != nil {
|
||||
t.Logf("%s: %s: %s", "plan", log.Level.String(), log.Output)
|
||||
}
|
||||
if c := msg.GetPlan(); c != nil {
|
||||
require.Empty(t, c.Error)
|
||||
// Capture the timing information returned by the plan process.
|
||||
timings = append(timings, c.GetTimings()...)
|
||||
break
|
||||
}
|
||||
}
|
||||
handleResponse(t, "plan")
|
||||
|
||||
// When: the plan has completed, let's trigger an apply.
|
||||
err = sendApply(sess, proto.WorkspaceTransition_START)
|
||||
require.NoError(t, err)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
default:
|
||||
}
|
||||
handleResponse(t, "apply")
|
||||
|
||||
msg, err := sess.Recv()
|
||||
require.NoError(t, err)
|
||||
// When: the apply has completed, graph the results
|
||||
err = sendGraph(sess, proto.GraphSource_SOURCE_STATE)
|
||||
require.NoError(t, err)
|
||||
|
||||
if log := msg.GetLog(); log != nil {
|
||||
t.Logf("%s: %s: %s", "apply", log.Level.String(), log.Output)
|
||||
}
|
||||
if c := msg.GetApply(); c != nil {
|
||||
require.Empty(t, c.Error)
|
||||
// Capture the timing information returned by the apply process.
|
||||
timings = append(timings, c.GetTimings()...)
|
||||
break
|
||||
}
|
||||
}
|
||||
handleResponse(t, "graph")
|
||||
|
||||
// Sort the timings stably to keep reduce flakiness.
|
||||
terraform_internal.StableSortTimings(t, timings)
|
||||
@@ -116,10 +118,19 @@ func TestTimingsFromProvision(t *testing.T) {
|
||||
{"start":"2024-08-15T08:26:39.626722Z", "end":"2024-08-15T08:26:39.669954Z", "action":"create", "source":"docker", "resource":"docker_image.main", "stage":"apply", "state":"COMPLETED"}
|
||||
{"start":"2024-08-15T08:26:39.627335Z", "end":"2024-08-15T08:26:39.660616Z", "action":"create", "source":"docker", "resource":"docker_volume.home_volume", "stage":"apply", "state":"COMPLETED"}
|
||||
{"start":"2024-08-15T08:26:39.682223Z", "end":"2024-08-15T08:26:40.186482Z", "action":"create", "source":"docker", "resource":"docker_container.workspace[0]", "stage":"apply", "state":"COMPLETED"}`))
|
||||
graphTimings := terraform_internal.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"building terraform dependency graph", "source":"terraform", "resource":"state file", "stage":"graph", "state":"COMPLETED"}`))
|
||||
graphTiming := graphTimings[0]
|
||||
// Graphing is omitted as it is captured by the stage timing, which uses now()
|
||||
|
||||
require.Len(t, timings, len(initTimings)+len(planTimings)+len(applyTimings)+len(graphTimings))
|
||||
totals := make(map[string]int)
|
||||
for _, ti := range timings {
|
||||
totals[ti.Stage]++
|
||||
data, _ := json.Marshal(ti) // for debugging
|
||||
t.Logf("Timings log (%s) :: %s", ti.Stage, string(data))
|
||||
}
|
||||
require.Equal(t, len(initTimings), totals["init"], "init")
|
||||
require.Equal(t, len(planTimings), totals["plan"], "plan")
|
||||
require.Equal(t, len(applyTimings), totals["apply"], "apply")
|
||||
// Lastly total
|
||||
require.Len(t, timings, len(initTimings)+len(planTimings)+len(applyTimings))
|
||||
|
||||
// init/graph timings are computed dynamically during provisioning whereas plan/apply come from the logs (fixtures) in
|
||||
// provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh.
|
||||
@@ -134,9 +145,6 @@ func TestTimingsFromProvision(t *testing.T) {
|
||||
case string(database.ProvisionerJobTimingStageInit):
|
||||
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{initTimings[iCursor]}, []*proto.Timing{tim}))
|
||||
iCursor++
|
||||
case string(database.ProvisionerJobTimingStageGraph):
|
||||
tim.Start, tim.End = graphTiming.Start, graphTiming.End
|
||||
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{graphTiming}, []*proto.Timing{tim}))
|
||||
case string(database.ProvisionerJobTimingStagePlan):
|
||||
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim}))
|
||||
pCursor++
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
@@ -131,6 +132,16 @@ func TestProvisionerd(t *testing.T) {
|
||||
}
|
||||
return c
|
||||
},
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
closerMutex.Lock()
|
||||
defer closerMutex.Unlock()
|
||||
err := closer.Close()
|
||||
c := &sdkproto.InitComplete{}
|
||||
if err != nil {
|
||||
c.Error = err.Error()
|
||||
}
|
||||
return c
|
||||
},
|
||||
}),
|
||||
})
|
||||
closerMutex.Unlock()
|
||||
@@ -138,47 +149,6 @@ func TestProvisionerd(t *testing.T) {
|
||||
require.NoError(t, closer.Close())
|
||||
})
|
||||
|
||||
t.Run("MaliciousTar", func(t *testing.T) {
|
||||
// Ensures tars with "../../../etc/passwd" as the path
|
||||
// are not allowed to run, and will fail the job.
|
||||
t.Parallel()
|
||||
done := make(chan struct{})
|
||||
t.Cleanup(func() {
|
||||
close(done)
|
||||
})
|
||||
var (
|
||||
completeChan = make(chan struct{})
|
||||
completeOnce sync.Once
|
||||
acq = newAcquireOne(t, &proto.AcquiredJob{
|
||||
JobId: "test",
|
||||
Provisioner: "someprovisioner",
|
||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{
|
||||
"../../../etc/passwd": "content",
|
||||
}),
|
||||
Type: &proto.AcquiredJob_TemplateImport_{
|
||||
TemplateImport: &proto.AcquiredJob_TemplateImport{
|
||||
Metadata: &sdkproto.Metadata{},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) {
|
||||
return createProvisionerDaemonClient(t, done, provisionerDaemonTestServer{
|
||||
acquireJobWithCancel: acq.acquireWithCancel,
|
||||
updateJob: noopUpdateJob,
|
||||
failJob: func(ctx context.Context, job *proto.FailedJob) (*proto.Empty, error) {
|
||||
completeOnce.Do(func() { close(completeChan) })
|
||||
return &proto.Empty{}, nil
|
||||
},
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{}),
|
||||
})
|
||||
require.Condition(t, closedWithin(completeChan, testutil.WaitMedium))
|
||||
require.NoError(t, closer.Close())
|
||||
})
|
||||
|
||||
// LargePayloads sends a 3mb tar file to the provisioner. The provisioner also
|
||||
// returns large payload messages back. The limit should be 4mb, so all
|
||||
// these messages should work.
|
||||
@@ -227,14 +197,16 @@ func TestProvisionerd(t *testing.T) {
|
||||
Readme: make([]byte, largeSize),
|
||||
}
|
||||
},
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
_ *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
_ <-chan struct{},
|
||||
) *sdkproto.PlanComplete {
|
||||
return &sdkproto.PlanComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
Plan: make([]byte, largeSize),
|
||||
Plan: make([]byte, largeSize),
|
||||
}
|
||||
},
|
||||
apply: func(
|
||||
@@ -246,6 +218,11 @@ func TestProvisionerd(t *testing.T) {
|
||||
State: make([]byte, largeSize),
|
||||
}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
}
|
||||
},
|
||||
}),
|
||||
})
|
||||
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||
@@ -299,6 +276,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
<-cancelOrComplete
|
||||
return &sdkproto.ParseComplete{}
|
||||
},
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
}),
|
||||
})
|
||||
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||
@@ -349,6 +329,7 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: extractInit(t),
|
||||
parse: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.ParseRequest,
|
||||
@@ -366,9 +347,7 @@ func TestProvisionerd(t *testing.T) {
|
||||
cancelOrComplete <-chan struct{},
|
||||
) *sdkproto.PlanComplete {
|
||||
s.ProvisionLog(sdkproto.LogLevel_INFO, "hello")
|
||||
return &sdkproto.PlanComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
}
|
||||
return &sdkproto.PlanComplete{}
|
||||
},
|
||||
apply: func(
|
||||
_ *provisionersdk.Session,
|
||||
@@ -378,6 +357,11 @@ func TestProvisionerd(t *testing.T) {
|
||||
t.Error("dry run should not apply")
|
||||
return &sdkproto.ApplyComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
}
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -433,14 +417,15 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
_ *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
_ <-chan struct{},
|
||||
) *sdkproto.PlanComplete {
|
||||
return &sdkproto.PlanComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
}
|
||||
return &sdkproto.PlanComplete{}
|
||||
},
|
||||
apply: func(
|
||||
_ *provisionersdk.Session,
|
||||
@@ -450,6 +435,11 @@ func TestProvisionerd(t *testing.T) {
|
||||
t.Error("dry run should not apply")
|
||||
return &sdkproto.ApplyComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{
|
||||
Resources: []*sdkproto.Resource{},
|
||||
}
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -498,6 +488,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -513,6 +506,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
) *sdkproto.ApplyComplete {
|
||||
return &sdkproto.ApplyComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{}
|
||||
},
|
||||
}),
|
||||
})
|
||||
require.Condition(t, closedWithin(acq.complete, testutil.WaitShort))
|
||||
@@ -570,6 +566,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -577,14 +576,7 @@ func TestProvisionerd(t *testing.T) {
|
||||
) *sdkproto.PlanComplete {
|
||||
s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
|
||||
return &sdkproto.PlanComplete{
|
||||
Resources: []*sdkproto.Resource{
|
||||
{
|
||||
DailyCost: 10,
|
||||
},
|
||||
{
|
||||
DailyCost: 15,
|
||||
},
|
||||
},
|
||||
DailyCost: 25,
|
||||
}
|
||||
},
|
||||
apply: func(
|
||||
@@ -593,7 +585,10 @@ func TestProvisionerd(t *testing.T) {
|
||||
_ <-chan struct{},
|
||||
) *sdkproto.ApplyComplete {
|
||||
t.Error("should not apply when resources exceed quota")
|
||||
return &sdkproto.ApplyComplete{
|
||||
return &sdkproto.ApplyComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{
|
||||
Resources: []*sdkproto.Resource{
|
||||
{
|
||||
DailyCost: 10,
|
||||
@@ -646,6 +641,12 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -756,6 +757,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -844,6 +848,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -938,6 +945,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
return client, nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
_ *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -1031,6 +1041,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
return client, nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
plan: func(
|
||||
_ *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -1045,6 +1058,9 @@ func TestProvisionerd(t *testing.T) {
|
||||
) *sdkproto.ApplyComplete {
|
||||
return &sdkproto.ApplyComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{}
|
||||
},
|
||||
}),
|
||||
})
|
||||
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||
@@ -1125,6 +1141,12 @@ func TestProvisionerd(t *testing.T) {
|
||||
}), nil
|
||||
}, provisionerd.LocalProvisioners{
|
||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return &sdkproto.InitComplete{}
|
||||
},
|
||||
graph: func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return &sdkproto.GraphComplete{}
|
||||
},
|
||||
plan: func(
|
||||
s *provisionersdk.Session,
|
||||
_ *sdkproto.PlanRequest,
|
||||
@@ -1253,9 +1275,15 @@ func createProvisionerClient(t *testing.T, done <-chan struct{}, server provisio
|
||||
}
|
||||
|
||||
type provisionerTestServer struct {
|
||||
init func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete
|
||||
parse func(s *provisionersdk.Session, r *sdkproto.ParseRequest, canceledOrComplete <-chan struct{}) *sdkproto.ParseComplete
|
||||
plan func(s *provisionersdk.Session, r *sdkproto.PlanRequest, canceledOrComplete <-chan struct{}) *sdkproto.PlanComplete
|
||||
apply func(s *provisionersdk.Session, r *sdkproto.ApplyRequest, canceledOrComplete <-chan struct{}) *sdkproto.ApplyComplete
|
||||
graph func(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete
|
||||
}
|
||||
|
||||
func (p *provisionerTestServer) Init(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
return p.init(s, r, canceledOrComplete)
|
||||
}
|
||||
|
||||
func (p *provisionerTestServer) Parse(s *provisionersdk.Session, r *sdkproto.ParseRequest, canceledOrComplete <-chan struct{}) *sdkproto.ParseComplete {
|
||||
@@ -1270,6 +1298,10 @@ func (p *provisionerTestServer) Apply(s *provisionersdk.Session, r *sdkproto.App
|
||||
return p.apply(s, r, canceledOrComplete)
|
||||
}
|
||||
|
||||
func (p *provisionerTestServer) Graph(s *provisionersdk.Session, r *sdkproto.GraphRequest, canceledOrComplete <-chan struct{}) *sdkproto.GraphComplete {
|
||||
return p.graph(s, r, canceledOrComplete)
|
||||
}
|
||||
|
||||
func (p *provisionerDaemonTestServer) UploadFile(stream proto.DRPCProvisionerDaemon_UploadFileStream) error {
|
||||
return p.uploadFile(stream)
|
||||
}
|
||||
@@ -1359,3 +1391,16 @@ func (a *acquireOne) acquireWithCancel(stream proto.DRPCProvisionerDaemon_Acquir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractInit(t *testing.T) func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
logger := slogtest.Make(t, nil)
|
||||
return func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||
err := s.Files.ExtractArchive(s.Context(), logger, afero.NewOsFs(), r.TemplateSourceArchive)
|
||||
if err != nil {
|
||||
return &sdkproto.InitComplete{
|
||||
Error: fmt.Sprintf("failed to extract template source archive: %v", err),
|
||||
}
|
||||
}
|
||||
return &sdkproto.InitComplete{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func (r *Runner) apply(ctx context.Context, stage string, req *sdkproto.ApplyRequest) (
|
||||
*sdkproto.ApplyComplete, *proto.FailedJob,
|
||||
) {
|
||||
// use the notStopped so that if we attempt to gracefully cancel, the stream
|
||||
// will still be available for us to send the cancel to the provisioner
|
||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Apply{Apply: req}})
|
||||
if err != nil {
|
||||
return nil, r.failedWorkspaceBuildf("start provision: %s", err)
|
||||
}
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, r.failedWorkspaceBuildf("recv workspace provision: %s", err)
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "workspace provisioner job logged",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
slog.F("workspace_build_id", r.job.GetWorkspaceBuild().WorkspaceBuildId),
|
||||
)
|
||||
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: stage,
|
||||
})
|
||||
case *sdkproto.Response_Apply:
|
||||
return msgType.Apply, nil
|
||||
default:
|
||||
return nil, r.failedJobf("unexpected plan response type %T", msg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func (r *Runner) graph(ctx context.Context, req *sdkproto.GraphRequest) (*sdkproto.GraphComplete, *proto.FailedJob) {
|
||||
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
|
||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Graph{Graph: req}})
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("send graph request: %v", err)
|
||||
}
|
||||
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("receive graph response: %v", err)
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "terraform graphing",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
)
|
||||
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: "Graphing Infrastructure",
|
||||
})
|
||||
case *sdkproto.Response_Graph:
|
||||
return msgType.Graph, nil
|
||||
default:
|
||||
return nil, r.failedJobf("unexpected graph response type %T", msg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
//nolint:revive
|
||||
func (r *Runner) init(ctx context.Context, omitModules bool, templateArchive []byte) (*sdkproto.InitComplete, *proto.FailedJob) {
|
||||
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
|
||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Init{Init: &sdkproto.InitRequest{
|
||||
TemplateSourceArchive: templateArchive,
|
||||
OmitModuleFiles: omitModules,
|
||||
}}})
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("send init request: %v", err)
|
||||
}
|
||||
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
var moduleFilesUpload *sdkproto.DataBuilder
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("receive init response: %v", err)
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "terraform initialization",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
)
|
||||
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: "Initializing Terraform Directory",
|
||||
})
|
||||
case *sdkproto.Response_DataUpload:
|
||||
if omitModules {
|
||||
return nil, r.failedJobf("received unexpected module files data upload when omitModules is true")
|
||||
}
|
||||
c := msgType.DataUpload
|
||||
if c.UploadType != sdkproto.DataUploadType_UPLOAD_TYPE_MODULE_FILES {
|
||||
return nil, r.failedJobf("invalid data upload type: %q", c.UploadType)
|
||||
}
|
||||
|
||||
if moduleFilesUpload != nil {
|
||||
return nil, r.failedJobf("multiple module data uploads received, only expect 1")
|
||||
}
|
||||
|
||||
moduleFilesUpload, err = sdkproto.NewDataBuilder(c)
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("create data builder: %s", err.Error())
|
||||
}
|
||||
case *sdkproto.Response_ChunkPiece:
|
||||
if omitModules {
|
||||
return nil, r.failedJobf("received unexpected module files data upload when omitModules is true")
|
||||
}
|
||||
c := msgType.ChunkPiece
|
||||
if moduleFilesUpload == nil {
|
||||
return nil, r.failedJobf("received chunk piece before module files data upload")
|
||||
}
|
||||
|
||||
_, err := moduleFilesUpload.Add(c)
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("module files, add chunk piece: %s", err.Error())
|
||||
}
|
||||
case *sdkproto.Response_Init:
|
||||
if moduleFilesUpload != nil {
|
||||
// If files were uploaded in multiple chunks, put them back together.
|
||||
moduleFilesData, err := moduleFilesUpload.Complete()
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("complete module files data upload: %s", err.Error())
|
||||
}
|
||||
|
||||
if !bytes.Equal(msgType.Init.ModuleFilesHash, moduleFilesUpload.Hash) {
|
||||
return nil, r.failedJobf("module files hash mismatch, uploaded: %x, expected: %x", moduleFilesUpload.Hash, msgType.Init.ModuleFilesHash)
|
||||
}
|
||||
msgType.Init.ModuleFiles = moduleFilesData
|
||||
}
|
||||
|
||||
return msgType.Init, nil
|
||||
default:
|
||||
return nil, r.failedJobf("unexpected init response type %T", msg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func (r *Runner) plan(ctx context.Context, stage string, req *sdkproto.PlanRequest) (*sdkproto.PlanComplete, *proto.FailedJob) {
|
||||
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
|
||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Plan{Plan: req}})
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("send plan request: %v", err)
|
||||
}
|
||||
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("receive plan response: %v", err)
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "terraform planning",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
)
|
||||
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: stage,
|
||||
})
|
||||
case *sdkproto.Response_Plan:
|
||||
return msgType.Plan, nil
|
||||
default:
|
||||
return nil, r.failedJobf("unexpected plan response type %T", msg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package runner
|
||||
|
||||
import "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
|
||||
func sumDailyCost(resources []*proto.Resource) int {
|
||||
var sum int
|
||||
for _, r := range resources {
|
||||
sum += int(r.DailyCost)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
+173
-245
@@ -1,7 +1,6 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -515,7 +514,6 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
||||
defer span.End()
|
||||
|
||||
failedJob := r.configure(&sdkproto.Config{
|
||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
||||
TemplateId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateId),
|
||||
TemplateVersionId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateVersionId),
|
||||
ExpReuseTerraformWorkspace: ptr.Ref(false),
|
||||
@@ -524,6 +522,18 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
||||
return nil, failedJob
|
||||
}
|
||||
|
||||
// Initialize the Terraform working directory
|
||||
initResp, failedInit := r.init(ctx, false, r.job.GetTemplateSourceArchive())
|
||||
if failedInit != nil {
|
||||
return nil, failedInit
|
||||
}
|
||||
if initResp == nil {
|
||||
return nil, r.failedJobf("template import init returned nil response")
|
||||
}
|
||||
if initResp.Error != "" {
|
||||
return nil, r.failedJobf("template import init error: %s", initResp.Error)
|
||||
}
|
||||
|
||||
// Parse parameters and update the job with the parameter specs
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER_DAEMON,
|
||||
@@ -560,7 +570,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
||||
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
|
||||
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
||||
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("template import provision for start: %s", err)
|
||||
}
|
||||
@@ -576,8 +586,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
||||
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
|
||||
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
||||
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
|
||||
}, true, // Modules downloaded on the start provision
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("template import provision for stop: %s", err)
|
||||
}
|
||||
@@ -597,12 +606,13 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
||||
RichParameters: startProvision.Parameters,
|
||||
ExternalAuthProvidersNames: externalAuthProviderNames,
|
||||
ExternalAuthProviders: startProvision.ExternalAuthProviders,
|
||||
StartModules: startProvision.Modules,
|
||||
StopModules: stopProvision.Modules,
|
||||
Presets: startProvision.Presets,
|
||||
Plan: startProvision.Plan,
|
||||
// ModuleFiles are not on the stopProvision. So grab from the startProvision.
|
||||
ModuleFiles: startProvision.ModuleFiles,
|
||||
// TODO: These are defined as different, but can they be?
|
||||
// Terraform downloads modules regardless of `count`, so this should be the same
|
||||
StartModules: initResp.Modules,
|
||||
StopModules: initResp.Modules,
|
||||
Presets: startProvision.Presets,
|
||||
Plan: startProvision.Plan,
|
||||
ModuleFiles: initResp.ModuleFiles,
|
||||
// ModuleFileHash will be populated if the file is uploaded async
|
||||
ModuleFilesHash: []byte{},
|
||||
HasAiTasks: startProvision.HasAITasks,
|
||||
@@ -666,10 +676,8 @@ type templateImportProvision struct {
|
||||
Resources []*sdkproto.Resource
|
||||
Parameters []*sdkproto.RichParameter
|
||||
ExternalAuthProviders []*sdkproto.ExternalAuthProviderResource
|
||||
Modules []*sdkproto.Module
|
||||
Presets []*sdkproto.Preset
|
||||
Plan json.RawMessage
|
||||
ModuleFiles []byte
|
||||
HasAITasks bool
|
||||
HasExternalAgents bool
|
||||
}
|
||||
@@ -677,8 +685,8 @@ type templateImportProvision struct {
|
||||
// Performs a dry-run provision when importing a template.
|
||||
// This is used to detect resources that would be provisioned for a workspace in various states.
|
||||
// It doesn't define values for rich parameters as they're unknown during template import.
|
||||
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata, omitModules bool) (*templateImportProvision, error) {
|
||||
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata, omitModules)
|
||||
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata) (*templateImportProvision, error) {
|
||||
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata)
|
||||
}
|
||||
|
||||
// Performs a dry-run provision with provided rich parameters.
|
||||
@@ -688,7 +696,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
|
||||
variableValues []*sdkproto.VariableValue,
|
||||
richParameterValues []*sdkproto.RichParameterValue,
|
||||
metadata *sdkproto.Metadata,
|
||||
omitModules bool,
|
||||
) (*templateImportProvision, error) {
|
||||
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
||||
defer span.End()
|
||||
@@ -700,126 +707,48 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
|
||||
case sdkproto.WorkspaceTransition_STOP:
|
||||
stage = "Detecting ephemeral resources"
|
||||
}
|
||||
// use the notStopped so that if we attempt to gracefully cancel, the stream will still be available for us
|
||||
// to send the cancel to the provisioner
|
||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Plan{Plan: &sdkproto.PlanRequest{
|
||||
Metadata: metadata,
|
||||
RichParameterValues: richParameterValues,
|
||||
// Template import has no previous values
|
||||
PreviousParameterValues: make([]*sdkproto.RichParameterValue, 0),
|
||||
|
||||
planComplete, failed := r.plan(ctx, stage, &sdkproto.PlanRequest{
|
||||
Metadata: metadata,
|
||||
RichParameterValues: richParameterValues,
|
||||
VariableValues: variableValues,
|
||||
OmitModuleFiles: omitModules,
|
||||
}}})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("start provision: %w", err)
|
||||
ExternalAuthProviders: nil,
|
||||
PreviousParameterValues: nil,
|
||||
State: nil,
|
||||
})
|
||||
if failed != nil {
|
||||
return nil, xerrors.Errorf("plan during template import provision: %w", failed)
|
||||
}
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
var moduleFilesUpload *sdkproto.DataBuilder
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("recv import provision: %w", err)
|
||||
}
|
||||
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "template import provision job logged",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
)
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: stage,
|
||||
})
|
||||
case *sdkproto.Response_DataUpload:
|
||||
c := msgType.DataUpload
|
||||
if c.UploadType != sdkproto.DataUploadType_UPLOAD_TYPE_MODULE_FILES {
|
||||
return nil, xerrors.Errorf("invalid data upload type: %q", c.UploadType)
|
||||
}
|
||||
|
||||
if moduleFilesUpload != nil {
|
||||
return nil, xerrors.New("multiple module data uploads received, only expect 1")
|
||||
}
|
||||
|
||||
moduleFilesUpload, err = sdkproto.NewDataBuilder(c)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create data builder: %w", err)
|
||||
}
|
||||
case *sdkproto.Response_ChunkPiece:
|
||||
c := msgType.ChunkPiece
|
||||
if moduleFilesUpload == nil {
|
||||
return nil, xerrors.New("received chunk piece before module files data upload")
|
||||
}
|
||||
|
||||
_, err := moduleFilesUpload.Add(c)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("module files, add chunk piece: %w", err)
|
||||
}
|
||||
case *sdkproto.Response_Plan:
|
||||
c := msgType.Plan
|
||||
if c.Error != "" {
|
||||
r.logger.Info(context.Background(), "dry-run provision failure",
|
||||
slog.F("error", c.Error),
|
||||
)
|
||||
|
||||
return nil, xerrors.New(c.Error)
|
||||
}
|
||||
|
||||
if moduleFilesUpload != nil && len(c.ModuleFiles) > 0 {
|
||||
return nil, xerrors.New("module files were uploaded and module files were returned in the plan response. Only one of these should be set")
|
||||
}
|
||||
|
||||
r.logger.Info(context.Background(), "parse dry-run provision successful",
|
||||
slog.F("resource_count", len(c.Resources)),
|
||||
slog.F("resources", resourceNames(c.Resources)),
|
||||
)
|
||||
|
||||
moduleFilesData := c.ModuleFiles
|
||||
if moduleFilesUpload != nil {
|
||||
uploadData, err := moduleFilesUpload.Complete()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("module files, complete upload: %w", err)
|
||||
}
|
||||
moduleFilesData = uploadData
|
||||
if !bytes.Equal(c.ModuleFilesHash, moduleFilesUpload.Hash) {
|
||||
return nil, xerrors.Errorf("module files hash mismatch, uploaded: %x, expected: %x", moduleFilesUpload.Hash, c.ModuleFilesHash)
|
||||
}
|
||||
}
|
||||
return &templateImportProvision{
|
||||
Resources: c.Resources,
|
||||
Parameters: c.Parameters,
|
||||
ExternalAuthProviders: c.ExternalAuthProviders,
|
||||
Modules: c.Modules,
|
||||
Presets: c.Presets,
|
||||
Plan: c.Plan,
|
||||
ModuleFiles: moduleFilesData,
|
||||
HasAITasks: c.HasAiTasks,
|
||||
HasExternalAgents: c.HasExternalAgents,
|
||||
}, nil
|
||||
default:
|
||||
return nil, xerrors.Errorf("invalid message type %q received from provisioner",
|
||||
reflect.TypeOf(msg.Type).String())
|
||||
}
|
||||
if planComplete == nil {
|
||||
return nil, xerrors.New("plan during template import provision returned nil response")
|
||||
}
|
||||
if planComplete.Error != "" {
|
||||
return nil, xerrors.Errorf("plan during template import provision error: %s", planComplete.Error)
|
||||
}
|
||||
|
||||
graphComplete, failed := r.graph(ctx, &sdkproto.GraphRequest{
|
||||
Metadata: metadata,
|
||||
Source: sdkproto.GraphSource_SOURCE_PLAN,
|
||||
})
|
||||
if failed != nil {
|
||||
return nil, xerrors.Errorf("graph during template import provision: %w", failed)
|
||||
}
|
||||
if graphComplete == nil {
|
||||
return nil, xerrors.New("graph during template import provision returned nil response")
|
||||
}
|
||||
if graphComplete.Error != "" {
|
||||
return nil, xerrors.Errorf("graph during template import provision error: %s", graphComplete.Error)
|
||||
}
|
||||
|
||||
return &templateImportProvision{
|
||||
Resources: graphComplete.Resources,
|
||||
Parameters: graphComplete.Parameters,
|
||||
ExternalAuthProviders: graphComplete.ExternalAuthProviders,
|
||||
Presets: graphComplete.Presets,
|
||||
Plan: planComplete.Plan,
|
||||
HasAITasks: graphComplete.HasAiTasks,
|
||||
HasExternalAgents: graphComplete.HasExternalAgents,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob) {
|
||||
@@ -854,19 +783,28 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
|
||||
metadata.WorkspaceOwnerId = id.String()
|
||||
}
|
||||
|
||||
failedJob := r.configure(&sdkproto.Config{
|
||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
||||
})
|
||||
failedJob := r.configure(&sdkproto.Config{})
|
||||
if failedJob != nil {
|
||||
return nil, failedJob
|
||||
}
|
||||
|
||||
// Initialize the Terraform working directory
|
||||
initResp, failedJob := r.init(ctx, false, r.job.GetTemplateSourceArchive())
|
||||
if failedJob != nil {
|
||||
return nil, failedJob
|
||||
}
|
||||
if initResp == nil {
|
||||
return nil, r.failedJobf("template dry-run init returned nil response")
|
||||
}
|
||||
if initResp.Error != "" {
|
||||
return nil, r.failedJobf("template dry-run init error: %s", initResp.Error)
|
||||
}
|
||||
|
||||
// Run the template import provision task since it's already a dry run.
|
||||
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
|
||||
r.job.GetTemplateDryRun().GetVariableValues(),
|
||||
r.job.GetTemplateDryRun().GetRichParameterValues(),
|
||||
metadata,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, r.failedJobf("run dry-run provision job: %s", err)
|
||||
@@ -877,73 +815,14 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
|
||||
Type: &proto.CompletedJob_TemplateDryRun_{
|
||||
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
|
||||
Resources: provision.Resources,
|
||||
Modules: provision.Modules,
|
||||
Modules: initResp.Modules,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Request) (
|
||||
*sdkproto.Response, *proto.FailedJob,
|
||||
) {
|
||||
// use the notStopped so that if we attempt to gracefully cancel, the stream
|
||||
// will still be available for us to send the cancel to the provisioner
|
||||
err := r.session.Send(req)
|
||||
if err != nil {
|
||||
return nil, r.failedWorkspaceBuildf("start provision: %s", err)
|
||||
}
|
||||
nevermind := make(chan struct{})
|
||||
defer close(nevermind)
|
||||
go func() {
|
||||
select {
|
||||
case <-nevermind:
|
||||
return
|
||||
case <-r.notStopped.Done():
|
||||
return
|
||||
case <-r.notCanceled.Done():
|
||||
_ = r.session.Send(&sdkproto.Request{
|
||||
Type: &sdkproto.Request_Cancel{
|
||||
Cancel: &sdkproto.CancelRequest{},
|
||||
},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, err := r.session.Recv()
|
||||
if err != nil {
|
||||
return nil, r.failedWorkspaceBuildf("recv workspace provision: %s", err)
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
case *sdkproto.Response_Log:
|
||||
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "workspace provisioner job logged",
|
||||
slog.F("level", msgType.Log.Level),
|
||||
slog.F("output", msgType.Log.Output),
|
||||
slog.F("workspace_build_id", r.job.GetWorkspaceBuild().WorkspaceBuildId),
|
||||
)
|
||||
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER,
|
||||
Level: msgType.Log.Level,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
Output: msgType.Log.Output,
|
||||
Stage: stage,
|
||||
})
|
||||
case *sdkproto.Response_DataUpload:
|
||||
continue // Only for template imports
|
||||
case *sdkproto.Response_ChunkPiece:
|
||||
continue // Only for template imports
|
||||
default:
|
||||
// Stop looping!
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) commitQuota(ctx context.Context, resources []*sdkproto.Resource) *proto.FailedJob {
|
||||
cost := sumDailyCost(resources)
|
||||
func (r *Runner) commitQuota(ctx context.Context, cost int32) *proto.FailedJob {
|
||||
r.logger.Debug(ctx, "committing quota",
|
||||
slog.F("resources", resourceNames(resources)),
|
||||
slog.F("cost", cost),
|
||||
)
|
||||
if cost == 0 {
|
||||
@@ -953,9 +832,8 @@ func (r *Runner) commitQuota(ctx context.Context, resources []*sdkproto.Resource
|
||||
const stage = "Commit quota"
|
||||
|
||||
resp, err := r.quotaCommitter.CommitQuota(ctx, &proto.CommitQuotaRequest{
|
||||
JobId: r.job.JobId,
|
||||
// #nosec G115 - Safe conversion as cost is expected to be within int32 range for provisioning costs
|
||||
DailyCost: int32(cost),
|
||||
JobId: r.job.JobId,
|
||||
DailyCost: cost,
|
||||
})
|
||||
if err != nil {
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
@@ -1014,8 +892,6 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
}
|
||||
|
||||
failedJob := r.configure(&sdkproto.Config{
|
||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
||||
State: r.job.GetWorkspaceBuild().State,
|
||||
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
|
||||
TemplateId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateId),
|
||||
TemplateVersionId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateVersionId),
|
||||
@@ -1025,25 +901,53 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
return nil, failedJob
|
||||
}
|
||||
|
||||
resp, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Request{
|
||||
Type: &sdkproto.Request_Plan{
|
||||
Plan: &sdkproto.PlanRequest{
|
||||
OmitModuleFiles: true, // Only useful for template imports
|
||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
|
||||
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
|
||||
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
|
||||
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
|
||||
// timings collects all timings from each phase of the build
|
||||
timings := make([]*sdkproto.Timing, 0)
|
||||
|
||||
// Initialize the Terraform working directory
|
||||
initComplete, failedJob := r.init(ctx, true, r.job.GetTemplateSourceArchive())
|
||||
if failedJob != nil {
|
||||
return nil, failedJob
|
||||
}
|
||||
if initComplete == nil {
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type received from provisioner during init")
|
||||
}
|
||||
// Collect init timings
|
||||
timings = append(timings, initComplete.Timings...)
|
||||
if initComplete.Error != "" {
|
||||
r.logger.Warn(context.Background(), "init request failed",
|
||||
slog.F("error", initComplete.Error),
|
||||
)
|
||||
|
||||
return nil, &proto.FailedJob{
|
||||
JobId: r.job.JobId,
|
||||
Error: initComplete.Error,
|
||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
||||
State: r.job.GetWorkspaceBuild().State,
|
||||
Timings: timings,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run `terraform plan`
|
||||
planComplete, failed := r.plan(ctx, "Planning Infrastructure", &sdkproto.PlanRequest{
|
||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
|
||||
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
|
||||
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
|
||||
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
|
||||
State: r.job.GetWorkspaceBuild().State,
|
||||
})
|
||||
if failed != nil {
|
||||
return nil, failed
|
||||
}
|
||||
planComplete := resp.GetPlan()
|
||||
if planComplete == nil {
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", resp.Type)
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type received from provisioner during plan")
|
||||
}
|
||||
// Collect plan timings
|
||||
timings = append(timings, planComplete.Timings...)
|
||||
if planComplete.Error != "" {
|
||||
r.logger.Warn(context.Background(), "plan request failed",
|
||||
slog.F("error", planComplete.Error),
|
||||
@@ -1053,27 +957,28 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
JobId: r.job.JobId,
|
||||
Error: planComplete.Error,
|
||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{},
|
||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
||||
Timings: timings,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(planComplete.AiTasks) > 1 {
|
||||
return nil, r.failedWorkspaceBuildf("only one 'coder_ai_task' resource can be provisioned per template")
|
||||
|
||||
if planComplete.AiTaskCount > 1 {
|
||||
return nil, r.failedWorkspaceBuildf("only one 'coder_ai_task' resource can be provisioned per template, found %d", planComplete.AiTaskCount)
|
||||
}
|
||||
|
||||
r.logger.Info(context.Background(), "plan request successful",
|
||||
slog.F("resource_count", len(planComplete.Resources)),
|
||||
slog.F("resources", resourceNames(planComplete.Resources)),
|
||||
)
|
||||
r.logger.Info(context.Background(), "plan request successful")
|
||||
r.flushQueuedLogs(ctx)
|
||||
if commitQuota {
|
||||
failed = r.commitQuota(ctx, planComplete.Resources)
|
||||
failed = r.commitQuota(ctx, planComplete.GetDailyCost())
|
||||
r.flushQueuedLogs(ctx)
|
||||
if failed != nil {
|
||||
return nil, failed
|
||||
}
|
||||
}
|
||||
|
||||
// Run Terraform Apply
|
||||
r.queueLog(ctx, &proto.Log{
|
||||
Source: proto.LogSource_PROVISIONER_DAEMON,
|
||||
Level: sdkproto.LogLevel_INFO,
|
||||
@@ -1081,24 +986,17 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
})
|
||||
|
||||
resp, failed = r.buildWorkspace(ctx, applyStage, &sdkproto.Request{
|
||||
Type: &sdkproto.Request_Apply{
|
||||
Apply: &sdkproto.ApplyRequest{
|
||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||
},
|
||||
},
|
||||
applyComplete, failed := r.apply(ctx, applyStage, &sdkproto.ApplyRequest{
|
||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||
})
|
||||
if failed != nil {
|
||||
return nil, failed
|
||||
}
|
||||
applyComplete := resp.GetApply()
|
||||
if applyComplete == nil {
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", resp.Type)
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type received from provisioner during apply")
|
||||
}
|
||||
|
||||
// Prepend the plan timings (since they occurred first).
|
||||
applyComplete.Timings = append(planComplete.Timings, applyComplete.Timings...)
|
||||
|
||||
// Collect apply timings
|
||||
timings = append(timings, applyComplete.Timings...)
|
||||
if applyComplete.Error != "" {
|
||||
r.logger.Warn(context.Background(), "apply failed; updating state",
|
||||
slog.F("error", applyComplete.Error),
|
||||
@@ -1111,15 +1009,46 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
||||
State: applyComplete.State,
|
||||
Timings: applyComplete.Timings,
|
||||
Timings: timings,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run Terraform Graph
|
||||
graphComplete, failed := r.graph(ctx, &sdkproto.GraphRequest{
|
||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||
Source: sdkproto.GraphSource_SOURCE_STATE,
|
||||
})
|
||||
if failed != nil {
|
||||
return nil, failed
|
||||
}
|
||||
if graphComplete == nil {
|
||||
return nil, r.failedWorkspaceBuildf("invalid message type received from provisioner during graph")
|
||||
}
|
||||
// Collect graph timings
|
||||
timings = append(timings, graphComplete.Timings...)
|
||||
if graphComplete.Error != "" {
|
||||
r.logger.Warn(context.Background(), "graph request failed",
|
||||
slog.F("error", planComplete.Error),
|
||||
)
|
||||
|
||||
return nil, &proto.FailedJob{
|
||||
JobId: r.job.JobId,
|
||||
Error: graphComplete.Error,
|
||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
||||
// Graph does not change the state, so return the state returned from apply.
|
||||
State: applyComplete.State,
|
||||
Timings: timings,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info(context.Background(), "apply successful",
|
||||
slog.F("resource_count", len(applyComplete.Resources)),
|
||||
slog.F("resources", resourceNames(applyComplete.Resources)),
|
||||
slog.F("resource_count", len(graphComplete.Resources)),
|
||||
slog.F("resources", resourceNames(graphComplete.Resources)),
|
||||
slog.F("state_len", len(applyComplete.State)),
|
||||
)
|
||||
r.flushQueuedLogs(ctx)
|
||||
@@ -1129,15 +1058,14 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
||||
Type: &proto.CompletedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
||||
State: applyComplete.State,
|
||||
Resources: applyComplete.Resources,
|
||||
Timings: applyComplete.Timings,
|
||||
// Modules are created on disk by `terraform init`, and that is only
|
||||
// called by `plan`. `apply` does not modify them, so we can use the
|
||||
// modules from the plan response.
|
||||
Modules: planComplete.Modules,
|
||||
Resources: graphComplete.Resources,
|
||||
Timings: timings,
|
||||
// Modules files are omitted for workspace builds, but the modules.json metadata
|
||||
// is available from init to return.
|
||||
Modules: initComplete.Modules,
|
||||
// Resource replacements are discovered at plan time, only.
|
||||
ResourceReplacements: planComplete.ResourceReplacements,
|
||||
AiTasks: applyComplete.AiTasks,
|
||||
AiTasks: graphComplete.AiTasks,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -10,6 +10,10 @@ func ParseErrorf(format string, args ...any) *proto.ParseComplete {
|
||||
return &proto.ParseComplete{Error: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
func InitErrorf(format string, args ...any) *proto.InitComplete {
|
||||
return &proto.InitComplete{Error: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
func PlanErrorf(format string, args ...any) *proto.PlanComplete {
|
||||
return &proto.PlanComplete{Error: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
@@ -17,3 +21,7 @@ func PlanErrorf(format string, args ...any) *proto.PlanComplete {
|
||||
func ApplyErrorf(format string, args ...any) *proto.ApplyComplete {
|
||||
return &proto.ApplyComplete{Error: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
func GraphError(format string, args ...any) *proto.GraphComplete {
|
||||
return &proto.GraphComplete{Error: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
Generated
+962
-575
File diff suppressed because it is too large
Load Diff
@@ -369,16 +369,12 @@ message Metadata {
|
||||
|
||||
// Config represents execution configuration shared by all subsequent requests in the Session
|
||||
message Config {
|
||||
// template_source_archive is a tar of the template source files
|
||||
bytes template_source_archive = 1;
|
||||
// state is the provisioner state (if any)
|
||||
bytes state = 2;
|
||||
string provisioner_log_level = 3;
|
||||
string provisioner_log_level = 1;
|
||||
// Template imports can omit template id
|
||||
optional string template_id = 4;
|
||||
optional string template_id = 2;
|
||||
// Dry runs omit version id
|
||||
optional string template_version_id = 5;
|
||||
optional bool exp_reuse_terraform_workspace = 6; // Whether to reuse existing terraform workspaces if they exist.
|
||||
optional string template_version_id = 3;
|
||||
optional bool exp_reuse_terraform_workspace = 4; // Whether to reuse existing terraform workspaces if they exist.
|
||||
}
|
||||
|
||||
// ParseRequest consumes source-code to produce inputs.
|
||||
@@ -393,6 +389,25 @@ message ParseComplete {
|
||||
map<string, string> workspace_tags = 4;
|
||||
}
|
||||
|
||||
message InitRequest {
|
||||
// template_source_archive is a tar of the template source files
|
||||
bytes template_source_archive = 1;
|
||||
|
||||
// If true, the provisioner can safely assume the caller does not need the
|
||||
// module files downloaded by the `terraform init` command.
|
||||
// Ideally this boolean would be flipped in its truthy value, however since
|
||||
// this is costly, the zero value omitting the module files is preferred.
|
||||
bool omit_module_files = 3;
|
||||
}
|
||||
|
||||
message InitComplete {
|
||||
string error = 1;
|
||||
repeated Timing timings = 2;
|
||||
repeated Module modules = 3;
|
||||
bytes module_files = 4;
|
||||
bytes module_files_hash = 5;
|
||||
}
|
||||
|
||||
// PlanRequest asks the provisioner to plan what resources & parameters it will create
|
||||
message PlanRequest {
|
||||
Metadata metadata = 1;
|
||||
@@ -401,52 +416,61 @@ message PlanRequest {
|
||||
repeated ExternalAuthProvider external_auth_providers = 4;
|
||||
repeated RichParameterValue previous_parameter_values = 5;
|
||||
|
||||
// If true, the provisioner can safely assume the caller does not need the
|
||||
// module files downloaded by the `terraform init` command.
|
||||
// Ideally this boolean would be flipped in its truthy value, however for
|
||||
// backwards compatibility reasons, the zero value should be the previous
|
||||
// behavior of downloading the module files.
|
||||
bool omit_module_files = 6;
|
||||
// state is the provisioner state (if any)
|
||||
bytes state = 6;
|
||||
}
|
||||
|
||||
// PlanComplete indicates a request to plan completed.
|
||||
message PlanComplete {
|
||||
string error = 1;
|
||||
repeated Resource resources = 2;
|
||||
repeated RichParameter parameters = 3;
|
||||
repeated ExternalAuthProviderResource external_auth_providers = 4;
|
||||
repeated Timing timings = 6;
|
||||
repeated Module modules = 7;
|
||||
repeated Preset presets = 8;
|
||||
bytes plan = 9;
|
||||
repeated ResourceReplacement resource_replacements = 10;
|
||||
bytes module_files = 11;
|
||||
bytes module_files_hash = 12;
|
||||
// Whether a template has any `coder_ai_task` resources defined, even if not planned for creation.
|
||||
// During a template import, a plan is run which may not yield in any `coder_ai_task` resources, but nonetheless we
|
||||
// still need to know that such resources are defined.
|
||||
//
|
||||
// See `hasAITaskResources` in provisioner/terraform/resources.go for more details.
|
||||
bool has_ai_tasks = 13;
|
||||
repeated provisioner.AITask ai_tasks = 14;
|
||||
bool has_external_agents = 15;
|
||||
repeated Timing timings = 2;
|
||||
bytes plan = 3;
|
||||
int32 dailyCost = 4;
|
||||
repeated ResourceReplacement resource_replacements = 5;
|
||||
int32 ai_task_count = 6;
|
||||
}
|
||||
|
||||
// ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
|
||||
// in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
|
||||
message ApplyRequest {
|
||||
Metadata metadata = 1;
|
||||
// state is the provisioner state (if any)
|
||||
bytes state = 6;
|
||||
}
|
||||
|
||||
// ApplyComplete indicates a request to apply completed.
|
||||
message ApplyComplete {
|
||||
bytes state = 1;
|
||||
string error = 2;
|
||||
repeated Timing timings = 3;
|
||||
}
|
||||
|
||||
enum GraphSource {
|
||||
SOURCE_UNKNOWN = 0;
|
||||
SOURCE_PLAN = 1;
|
||||
SOURCE_STATE = 2;
|
||||
}
|
||||
|
||||
message GraphRequest {
|
||||
Metadata metadata = 1;
|
||||
GraphSource source = 2;
|
||||
}
|
||||
|
||||
message GraphComplete {
|
||||
string error = 1;
|
||||
repeated Timing timings = 2;
|
||||
repeated Resource resources = 3;
|
||||
repeated RichParameter parameters = 4;
|
||||
repeated ExternalAuthProviderResource external_auth_providers = 5;
|
||||
repeated Timing timings = 6;
|
||||
repeated provisioner.AITask ai_tasks = 7;
|
||||
repeated Preset presets = 6;
|
||||
// Whether a template has any `coder_ai_task` resources defined, even if not planned for creation.
|
||||
// During a template import, a plan is run which may not yield in any `coder_ai_task` resources, but nonetheless we
|
||||
// still need to know that such resources are defined.
|
||||
//
|
||||
// See `hasAITaskResources` in provisioner/terraform/resources.go for more details.
|
||||
bool has_ai_tasks = 7;
|
||||
repeated provisioner.AITask ai_tasks = 8;
|
||||
bool has_external_agents = 9;
|
||||
}
|
||||
|
||||
message Timing {
|
||||
@@ -472,9 +496,11 @@ message Request {
|
||||
oneof type {
|
||||
Config config = 1;
|
||||
ParseRequest parse = 2;
|
||||
PlanRequest plan = 3;
|
||||
ApplyRequest apply = 4;
|
||||
CancelRequest cancel = 5;
|
||||
InitRequest init = 3;
|
||||
PlanRequest plan = 4;
|
||||
ApplyRequest apply = 5;
|
||||
GraphRequest graph = 6;
|
||||
CancelRequest cancel = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,10 +508,12 @@ message Response {
|
||||
oneof type {
|
||||
Log log = 1;
|
||||
ParseComplete parse = 2;
|
||||
PlanComplete plan = 3;
|
||||
ApplyComplete apply = 4;
|
||||
DataUpload data_upload = 5;
|
||||
ChunkPiece chunk_piece = 6;
|
||||
InitComplete init = 3;
|
||||
PlanComplete plan = 4;
|
||||
ApplyComplete apply = 5;
|
||||
GraphComplete graph = 6;
|
||||
DataUpload data_upload = 7;
|
||||
ChunkPiece chunk_piece = 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,14 +547,28 @@ message ChunkPiece {
|
||||
|
||||
service Provisioner {
|
||||
// Session represents provisioning a single template import or workspace. The daemon always sends Config followed
|
||||
// by one of the requests (ParseRequest, PlanRequest, ApplyRequest). The provisioner should respond with a stream
|
||||
// of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
|
||||
// ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
|
||||
// and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
|
||||
// request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
|
||||
// by one of the requests (InitRequest, ParseRequest, PlanRequest, ApplyRequest, GraphRequest). The provisioner
|
||||
// should respond with a stream of zero or more Logs, followed by the corresponding complete message
|
||||
// (InitComplete, ParseComplete, PlanComplete, ApplyComplete, GraphComplete).
|
||||
// The daemon may then send a new request.
|
||||
//
|
||||
// The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
|
||||
// PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
|
||||
// that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
|
||||
// A request to Parse or Plan MUST be preceded by a request init. The provisioner should store the init data on
|
||||
// the session after a successful init. If the daemon closes the session, the init data may be safely discarded.
|
||||
//
|
||||
// A request to apply MUST be preceded by a request plan, and the provisioner should store the plan data on the
|
||||
// Session after a successful plan, so that the daemon may request an apply. If the daemon closes
|
||||
// the Session without an apply, the plan data may be safely discarded.
|
||||
//
|
||||
// A request to graph MUST be preceded by a plan or an apply.
|
||||
//
|
||||
// The order of requests is then one of the following:
|
||||
// 1. Init -> Parse
|
||||
// 2. Init -> Plan -> Graph
|
||||
// 3. Init -> Plan -> Apply -> Graph
|
||||
//
|
||||
// The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous InitRequest,
|
||||
// ParseRequest, PlanRequest, ApplyRequest, or GraphRequest. The provisioner MUST reply with a complete message
|
||||
// corresponding to the request that was canceled. If the provisioner has already completed the request,
|
||||
// it may ignore the CancelRequest.
|
||||
rpc Session(stream Request) returns (stream Response);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ type ServeOptions struct {
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
Init(s *Session, r *proto.InitRequest, canceledOrComplete <-chan struct{}) *proto.InitComplete
|
||||
Parse(s *Session, r *proto.ParseRequest, canceledOrComplete <-chan struct{}) *proto.ParseComplete
|
||||
Plan(s *Session, r *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete
|
||||
Apply(s *Session, r *proto.ApplyRequest, canceledOrComplete <-chan struct{}) *proto.ApplyComplete
|
||||
Graph(s *Session, r *proto.GraphRequest, canceledOrComplete <-chan struct{}) *proto.GraphComplete
|
||||
}
|
||||
|
||||
// Serve starts a dRPC connection for the provisioner and transport provided.
|
||||
|
||||
@@ -44,6 +44,11 @@ func TestProvisionerSDK(t *testing.T) {
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{}}})
|
||||
require.NoError(t, err)
|
||||
_, err = s.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||
require.NoError(t, err)
|
||||
msg, err := s.Recv()
|
||||
@@ -102,6 +107,11 @@ func TestProvisionerSDK(t *testing.T) {
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{}}})
|
||||
require.NoError(t, err)
|
||||
_, err = s.Recv()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||
require.NoError(t, err)
|
||||
msg, err := s.Recv()
|
||||
@@ -135,8 +145,18 @@ func TestProvisionerSDK(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
var _ provisionersdk.Server = unimplementedServer{}
|
||||
|
||||
type unimplementedServer struct{}
|
||||
|
||||
func (unimplementedServer) Init(s *provisionersdk.Session, r *proto.InitRequest, canceledOrComplete <-chan struct{}) *proto.InitComplete {
|
||||
return &proto.InitComplete{}
|
||||
}
|
||||
|
||||
func (unimplementedServer) Graph(s *provisionersdk.Session, r *proto.GraphRequest, canceledOrComplete <-chan struct{}) *proto.GraphComplete {
|
||||
return &proto.GraphComplete{Error: "unimplemented"}
|
||||
}
|
||||
|
||||
func (unimplementedServer) Parse(_ *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
|
||||
return &proto.ParseComplete{Error: "unimplemented"}
|
||||
}
|
||||
|
||||
+102
-41
@@ -66,10 +66,6 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
|
||||
return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err)
|
||||
}
|
||||
|
||||
err = s.Files.ExtractArchive(s.Context(), s.Logger, afero.NewOsFs(), s.Config)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("extract archive: %w", err)
|
||||
}
|
||||
return s.handleRequests()
|
||||
}
|
||||
|
||||
@@ -110,6 +106,10 @@ func (s *Session) handleRequests() error {
|
||||
}
|
||||
resp := &proto.Response{}
|
||||
if parse := req.GetParse(); parse != nil {
|
||||
if !s.initialized {
|
||||
// Files must be initialized before parsing.
|
||||
return xerrors.New("cannot parse before successful init")
|
||||
}
|
||||
r := &request[*proto.ParseRequest, *proto.ParseComplete]{
|
||||
req: parse,
|
||||
session: s,
|
||||
@@ -129,48 +129,28 @@ func (s *Session) handleRequests() error {
|
||||
}
|
||||
resp.Type = &proto.Response_Parse{Parse: complete}
|
||||
}
|
||||
if plan := req.GetPlan(); plan != nil {
|
||||
r := &request[*proto.PlanRequest, *proto.PlanComplete]{
|
||||
req: plan,
|
||||
session: s,
|
||||
serverFn: s.server.Plan,
|
||||
cancels: requests,
|
||||
if init := req.GetInit(); init != nil {
|
||||
if s.initialized {
|
||||
return xerrors.New("cannot init more than once per session")
|
||||
}
|
||||
complete, err := r.do()
|
||||
initResp, err := s.handleInitRequest(init, requests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Type = &proto.Response_Plan{Plan: complete}
|
||||
|
||||
if protobuf.Size(resp) > drpcsdk.MaxMessageSize {
|
||||
// It is likely the modules that is pushing the message size over the limit.
|
||||
// Send the modules over a stream of messages instead.
|
||||
s.Logger.Info(s.Context(), "plan response too large, sending modules as stream",
|
||||
slog.F("size_bytes", len(complete.ModuleFiles)),
|
||||
)
|
||||
dataUp, chunks := proto.BytesToDataUpload(proto.DataUploadType_UPLOAD_TYPE_MODULE_FILES, complete.ModuleFiles)
|
||||
|
||||
complete.ModuleFiles = nil // sent over the stream
|
||||
complete.ModuleFilesHash = dataUp.DataHash
|
||||
resp.Type = &proto.Response_Plan{Plan: complete}
|
||||
|
||||
err := s.stream.Send(&proto.Response{Type: &proto.Response_DataUpload{DataUpload: dataUp}})
|
||||
if err != nil {
|
||||
complete.Error = fmt.Sprintf("send data upload: %s", err.Error())
|
||||
} else {
|
||||
for i, chunk := range chunks {
|
||||
err := s.stream.Send(&proto.Response{Type: &proto.Response_ChunkPiece{ChunkPiece: chunk}})
|
||||
if err != nil {
|
||||
complete.Error = fmt.Sprintf("send data piece upload %d/%d: %s", i, dataUp.Chunks, err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.Type = &proto.Response_Init{Init: initResp}
|
||||
}
|
||||
if plan := req.GetPlan(); plan != nil {
|
||||
if !s.initialized {
|
||||
return xerrors.New("cannot plan before successful init")
|
||||
}
|
||||
|
||||
if complete.Error == "" {
|
||||
planResp, err := s.handlePlanRequest(plan, requests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if planResp.Error == "" {
|
||||
planned = true
|
||||
}
|
||||
resp.Type = &proto.Response_Plan{Plan: planResp}
|
||||
}
|
||||
if apply := req.GetApply(); apply != nil {
|
||||
if !planned {
|
||||
@@ -188,6 +168,23 @@ func (s *Session) handleRequests() error {
|
||||
}
|
||||
resp.Type = &proto.Response_Apply{Apply: complete}
|
||||
}
|
||||
if graph := req.GetGraph(); graph != nil {
|
||||
if !s.initialized {
|
||||
return xerrors.New("cannot graph before successful init")
|
||||
}
|
||||
|
||||
r := &request[*proto.GraphRequest, *proto.GraphComplete]{
|
||||
req: graph,
|
||||
session: s,
|
||||
serverFn: s.server.Graph,
|
||||
cancels: requests,
|
||||
}
|
||||
complete, err := r.do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Type = &proto.Response_Graph{Graph: complete}
|
||||
}
|
||||
err := s.stream.Send(resp)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("send response: %w", err)
|
||||
@@ -196,11 +193,75 @@ func (s *Session) handleRequests() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) handleInitRequest(init *proto.InitRequest, requests <-chan *proto.Request) (*proto.InitComplete, error) {
|
||||
r := &request[*proto.InitRequest, *proto.InitComplete]{
|
||||
req: init,
|
||||
session: s,
|
||||
serverFn: s.server.Init,
|
||||
cancels: requests,
|
||||
}
|
||||
complete, err := r.do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if complete.Error != "" {
|
||||
return complete, nil
|
||||
}
|
||||
|
||||
// If the size of the complete message is too large, we need to stream the module files separately.
|
||||
if protobuf.Size(&proto.Response{Type: &proto.Response_Init{Init: complete}}) > drpcsdk.MaxMessageSize {
|
||||
// It is likely the modules that is pushing the message size over the limit.
|
||||
// Send the modules over a stream of messages instead.
|
||||
s.Logger.Info(s.Context(), "plan response too large, sending modules as stream",
|
||||
slog.F("size_bytes", len(complete.ModuleFiles)),
|
||||
)
|
||||
dataUp, chunks := proto.BytesToDataUpload(proto.DataUploadType_UPLOAD_TYPE_MODULE_FILES, complete.ModuleFiles)
|
||||
|
||||
complete.ModuleFiles = nil // sent over the stream
|
||||
complete.ModuleFilesHash = dataUp.DataHash
|
||||
|
||||
err := s.stream.Send(&proto.Response{Type: &proto.Response_DataUpload{DataUpload: dataUp}})
|
||||
if err != nil {
|
||||
complete.Error = fmt.Sprintf("send data upload: %s", err.Error())
|
||||
} else {
|
||||
for i, chunk := range chunks {
|
||||
err := s.stream.Send(&proto.Response{Type: &proto.Response_ChunkPiece{ChunkPiece: chunk}})
|
||||
if err != nil {
|
||||
complete.Error = fmt.Sprintf("send data piece upload %d/%d: %s", i, dataUp.Chunks, err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.initialized = true
|
||||
|
||||
return complete, nil
|
||||
}
|
||||
|
||||
func (s *Session) handlePlanRequest(plan *proto.PlanRequest, requests <-chan *proto.Request) (*proto.PlanComplete, error) {
|
||||
r := &request[*proto.PlanRequest, *proto.PlanComplete]{
|
||||
req: plan,
|
||||
session: s,
|
||||
serverFn: s.server.Plan,
|
||||
cancels: requests,
|
||||
}
|
||||
complete, err := r.do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return complete, nil
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Logger slog.Logger
|
||||
Files tfpath.Layouter
|
||||
Config *proto.Config
|
||||
|
||||
// initialized indicates if an init was run.
|
||||
// Required for plan/apply.
|
||||
initialized bool
|
||||
|
||||
server Server
|
||||
stream proto.DRPCProvisioner_SessionStream
|
||||
logLevel int32
|
||||
@@ -226,11 +287,11 @@ func (s *Session) ProvisionLog(level proto.LogLevel, output string) {
|
||||
}
|
||||
|
||||
type pRequest interface {
|
||||
*proto.ParseRequest | *proto.PlanRequest | *proto.ApplyRequest
|
||||
*proto.ParseRequest | *proto.InitRequest | *proto.PlanRequest | *proto.ApplyRequest | *proto.GraphRequest
|
||||
}
|
||||
|
||||
type pComplete interface {
|
||||
*proto.ParseComplete | *proto.PlanComplete | *proto.ApplyComplete
|
||||
*proto.ParseComplete | *proto.InitComplete | *proto.PlanComplete | *proto.ApplyComplete | *proto.GraphComplete
|
||||
}
|
||||
|
||||
// request processes a single request call to the Server and returns its complete result, while also processing cancel
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
)
|
||||
|
||||
type Layouter interface {
|
||||
@@ -28,7 +27,7 @@ type Layouter interface {
|
||||
TerraformMetadataDir() string
|
||||
ModulesDirectory() string
|
||||
ModulesFilePath() string
|
||||
ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error
|
||||
ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, templateSourceArchive []byte) error
|
||||
Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs)
|
||||
CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error
|
||||
}
|
||||
@@ -93,9 +92,9 @@ func (l Layout) ModulesFilePath() string {
|
||||
return filepath.Join(l.ModulesDirectory(), "modules.json")
|
||||
}
|
||||
|
||||
func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error {
|
||||
func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, templateSourceArchive []byte) error {
|
||||
logger.Info(ctx, "unpacking template source archive",
|
||||
slog.F("size_bytes", len(cfg.TemplateSourceArchive)),
|
||||
slog.F("size_bytes", len(templateSourceArchive)),
|
||||
)
|
||||
|
||||
err := fs.MkdirAll(l.WorkDirectory(), 0o700)
|
||||
@@ -103,11 +102,7 @@ func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero
|
||||
return xerrors.Errorf("create work directory %q: %w", l.WorkDirectory(), err)
|
||||
}
|
||||
|
||||
// TODO: Pass in cfg.TemplateSourceArchive, not the full config.
|
||||
// niling out the config field is a bit hacky.
|
||||
reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
|
||||
// for safety, nil out the reference on Config, since the reader now owns it.
|
||||
cfg.TemplateSourceArchive = nil
|
||||
reader := tar.NewReader(bytes.NewBuffer(templateSourceArchive))
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err != nil {
|
||||
|
||||
@@ -140,9 +140,9 @@ func (td Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
|
||||
slog.F("path", path), slog.Error(err))
|
||||
}
|
||||
|
||||
func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error {
|
||||
func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, archive []byte) error {
|
||||
logger.Info(ctx, "unpacking template source archive",
|
||||
slog.F("size_bytes", len(cfg.TemplateSourceArchive)),
|
||||
slog.F("size_bytes", len(archive)),
|
||||
)
|
||||
|
||||
err := fs.MkdirAll(td.WorkDirectory(), 0o700)
|
||||
@@ -163,9 +163,7 @@ func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afer
|
||||
return xerrors.Errorf("select terraform workspace: %w", err)
|
||||
}
|
||||
|
||||
reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
|
||||
// for safety, nil out the reference on Config, since the reader now owns it.
|
||||
cfg.TemplateSourceArchive = nil
|
||||
reader := tar.NewReader(bytes.NewBuffer(archive))
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err != nil {
|
||||
|
||||
@@ -230,9 +230,9 @@ func setupRunnerTest(t *testing.T) (client *codersdk.Client, agentID uuid.UUID)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -43,10 +43,10 @@ func TestRun(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
|
||||
@@ -60,27 +60,11 @@ func Test_Runner(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: testParameters,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "hello from logs",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
@@ -101,6 +85,21 @@ func Test_Runner(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "hello from logs",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -209,10 +208,10 @@ func Test_Runner(t *testing.T) {
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: testParameters,
|
||||
},
|
||||
},
|
||||
@@ -341,27 +340,11 @@ func Test_Runner(t *testing.T) {
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: testParameters,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "hello from logs",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
@@ -382,6 +365,21 @@ func Test_Runner(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "hello from logs",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
version = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
@@ -484,10 +482,10 @@ func Test_Runner(t *testing.T) {
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Parameters: testParameters,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -257,9 +257,9 @@ func setupRunnerTest(t *testing.T) (client *codersdk.Client, agentID uuid.UUID)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -58,7 +58,14 @@ func Test_Runner(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Apply: &proto.ApplyComplete{},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example1",
|
||||
@@ -245,8 +252,10 @@ func Test_Runner(t *testing.T) {
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionInit: echo.InitComplete,
|
||||
ProvisionGraph: echo.GraphComplete,
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
|
||||
@@ -49,9 +49,9 @@ func TestRun(t *testing.T) {
|
||||
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
@@ -168,9 +168,9 @@ func TestRun(t *testing.T) {
|
||||
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
ProvisionGraph: []*proto.Response{{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
|
||||
@@ -39,10 +39,10 @@ func TestRun(t *testing.T) {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.PlanComplete,
|
||||
ProvisionApply: []*proto.Response{
|
||||
ProvisionGraph: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Type: &proto.Response_Graph{
|
||||
Graph: &proto.GraphComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Name: "example",
|
||||
|
||||
+110
-49
@@ -32,6 +32,8 @@ import {
|
||||
type ApplyComplete,
|
||||
AppSharingLevel,
|
||||
type ExternalAuthProviderResource,
|
||||
type GraphComplete,
|
||||
type InitComplete,
|
||||
type ParseComplete,
|
||||
type PlanComplete,
|
||||
type Resource,
|
||||
@@ -540,12 +542,14 @@ type RecursivePartial<T> = {
|
||||
};
|
||||
|
||||
interface EchoProvisionerResponses {
|
||||
init?: RecursivePartial<Response>[];
|
||||
// parse is for observing any Terraform variables
|
||||
parse?: RecursivePartial<Response>[];
|
||||
// plan occurs when the template is imported
|
||||
plan?: RecursivePartial<Response>[];
|
||||
// apply occurs when the workspace is built
|
||||
apply?: RecursivePartial<Response>[];
|
||||
graph?: RecursivePartial<Response>[];
|
||||
// extraFiles allows the bundling of terraform files in echo provisioner tars
|
||||
// in order to support dynamic parameters
|
||||
extraFiles?: Map<string, string>;
|
||||
@@ -560,6 +564,40 @@ const emptyPlan = new TextEncoder().encode("{}");
|
||||
const createTemplateVersionTar = async (
|
||||
responses: EchoProvisionerResponses = {},
|
||||
): Promise<Buffer> => {
|
||||
if (responses.graph) {
|
||||
if (!responses.apply) {
|
||||
responses.apply = responses.graph.map((response) => {
|
||||
if (response.log) {
|
||||
return response;
|
||||
}
|
||||
return {
|
||||
apply: {
|
||||
error: response.graph?.error ?? "",
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
if (!responses.plan) {
|
||||
responses.plan = responses.graph.map((response) => {
|
||||
if (response.log) {
|
||||
return response;
|
||||
}
|
||||
return {
|
||||
plan: {
|
||||
error: response.graph?.error ?? "",
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!responses.init) {
|
||||
responses.init = [
|
||||
{
|
||||
init: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!responses.parse) {
|
||||
responses.parse = [
|
||||
{
|
||||
@@ -575,25 +613,18 @@ const createTemplateVersionTar = async (
|
||||
];
|
||||
}
|
||||
if (!responses.plan) {
|
||||
responses.plan = responses.apply.map((response) => {
|
||||
if (response.log) {
|
||||
return response;
|
||||
}
|
||||
return {
|
||||
plan: {
|
||||
error: response.apply?.error ?? "",
|
||||
resources: response.apply?.resources ?? [],
|
||||
parameters: response.apply?.parameters ?? [],
|
||||
externalAuthProviders: response.apply?.externalAuthProviders ?? [],
|
||||
timings: response.apply?.timings ?? [],
|
||||
presets: [],
|
||||
resourceReplacements: [],
|
||||
plan: emptyPlan,
|
||||
moduleFiles: new Uint8Array(),
|
||||
moduleFilesHash: new Uint8Array(),
|
||||
},
|
||||
};
|
||||
});
|
||||
responses.plan = [
|
||||
{
|
||||
plan: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!responses.graph) {
|
||||
responses.graph = [
|
||||
{
|
||||
graph: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const tar = new TarWriter();
|
||||
@@ -617,6 +648,33 @@ const createTemplateVersionTar = async (
|
||||
Response.encode(response as Response).finish(),
|
||||
);
|
||||
});
|
||||
responses.init.forEach((response, index) => {
|
||||
response.init = {
|
||||
error: "",
|
||||
timings: [],
|
||||
modules: [],
|
||||
moduleFiles: new Uint8Array(),
|
||||
moduleFilesHash: new Uint8Array(),
|
||||
...response.init,
|
||||
} as InitComplete;
|
||||
tar.addFile(
|
||||
`${index}.init.protobuf`,
|
||||
Response.encode(response as Response).finish(),
|
||||
);
|
||||
});
|
||||
responses.plan.forEach((response, index) => {
|
||||
response.plan = {
|
||||
error: "",
|
||||
timings: [],
|
||||
plan: emptyPlan,
|
||||
resourceReplacements: [],
|
||||
...response.plan,
|
||||
} as PlanComplete;
|
||||
tar.addFile(
|
||||
`${index}.plan.protobuf`,
|
||||
Response.encode(response as Response).finish(),
|
||||
);
|
||||
});
|
||||
|
||||
const fillResource = (resource: RecursivePartial<Resource>) => {
|
||||
if (resource.agents) {
|
||||
@@ -701,40 +759,31 @@ const createTemplateVersionTar = async (
|
||||
response.apply = {
|
||||
error: "",
|
||||
state: new Uint8Array(),
|
||||
resources: [],
|
||||
parameters: [],
|
||||
externalAuthProviders: [],
|
||||
timings: [],
|
||||
aiTasks: [],
|
||||
...response.apply,
|
||||
} as ApplyComplete;
|
||||
response.apply.resources = response.apply.resources?.map(fillResource);
|
||||
|
||||
tar.addFile(
|
||||
`${index}.apply.protobuf`,
|
||||
Response.encode(response as Response).finish(),
|
||||
);
|
||||
});
|
||||
responses.plan.forEach((response, index) => {
|
||||
response.plan = {
|
||||
responses.graph.forEach((response, index) => {
|
||||
response.graph = {
|
||||
error: "",
|
||||
resources: [],
|
||||
parameters: [],
|
||||
externalAuthProviders: [],
|
||||
timings: [],
|
||||
modules: [],
|
||||
presets: [],
|
||||
resourceReplacements: [],
|
||||
plan: emptyPlan,
|
||||
moduleFiles: new Uint8Array(),
|
||||
moduleFilesHash: new Uint8Array(),
|
||||
aiTasks: [],
|
||||
...response.plan,
|
||||
} as PlanComplete;
|
||||
response.plan.resources = response.plan.resources?.map(fillResource);
|
||||
...response.graph,
|
||||
} as GraphComplete;
|
||||
response.graph.resources = response.graph.resources?.map(fillResource);
|
||||
|
||||
tar.addFile(
|
||||
`${index}.plan.protobuf`,
|
||||
`${index}.graph.protobuf`,
|
||||
Response.encode(response as Response).finish(),
|
||||
);
|
||||
});
|
||||
@@ -889,16 +938,20 @@ ${options}}
|
||||
parse: {},
|
||||
},
|
||||
],
|
||||
plan: [
|
||||
init: [
|
||||
{
|
||||
plan: {
|
||||
parameters: richParameters,
|
||||
},
|
||||
init: {},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
plan: [
|
||||
{
|
||||
apply: {
|
||||
plan: {},
|
||||
},
|
||||
],
|
||||
graph: [
|
||||
{
|
||||
graph: {
|
||||
parameters: richParameters,
|
||||
resources: [
|
||||
{
|
||||
name: "example",
|
||||
@@ -907,6 +960,11 @@ ${options}}
|
||||
},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
{
|
||||
apply: {},
|
||||
},
|
||||
],
|
||||
extraFiles: new Map([["main.tf", tf]]),
|
||||
};
|
||||
};
|
||||
@@ -915,21 +973,19 @@ export const echoResponsesWithExternalAuth = (
|
||||
providers: ExternalAuthProviderResource[],
|
||||
): EchoProvisionerResponses => {
|
||||
return {
|
||||
init: [
|
||||
{
|
||||
init: {},
|
||||
},
|
||||
],
|
||||
parse: [
|
||||
{
|
||||
parse: {},
|
||||
},
|
||||
],
|
||||
plan: [
|
||||
graph: [
|
||||
{
|
||||
plan: {
|
||||
externalAuthProviders: providers,
|
||||
},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
graph: {
|
||||
externalAuthProviders: providers,
|
||||
resources: [
|
||||
{
|
||||
@@ -939,6 +995,11 @@ export const echoResponsesWithExternalAuth = (
|
||||
},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
{
|
||||
apply: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Generated
+194
-99
@@ -69,6 +69,13 @@ export enum PrebuiltWorkspaceBuildStage {
|
||||
UNRECOGNIZED = -1,
|
||||
}
|
||||
|
||||
export enum GraphSource {
|
||||
SOURCE_UNKNOWN = 0,
|
||||
SOURCE_PLAN = 1,
|
||||
SOURCE_STATE = 2,
|
||||
UNRECOGNIZED = -1,
|
||||
}
|
||||
|
||||
export enum TimingState {
|
||||
STARTED = 0,
|
||||
COMPLETED = 1,
|
||||
@@ -410,10 +417,6 @@ export interface Metadata {
|
||||
|
||||
/** Config represents execution configuration shared by all subsequent requests in the Session */
|
||||
export interface Config {
|
||||
/** template_source_archive is a tar of the template source files */
|
||||
templateSourceArchive: Uint8Array;
|
||||
/** state is the provisioner state (if any) */
|
||||
state: Uint8Array;
|
||||
provisionerLogLevel: string;
|
||||
/** Template imports can omit template id */
|
||||
templateId?:
|
||||
@@ -444,6 +447,26 @@ export interface ParseComplete_WorkspaceTagsEntry {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface InitRequest {
|
||||
/** template_source_archive is a tar of the template source files */
|
||||
templateSourceArchive: Uint8Array;
|
||||
/**
|
||||
* If true, the provisioner can safely assume the caller does not need the
|
||||
* module files downloaded by the `terraform init` command.
|
||||
* Ideally this boolean would be flipped in its truthy value, however since
|
||||
* this is costly, the zero value omitting the module files is preferred.
|
||||
*/
|
||||
omitModuleFiles: boolean;
|
||||
}
|
||||
|
||||
export interface InitComplete {
|
||||
error: string;
|
||||
timings: Timing[];
|
||||
modules: Module[];
|
||||
moduleFiles: Uint8Array;
|
||||
moduleFilesHash: Uint8Array;
|
||||
}
|
||||
|
||||
/** PlanRequest asks the provisioner to plan what resources & parameters it will create */
|
||||
export interface PlanRequest {
|
||||
metadata: Metadata | undefined;
|
||||
@@ -451,29 +474,51 @@ export interface PlanRequest {
|
||||
variableValues: VariableValue[];
|
||||
externalAuthProviders: ExternalAuthProvider[];
|
||||
previousParameterValues: RichParameterValue[];
|
||||
/**
|
||||
* If true, the provisioner can safely assume the caller does not need the
|
||||
* module files downloaded by the `terraform init` command.
|
||||
* Ideally this boolean would be flipped in its truthy value, however for
|
||||
* backwards compatibility reasons, the zero value should be the previous
|
||||
* behavior of downloading the module files.
|
||||
*/
|
||||
omitModuleFiles: boolean;
|
||||
/** state is the provisioner state (if any) */
|
||||
state: Uint8Array;
|
||||
}
|
||||
|
||||
/** PlanComplete indicates a request to plan completed. */
|
||||
export interface PlanComplete {
|
||||
error: string;
|
||||
timings: Timing[];
|
||||
plan: Uint8Array;
|
||||
dailyCost: number;
|
||||
resourceReplacements: ResourceReplacement[];
|
||||
aiTaskCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
|
||||
* in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
|
||||
*/
|
||||
export interface ApplyRequest {
|
||||
metadata:
|
||||
| Metadata
|
||||
| undefined;
|
||||
/** state is the provisioner state (if any) */
|
||||
state: Uint8Array;
|
||||
}
|
||||
|
||||
/** ApplyComplete indicates a request to apply completed. */
|
||||
export interface ApplyComplete {
|
||||
state: Uint8Array;
|
||||
error: string;
|
||||
timings: Timing[];
|
||||
}
|
||||
|
||||
export interface GraphRequest {
|
||||
metadata: Metadata | undefined;
|
||||
source: GraphSource;
|
||||
}
|
||||
|
||||
export interface GraphComplete {
|
||||
error: string;
|
||||
timings: Timing[];
|
||||
resources: Resource[];
|
||||
parameters: RichParameter[];
|
||||
externalAuthProviders: ExternalAuthProviderResource[];
|
||||
timings: Timing[];
|
||||
modules: Module[];
|
||||
presets: Preset[];
|
||||
plan: Uint8Array;
|
||||
resourceReplacements: ResourceReplacement[];
|
||||
moduleFiles: Uint8Array;
|
||||
moduleFilesHash: Uint8Array;
|
||||
/**
|
||||
* Whether a template has any `coder_ai_task` resources defined, even if not planned for creation.
|
||||
* During a template import, a plan is run which may not yield in any `coder_ai_task` resources, but nonetheless we
|
||||
@@ -486,25 +531,6 @@ export interface PlanComplete {
|
||||
hasExternalAgents: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
|
||||
* in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
|
||||
*/
|
||||
export interface ApplyRequest {
|
||||
metadata: Metadata | undefined;
|
||||
}
|
||||
|
||||
/** ApplyComplete indicates a request to apply completed. */
|
||||
export interface ApplyComplete {
|
||||
state: Uint8Array;
|
||||
error: string;
|
||||
resources: Resource[];
|
||||
parameters: RichParameter[];
|
||||
externalAuthProviders: ExternalAuthProviderResource[];
|
||||
timings: Timing[];
|
||||
aiTasks: AITask[];
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
start: Date | undefined;
|
||||
end: Date | undefined;
|
||||
@@ -522,16 +548,20 @@ export interface CancelRequest {
|
||||
export interface Request {
|
||||
config?: Config | undefined;
|
||||
parse?: ParseRequest | undefined;
|
||||
init?: InitRequest | undefined;
|
||||
plan?: PlanRequest | undefined;
|
||||
apply?: ApplyRequest | undefined;
|
||||
graph?: GraphRequest | undefined;
|
||||
cancel?: CancelRequest | undefined;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
log?: Log | undefined;
|
||||
parse?: ParseComplete | undefined;
|
||||
init?: InitComplete | undefined;
|
||||
plan?: PlanComplete | undefined;
|
||||
apply?: ApplyComplete | undefined;
|
||||
graph?: GraphComplete | undefined;
|
||||
dataUpload?: DataUpload | undefined;
|
||||
chunkPiece?: ChunkPiece | undefined;
|
||||
}
|
||||
@@ -1318,23 +1348,17 @@ export const Metadata = {
|
||||
|
||||
export const Config = {
|
||||
encode(message: Config, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.templateSourceArchive.length !== 0) {
|
||||
writer.uint32(10).bytes(message.templateSourceArchive);
|
||||
}
|
||||
if (message.state.length !== 0) {
|
||||
writer.uint32(18).bytes(message.state);
|
||||
}
|
||||
if (message.provisionerLogLevel !== "") {
|
||||
writer.uint32(26).string(message.provisionerLogLevel);
|
||||
writer.uint32(10).string(message.provisionerLogLevel);
|
||||
}
|
||||
if (message.templateId !== undefined) {
|
||||
writer.uint32(34).string(message.templateId);
|
||||
writer.uint32(18).string(message.templateId);
|
||||
}
|
||||
if (message.templateVersionId !== undefined) {
|
||||
writer.uint32(42).string(message.templateVersionId);
|
||||
writer.uint32(26).string(message.templateVersionId);
|
||||
}
|
||||
if (message.expReuseTerraformWorkspace !== undefined) {
|
||||
writer.uint32(48).bool(message.expReuseTerraformWorkspace);
|
||||
writer.uint32(32).bool(message.expReuseTerraformWorkspace);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1376,6 +1400,39 @@ export const ParseComplete_WorkspaceTagsEntry = {
|
||||
},
|
||||
};
|
||||
|
||||
export const InitRequest = {
|
||||
encode(message: InitRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.templateSourceArchive.length !== 0) {
|
||||
writer.uint32(10).bytes(message.templateSourceArchive);
|
||||
}
|
||||
if (message.omitModuleFiles !== false) {
|
||||
writer.uint32(24).bool(message.omitModuleFiles);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
export const InitComplete = {
|
||||
encode(message: InitComplete, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.error !== "") {
|
||||
writer.uint32(10).string(message.error);
|
||||
}
|
||||
for (const v of message.timings) {
|
||||
Timing.encode(v!, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.modules) {
|
||||
Module.encode(v!, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
if (message.moduleFiles.length !== 0) {
|
||||
writer.uint32(34).bytes(message.moduleFiles);
|
||||
}
|
||||
if (message.moduleFilesHash.length !== 0) {
|
||||
writer.uint32(42).bytes(message.moduleFilesHash);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
export const PlanRequest = {
|
||||
encode(message: PlanRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.metadata !== undefined) {
|
||||
@@ -1393,8 +1450,8 @@ export const PlanRequest = {
|
||||
for (const v of message.previousParameterValues) {
|
||||
RichParameterValue.encode(v!, writer.uint32(42).fork()).ldelim();
|
||||
}
|
||||
if (message.omitModuleFiles !== false) {
|
||||
writer.uint32(48).bool(message.omitModuleFiles);
|
||||
if (message.state.length !== 0) {
|
||||
writer.uint32(50).bytes(message.state);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1405,44 +1462,20 @@ export const PlanComplete = {
|
||||
if (message.error !== "") {
|
||||
writer.uint32(10).string(message.error);
|
||||
}
|
||||
for (const v of message.resources) {
|
||||
Resource.encode(v!, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.parameters) {
|
||||
RichParameter.encode(v!, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.externalAuthProviders) {
|
||||
ExternalAuthProviderResource.encode(v!, writer.uint32(34).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.timings) {
|
||||
Timing.encode(v!, writer.uint32(50).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.modules) {
|
||||
Module.encode(v!, writer.uint32(58).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.presets) {
|
||||
Preset.encode(v!, writer.uint32(66).fork()).ldelim();
|
||||
Timing.encode(v!, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
if (message.plan.length !== 0) {
|
||||
writer.uint32(74).bytes(message.plan);
|
||||
writer.uint32(26).bytes(message.plan);
|
||||
}
|
||||
if (message.dailyCost !== 0) {
|
||||
writer.uint32(32).int32(message.dailyCost);
|
||||
}
|
||||
for (const v of message.resourceReplacements) {
|
||||
ResourceReplacement.encode(v!, writer.uint32(82).fork()).ldelim();
|
||||
ResourceReplacement.encode(v!, writer.uint32(42).fork()).ldelim();
|
||||
}
|
||||
if (message.moduleFiles.length !== 0) {
|
||||
writer.uint32(90).bytes(message.moduleFiles);
|
||||
}
|
||||
if (message.moduleFilesHash.length !== 0) {
|
||||
writer.uint32(98).bytes(message.moduleFilesHash);
|
||||
}
|
||||
if (message.hasAiTasks !== false) {
|
||||
writer.uint32(104).bool(message.hasAiTasks);
|
||||
}
|
||||
for (const v of message.aiTasks) {
|
||||
AITask.encode(v!, writer.uint32(114).fork()).ldelim();
|
||||
}
|
||||
if (message.hasExternalAgents !== false) {
|
||||
writer.uint32(120).bool(message.hasExternalAgents);
|
||||
if (message.aiTaskCount !== 0) {
|
||||
writer.uint32(48).int32(message.aiTaskCount);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1453,6 +1486,9 @@ export const ApplyRequest = {
|
||||
if (message.metadata !== undefined) {
|
||||
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim();
|
||||
}
|
||||
if (message.state.length !== 0) {
|
||||
writer.uint32(50).bytes(message.state);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
@@ -1465,6 +1501,33 @@ export const ApplyComplete = {
|
||||
if (message.error !== "") {
|
||||
writer.uint32(18).string(message.error);
|
||||
}
|
||||
for (const v of message.timings) {
|
||||
Timing.encode(v!, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
export const GraphRequest = {
|
||||
encode(message: GraphRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.metadata !== undefined) {
|
||||
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim();
|
||||
}
|
||||
if (message.source !== 0) {
|
||||
writer.uint32(16).int32(message.source);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
export const GraphComplete = {
|
||||
encode(message: GraphComplete, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.error !== "") {
|
||||
writer.uint32(10).string(message.error);
|
||||
}
|
||||
for (const v of message.timings) {
|
||||
Timing.encode(v!, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.resources) {
|
||||
Resource.encode(v!, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
@@ -1474,11 +1537,17 @@ export const ApplyComplete = {
|
||||
for (const v of message.externalAuthProviders) {
|
||||
ExternalAuthProviderResource.encode(v!, writer.uint32(42).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.timings) {
|
||||
Timing.encode(v!, writer.uint32(50).fork()).ldelim();
|
||||
for (const v of message.presets) {
|
||||
Preset.encode(v!, writer.uint32(50).fork()).ldelim();
|
||||
}
|
||||
if (message.hasAiTasks !== false) {
|
||||
writer.uint32(56).bool(message.hasAiTasks);
|
||||
}
|
||||
for (const v of message.aiTasks) {
|
||||
AITask.encode(v!, writer.uint32(58).fork()).ldelim();
|
||||
AITask.encode(v!, writer.uint32(66).fork()).ldelim();
|
||||
}
|
||||
if (message.hasExternalAgents !== false) {
|
||||
writer.uint32(72).bool(message.hasExternalAgents);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1525,14 +1594,20 @@ export const Request = {
|
||||
if (message.parse !== undefined) {
|
||||
ParseRequest.encode(message.parse, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
if (message.init !== undefined) {
|
||||
InitRequest.encode(message.init, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
if (message.plan !== undefined) {
|
||||
PlanRequest.encode(message.plan, writer.uint32(26).fork()).ldelim();
|
||||
PlanRequest.encode(message.plan, writer.uint32(34).fork()).ldelim();
|
||||
}
|
||||
if (message.apply !== undefined) {
|
||||
ApplyRequest.encode(message.apply, writer.uint32(34).fork()).ldelim();
|
||||
ApplyRequest.encode(message.apply, writer.uint32(42).fork()).ldelim();
|
||||
}
|
||||
if (message.graph !== undefined) {
|
||||
GraphRequest.encode(message.graph, writer.uint32(50).fork()).ldelim();
|
||||
}
|
||||
if (message.cancel !== undefined) {
|
||||
CancelRequest.encode(message.cancel, writer.uint32(42).fork()).ldelim();
|
||||
CancelRequest.encode(message.cancel, writer.uint32(58).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1546,17 +1621,23 @@ export const Response = {
|
||||
if (message.parse !== undefined) {
|
||||
ParseComplete.encode(message.parse, writer.uint32(18).fork()).ldelim();
|
||||
}
|
||||
if (message.init !== undefined) {
|
||||
InitComplete.encode(message.init, writer.uint32(26).fork()).ldelim();
|
||||
}
|
||||
if (message.plan !== undefined) {
|
||||
PlanComplete.encode(message.plan, writer.uint32(26).fork()).ldelim();
|
||||
PlanComplete.encode(message.plan, writer.uint32(34).fork()).ldelim();
|
||||
}
|
||||
if (message.apply !== undefined) {
|
||||
ApplyComplete.encode(message.apply, writer.uint32(34).fork()).ldelim();
|
||||
ApplyComplete.encode(message.apply, writer.uint32(42).fork()).ldelim();
|
||||
}
|
||||
if (message.graph !== undefined) {
|
||||
GraphComplete.encode(message.graph, writer.uint32(50).fork()).ldelim();
|
||||
}
|
||||
if (message.dataUpload !== undefined) {
|
||||
DataUpload.encode(message.dataUpload, writer.uint32(42).fork()).ldelim();
|
||||
DataUpload.encode(message.dataUpload, writer.uint32(58).fork()).ldelim();
|
||||
}
|
||||
if (message.chunkPiece !== undefined) {
|
||||
ChunkPiece.encode(message.chunkPiece, writer.uint32(50).fork()).ldelim();
|
||||
ChunkPiece.encode(message.chunkPiece, writer.uint32(66).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
@@ -1598,15 +1679,29 @@ export const ChunkPiece = {
|
||||
export interface Provisioner {
|
||||
/**
|
||||
* Session represents provisioning a single template import or workspace. The daemon always sends Config followed
|
||||
* by one of the requests (ParseRequest, PlanRequest, ApplyRequest). The provisioner should respond with a stream
|
||||
* of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
|
||||
* ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
|
||||
* and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
|
||||
* request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
|
||||
* by one of the requests (InitRequest, ParseRequest, PlanRequest, ApplyRequest, GraphRequest). The provisioner
|
||||
* should respond with a stream of zero or more Logs, followed by the corresponding complete message
|
||||
* (InitComplete, ParseComplete, PlanComplete, ApplyComplete, GraphComplete).
|
||||
* The daemon may then send a new request.
|
||||
*
|
||||
* The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
|
||||
* PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
|
||||
* that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
|
||||
* A request to Parse or Plan MUST be preceded by a request init. The provisioner should store the init data on
|
||||
* the session after a successful init. If the daemon closes the session, the init data may be safely discarded.
|
||||
*
|
||||
* A request to apply MUST be preceded by a request plan, and the provisioner should store the plan data on the
|
||||
* Session after a successful plan, so that the daemon may request an apply. If the daemon closes
|
||||
* the Session without an apply, the plan data may be safely discarded.
|
||||
*
|
||||
* A request to graph MUST be preceded by a plan or an apply.
|
||||
*
|
||||
* The order of requests is then one of the following:
|
||||
* 1. Init -> Parse
|
||||
* 2. Init -> Plan -> Graph
|
||||
* 3. Init -> Plan -> Apply -> Graph
|
||||
*
|
||||
* The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous InitRequest,
|
||||
* ParseRequest, PlanRequest, ApplyRequest, or GraphRequest. The provisioner MUST reply with a complete message
|
||||
* corresponding to the request that was canceled. If the provisioner has already completed the request,
|
||||
* it may ignore the CancelRequest.
|
||||
*/
|
||||
Session(request: Observable<Request>): Observable<Response>;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ test("app", async ({ context, page }) => {
|
||||
}
|
||||
const appName = "test-app";
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
graph: [
|
||||
{
|
||||
apply: {
|
||||
graph: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
|
||||
@@ -25,9 +25,9 @@ test.skip(`ssh with agent ${agentVersion}`, async ({ page }) => {
|
||||
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
graph: [
|
||||
{
|
||||
apply: {
|
||||
graph: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
|
||||
@@ -23,9 +23,9 @@ test.beforeEach(async ({ page }) => {
|
||||
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
graph: [
|
||||
{
|
||||
apply: {
|
||||
graph: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
|
||||
@@ -18,9 +18,9 @@ test.beforeEach(async ({ page }) => {
|
||||
test("web terminal", async ({ context, page }) => {
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
graph: [
|
||||
{
|
||||
apply: {
|
||||
graph: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
|
||||
@@ -32,7 +32,7 @@ test.beforeEach(async ({ page }) => {
|
||||
test("create workspace", async ({ page }) => {
|
||||
await login(page, users.templateAdmin);
|
||||
const template = await createTemplate(page, {
|
||||
apply: [{ apply: { resources: [{ name: "example" }] } }],
|
||||
graph: [{ graph: { resources: [{ name: "example" }] } }],
|
||||
});
|
||||
|
||||
await login(page, users.member);
|
||||
|
||||
@@ -246,16 +246,6 @@ export const provisioningStages: Stage[] = [
|
||||
"Compare state of desired vs actual resources and compute changes to be made.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "graph",
|
||||
label: "graph",
|
||||
section: "provisioning",
|
||||
tooltip: {
|
||||
heading: "Terraform graph",
|
||||
description:
|
||||
"List all resources in plan, used to update coderd database.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apply",
|
||||
label: "apply",
|
||||
@@ -266,6 +256,16 @@ export const provisioningStages: Stage[] = [
|
||||
"Execute Terraform plan to create/modify/delete resources into desired states.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "graph",
|
||||
label: "graph",
|
||||
section: "provisioning",
|
||||
tooltip: {
|
||||
heading: "Terraform graph",
|
||||
description:
|
||||
"List all resources in plan, used to update coderd database.",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const agentStages = (section: string): Stage[] => {
|
||||
|
||||
Reference in New Issue
Block a user